在 Powershell 中将 x XML 节点设置为值

Set x XML node to value in Powershell

我有这个 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 中收到以下错误的原因:"Cannot set "Lvl2" 因为只能将字符串用作设置 XmlNode 属性的值。"

当我删除一个 (Lvl2) 节点时,该脚本非常有效。

请指教。

您始终可以使用 .NET 语法,它很有魅力。

$xml1.SelectSingleNode('Lvl1/Lvl2[2]').InnerText='./'
$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

调试输出.\SO280990.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>
'@

可以将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>
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]”指的是该对象第二次存在。

provides a concise solution that bypasses ,它使用熟悉的 点符号 ,有利于直接使用 .NET 方法。

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

不幸的是,在手头的情况下,这只能通过 混合 方法,结合点符号(使用元素或属性名称与 属性 名称一样直接)具有基础 System.Xml.XmlNode 类型的本机属性:

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

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

firstLvl2child赋值,可以使用以下方法:

# Assign to the *first* Lvl2 element.
$xml.Lvl1['Lvl2'].InnerText = './'
  • 使用 索引器 (['Lvl2']) 而不是 点符号 (.Lvl2 ) 利用 System.Xml.XmlElement 类型的 type-native indexer 通过给定的 name[= 定位 first 子元素100=].

  • 那个元素的.InnerText属性然后可以赋值给。

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

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

从 PowerShell [Core] 7.0 开始, 不起作用

不幸的是,单独使用点符号直接索引到子元素中 assignments[=101] =]:

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

这种令人惊讶的行为已在 this GitHub issue 中报告。

但是,通过这种方式(通常)获取一个值效果很好:

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

警告

如果命名元素恰好是唯一子元素它本身有元素children,索引 工作 ,阻止 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 使用原生类型 by name 索引器来查找元素 named '0' -定义失败,因为 XML 元素名称不能以 digits.

开头

This GitHub suggestion 建议在这种情况下使用 PowerShell 的自动位置索引,基于参数是 integer.