【问题标题】:powershell xml sort nodes and replacechildpowershell xml排序节点和replacechild
【发布时间】:2023-03-03 06:52:21
【问题描述】:

我正在尝试使用 powershell 和 xml 做一些非常简单的事情,但没有遇到一点麻烦。 基本上我正在尝试采用以下xml ...并按名称对机器元素进行排序。然后将它们放回 XML 中,以便我可以保存回文件。

如果输出 $new 对象,排序似乎可以工作,但是,在 replacechild 期间它抱怨“无法转换参数“0”,值为:“ReplaceChild”的“System.Object []”键入“System.Xml。 XmlNode”:“无法将“System.Object[]”转换为类型“System.Xml.XmlNode”。

如果我在 $orig 和 $new 上都执行 Get-Member,它们都说它们是 XMLElement 类型,我相信它继承自 XMLNode。

我想念什么?使我抓狂。感谢您的帮助!

<company>
    <stuff>
    </stuff>
    <machines>
        <machine>
            <name>ca</name>
            <b>123</b>
            <c>123</c>
        </machine>
        <machine>
            <name>ad</name>
            <b>234</b>
            <c>234</c>
        </machine>
        <machine>
            <name>be</name>
            <b>345</b>
            <c>345</c>
        </machine>
    </machines>
    <otherstuff>
    </otherstuff>
</company>

[xml]$xml = (get-content Company.xml)
[XmlNode]$orig = $xml.Company.Machines
[XmlNode]$new = ($orig.Machine | sort Name )
$xml.Company.ReplaceChild($new, $orig)

【问题讨论】:

标签: xml powershell powershell-3.0


【解决方案1】:

这里有各种各样的问题。一个是您的 sort 返回一个 xml 元素列表,而不是单个 xml 元素。另一个问题是它返回原始 xml 元素而不是副本,因此您使用它们对 xml DOM 进行的任何操作也会影响结果。

这是获得您想要的东西的简单方法。以 reverse 顺序排序,然后将每个节点依次插入到其他节点的前面。每次从排序结果中插入一个节点时,它都会自动将其从原始节点集中删除:

[xml]$xml = @"
<company>
    <stuff>
    </stuff>
    <machines>
        <machine>
            <name>ca</name>
            <b>123</b>
            <c>123</c>
        </machine>
        <machine>
            <name>ad</name>
            <b>234</b>
            <c>234</c>
        </machine>
        <machine>
            <name>be</name>
            <b>345</b>
            <c>345</c>
        </machine>
    </machines>
    <otherstuff>
    </otherstuff>
</company>
"@
[System.Xml.XmlNode]$orig = $xml.Company.Machines
$orig.Machine | sort Name  -Descending |
  foreach { [void]$xml.company.machines.PrependChild($_) }
$xml.company.machines.machine

编辑:管道也可以用升序排序(正如大卫马丁指出的那样),您可以通过使用变量中的节点来减少输入:

$orig.Machine | sort Name | % { [void]$orig.AppendChild($_) }

【讨论】:

  • 看起来非常好 - 我必须先删除所有,然后再将它们重新添加,否则我的正确率会是原来的两倍?
  • 不,这是我关于它们是原始节点而不是副本的观点:当您将节点插入树中时,它会将其从原始位置移动。 (我贴的代码是完整的示例,你应该可以复制粘贴看看效果)。
  • @DavidMartin,不,您也可以升序排序并改为 AppendChild。我对以这种方式操作子节点有点谨慎,因为迭代您要附加的列表感觉存在无限循环的风险,但这不是问题。
【解决方案2】:

这不起作用的原因是 $xml.Company.Machines 是单个 XmlElement。要获取机器元素的集合,您需要使用 $xml.Company.Machines.Machine,因此这是您要排序的那个。

但是,ReplaceChild 方法不采用集合,所以我猜您必须删除所有子项,然后按照您想要的顺序将它们附加回来。像下面这样的东西应该可以正常工作:

[xml]$xml = Get-Content .\Company.xml
$machines = $xml.company.machines
$orderedMachineCollection = $machines.machine | Sort Name
$machines.RemoveAll()
$orderedMachineCollection | foreach { $machines.AppendChild($_) } | Out-Null

【讨论】:

  • 是的,这正是我最终要做的。感谢您确认原因!
  • @RobertWesterlund 我实现了您的解决方案,使用 PowerShell ISE 运行我的脚本,我可以看到节点的顺序如何变化,但随后我将更改保存到文件中,它根本没有变化。
【解决方案3】:

除了 Duncan 和 robert.westerlund 已经非常出色的答案之外,我想提供一个示例,说明如何对 xml 元素进行排序,以防节点不总是在同一路径中:

[xml]$xml = @"
<config>
    <namespace name='Some namespace'>
        <settings>
            <setting name='c setting' />
            <setting name='a setting' />
            <setting name='b setting' />
        </settings>
        <namespace name='Child namespace'>
            <settings>
                <setting name='z setting' />
                <setting name='y setting' />
                <setting name='x setting' />
            </settings>
        </namespace>
    </namespace>
</config>
"@

#[xml]$xml = Get-Content "PATH_TO_SOME_FILE.xml"

$xml.SelectNodes("//namespace/settings") | % { 
    $parentNode = $_; $_.Setting | Sort-Object -Property name | % { 
        $parentNode.AppendChild($_) | Out-Null
    } 
}

$xml.Save("PATH_TO_SOME_FILE.xml")

在本例中,settings 节点出现在 xml 文件中的不同层级,并针对该层级进行排序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-14
    • 1970-01-01
    • 2012-01-15
    • 1970-01-01
    • 1970-01-01
    • 2018-03-25
    • 2021-12-18
    • 1970-01-01
    相关资源
    最近更新 更多