【问题标题】:Set x XML node to value in Powershell在 Powershell 中将 x XML 节点设置为值
【发布时间】:2020-06-02 11:30:57
【问题描述】:

我有这个 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) 节点时,脚本就像一个魅力。

请指教。

【问题讨论】:

  • @luuk,我在您发给我的链接中没有看到我的问题的答案。我能够更改节点属性。但是,当有多个同名节点时,我是 stuk。

标签: powershell repeat xmlnode


【解决方案1】:

您始终可以使用 .NET 语法,它就像一个魅力。

$xml1.SelectSingleNode('Lvl1/Lvl2[2]').InnerText='./'

【讨论】:

    【解决方案2】:

    您可以将Lvl1的子节点作为aray,然后使用索引来调整想要的节点:

    [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>
    

    【讨论】:

      【解决方案3】:
      $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>
      '@
      

      【讨论】:

        【解决方案4】:
        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]' 指的是这个东西第二次存在。

        【讨论】:

          【解决方案5】:

          Paweł Dyl's helpful answer 提供了一个简洁的解决方案,绕过 PowerShell's adaptation of the XML DOM,它使用熟悉的点符号,有利于直接使用 .NET 方法。

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

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

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

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

          要为 first Lvl2 孩子赋值,您可以使用以下方法:

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

          • 然后可以分配该元素的.InnerText 属性。

          如果您需要分配给具有给定索引的子元素,则需要使用不同的方法,使用数字(基于0)的索引XmlElement.ChildNodes 集合(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' 
          

          this GitHub issue 报告了这种令人惊讶的行为。

          但是,获取这种方式(通常)可以正常工作

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

          警告

          如果一个命名元素恰好是only子元素并且它本身有元素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 元素名称不能t 以数字开头。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-02-04
            • 1970-01-01
            相关资源
            最近更新 更多