简体   繁体   English

powershell xml排序节点和替换子节点

[英]powershell xml sort nodes and replacechild

I'm trying to do something pretty simple with powershell and xml but haven't a bit of trouble. 我试着用powershell和xml做一些非常简单的事情,但没有一点麻烦。 Basically I'm trying to take the following xml... and sort the machine elements by name. 基本上我正在尝试使用以下xml ...并按名称对机器元素进行排序。 Then put them back into the XML so that I can save back to a file. 然后将它们放回XML中,以便我可以保存回文件。

Sorting seems to be working if output the $new object however, during the replacechild it's complaining "Cannot convert argument "0", with value: "System.Object[]" for "ReplaceChild" to type "System.Xml.XmlNode" : "Cannot convert the "System.Object[]" to type "System.Xml.XmlNode". 如果输出$ new对象,排序似乎有效,但是在替换它期间它抱怨“无法转换参数”0“,值为:”System.Object []“for”ReplaceChild“键入”System.Xml.XmlNode“: “无法将”System.Object []“转换为”System.Xml.XmlNode“类型。

If I do a Get-Member on both $orig and $new they both say they are of type XMLElement which I believe inherits from XMLNode. 如果我在$ orig和$ new上都有一个Get-Member,他们都说它们是XMLElement类型,我认为它继承自XMLNode。

What am I missing guys? 我想念的是什么人? Driving me nuts. 使我抓狂。 Thanks for your help! 谢谢你的帮助!

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

There are various problems here. 这里有各种各样的问题。 One is that your sort returns a list of xml elements rather than a single xml element. 一个是你的sort返回一个xml元素列表而不是一个xml元素。 Another problem is that it returns the original xml elements rather than copies, so any manipulation of the xml DOM that you do using them will also affect the result. 另一个问题是它返回原始的xml元素而不是副本,因此使用它们对xml DOM的任何操作也会影响结果。

Here's a simple way to get what you want. 这是获得您想要的简单方法。 Sort in reverse order, and then insert each node in turn in front of the others. 相反顺序排序,然后依次将每个节点插入其他节点前面。 Each time you insert a node from the result of the sort it will automatically remove it from the original set of node: 每次从排序结果中插入节点时,它都会自动将其从原始节点集中删除:

[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

Edit: The pipeline can also be written with an ascending sort (as David Martin pointed out), and you can reduce typing by using the node in the variable: 编辑:管道也可以按升序编写(如David Martin指出的那样),并且您可以通过使用变量中的节点来减少键入:

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

In addition to the already excellent answers by Duncan and robert.westerlund I'd like to provide an example of how to do the sorting of xml elements in case the nodes are not always in the same path: 除了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")

In this example, the settings node appears on different levels in the xml file and sorting is done with respect to this level. 在此示例中, settings节点显示在xml文件中的不同级别上,并且已针对此级别进行排序。

The reason this doesn't work is that $xml.Company.Machines is a single XmlElement. 这不起作用的原因是$ xml.Company.Machines是一个XmlElement。 To get the collection of machine elements, you need to use $xml.Company.Machines.Machine, so this is the one you want to sort. 要获取机器元素的集合,您需要使用$ xml.Company.Machines.Machine,因此这是您要排序的那个。

However, the ReplaceChild method doesn't take a collection, so I'm guessing you'll have to remove all children and then append them back in the order you want them. 但是,ReplaceChild方法不会占用集合,所以我猜你必须删除所有子节点,然后按照你想要的顺序将它们追加。 Something like the following should work fine: 像下面这样的东西应该工作正常:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM