PowerShell XML排序节点和替换子节点

4
我正在尝试使用powershell和xml做一些相当简单的事情,但遇到了一些麻烦。基本上我正在尝试按名称对机器元素进行排序。然后将它们放回XML中,以便我可以保存回文件。 如果输出$new对象,则排序似乎有效,但在replacechild期间,它会抱怨“无法将参数“0”(值为:“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排序脚本,可以用于比较两个类似的XML文件。通过对XML元素和属性进行排序来查看XML差异 - nudl
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
5
这里有几个问题。其中一个是你的 sort 返回的是一个 XML 元素列表,而不是单个 XML 元素。另一个问题是它返回的是原始的 XML 元素,而不是副本,因此你对它们进行的任何 XML DOM 操作也会影响结果。 以下是一个简单的方法来实现你想要的效果。以相反的顺序排序,然后依次将每个节点插入到其他节点前面。每次从排序结果中插入一个节点时,它都会自动从原始节点集中删除它:
[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

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

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

看起来非常不错 - 我需要在添加它们回去之前将它们全部删除,否则我会有两倍的数量,对吗? - Shaunt
不,这就是我所说的它们是原始节点而不是副本的意思:当您将节点重新插入树中时,它会从原始位置移动。(我发布的代码是一个完整的示例,您应该能够复制/粘贴它并查看效果)。 - Duncan
@DavidMartin,你也可以进行升序排序并使用AppendChild。我有点担心以这种方式操作子节点,因为在迭代附加的列表时感觉存在无限循环的风险,但这在这里不是问题。 - Duncan

2

除了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文件的不同层级,并且根据此级别进行排序。

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

是的,这正是我最终所做的。感谢您确认了原因! - Shaunt
@RobertWesterlund 我实现了你的解决方案,在PowerShell ISE中运行我的脚本,我可以看到节点的顺序发生了变化,但是当我保存更改到文件中时,它并没有改变。 - XtianGIS

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