在PowerShell中将x XML节点设置为某个值

3

我有一个PowerShell脚本,其中脚本将设置XML文件的值。 当所有子节点都具有唯一名称时,这很完美地运行。 但是,我正在将XML文件调整为其中某些节点重复的文件。 现在我在PowerShell中收到错误。

我的问题是,如何通过PowerShell将XML中的第X个节点设置为特定值?

简而言之,我的脚本的工作方式如下:

cls

[xml] $xml1 = '<Lvl1>
                    <Lvl2>""</Lvl2>
                    <Lvl2>""</Lvl2>
                </Lvl1>' 

$xml1.Lvl1.Lvl2='./'

$xml1.Save("text.xml")

有两次相同的节点(Lvl2),这就是我在PowerShell中遇到以下错误的原因:"无法设置“Lvl2”,因为只能将字符串用作设置XmlNode属性的值。"

当我删除一个(Lvl2)节点时,脚本就可以正常工作。

请给予建议。


请参见以下链接:https://dev59.com/s1gQ5IYBdhLWcg3wfT5R - Luuk
@luuk,我在你发送给我的链接中没有看到我的问题的答案。我能够更改节点属性。但是,当有多个具有相同名称的节点时,我就卡住了。 - Sunfile
5个回答

3
你可以始终使用.NET语法,它的效果非常好。
$xml1.SelectSingleNode('Lvl1/Lvl2[2]').InnerText='./'

1
$xml1 = [xml]@'
    <Lvl1>
        <Lvl2>""</Lvl2>
        <Lvl2>""</Lvl2>
    </Lvl1>
'@

$xml1.GetElementsByTagName('Lvl2') | ForEach-Object { $_.InnerText = './' }

$xml1.Save("text.xml")

Get-Content -Path "text.xml" # debugging output

调试输出:

文件路径.\SO\60280990.ps1

<Lvl1>
  <Lvl2>./</Lvl2>
  <Lvl2>./</Lvl2>
</Lvl1>

上述方法即使对于稍微复杂一些的输入数据也适用,例如以下情况:

$xml1 = [xml]@'
<root>
    <Lvl1>
        <Lvl2>"a"</Lvl2>
        <Lvl2>"b"</Lvl2>
    </Lvl1>
    <Lvl0>
        <Lvl1>
            <Lvl2>"c"</Lvl2>
            <Lvl2>"d"</Lvl2>
        </Lvl1>
    </Lvl0>
</root>
'@

1
你可以将Lvl1的子节点作为数组获取,然后使用索引来调整所需的节点:
[xml] $xml1 = '<Lvl1>
                    <Lvl2>""</Lvl2>
                    <Lvl2>""</Lvl2>
                </Lvl1>' 

$lvl2Nodes = @($xml1.Lvl1.ChildNodes)

$lvl2Nodes[1].'#text' = "blah"  # updating the second childnode only

$xml1.Save("D:\text.xml")

结果:

<Lvl1>
  <Lvl2>""</Lvl2>
  <Lvl2>blah</Lvl2>
</Lvl1>

0
PS D:\TEMP> type lvl.xml
<Lvl1>
                    <Lvl2>"test1"</Lvl2>
                    <Lvl2>"test2"</Lvl2>
                </Lvl1>
PS D:\TEMP> $xml = New-Object XML
PS D:\TEMP> $xml.Load("lvl.xml")
PS D:\TEMP> $element =  $xml.SelectSingleNode("/Lvl1/Lvl2[2]")
PS D:\TEMP> $element.InnerText ="bla"
PS D:\TEMP> $xml.Save("lvl2.xml")
PS D:\TEMP> type lvl2.xml
<Lvl1>
  <Lvl2>"test1"</Lvl2>
  <Lvl2>bla</Lvl2>
</Lvl1>
PS D:\TEMP>

在XmlPath(即'/Lvl1/Lvl2[2]')中,'[2]'表示此事物存在的第二次。

0

Paweł Dyl的有用答案提供了一个简洁的解决方案,绕过了PowerShell对XML DOM的适应, 而是直接使用.NET方法。

然而,PowerShell的点符号表示法既方便又使用熟悉的语法,因此在可能和适当的情况下值得坚持使用它

不幸的是,在这种情况下,只有通过混合方法才能实现,即将点符号表示法(直接使用元素或属性名称作为属性名称)与底层System.Xml.XmlNode类型的本机属性相结合:

注意:我在下面的命令中使用以下修改后的XML和变量名$xml

[xml] $xml = @'
<Lvl1>
  <Lvl2>a</Lvl2>
  <Lvl2>b</Lvl2>
  <Other><Stuff>c</Stuff></Other>
</Lvl1>
'@ 

为了给第一个 Lvl2 子元素赋值,您可以使用以下方法:

# Assign to the *first* Lvl2 element.
$xml.Lvl1['Lvl2'].InnerText = './'
  • 使用 索引器 (['Lvl2']) 而不是 点符号 (.Lvl2),利用 System.Xml.XmlElement 类型的 本地索引器 来定位给定 名称第一个 子元素。

  • 然后可以将该元素的 .InnerText 属性赋值。

如果需要为具有给定索引的子元素分配值,则需要使用不同的方法,使用 XmlElement.ChildNodes 集合的数字 (0-based) 索引(Theo's helpful answer 的简化版本):

# Assign to the *last* Lvl2 element.
$xml1.Lvl1.ChildNodes[-1].InnerText = 'last'

PowerShell [Core] 7.0 不支持以下操作:

不幸的是,仅使用点符号直接对子元素进行索引赋值操作是行不通的

# DOESN'T WORK: PowerShell *quietly ignores* the assignment.
$xml.Lvl1.Lvl2[-1] = 'last' 

这种令人惊讶的行为已经在这个GitHub问题中被报道。

然而,通过这种方式获取值(通常)是正常工作的

# OK
PS $xml.Lvl1.Lvl2[0]
a

注意

如果一个命名元素恰好是仅有的子元素并且它本身有元素子元素,则索引无法使用,这会防止 PowerShell 通常提供的标量和集合的统一处理:

# DOES NOT WORK, because there is only a single 'Other' element
# *and* it has a child element.
PS $xml.Lvl1.Other[0]
 # !! No output

令人惊讶的是,PowerShell使用类型本地的按名称索引器来查找名为'0'的元素 - 根据定义,这将失败,因为XML元素名称不能以数字开头。

这个GitHub建议在这种情况下建议使用PowerShell的自动位置索引,基于参数是一个整数


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接