Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. Basically I'm trying to take the following xml... and sort the machine elements by name. Then put them back into the XML so that I can save back to a file.

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".

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.

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)
like image 408
Shaunt Avatar asked Jan 12 '23 11:01

Shaunt


2 Answers

There are various problems here. One is that your sort returns a list of xml elements rather than a single xml element. 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.

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:

$orig.Machine | sort Name | % { [void]$orig.AppendChild($_) }
like image 67
Duncan Avatar answered Jan 21 '23 01:01

Duncan


The reason this doesn't work is that $xml.Company.Machines is a single 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.

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. 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
like image 44
Robert Westerlund Avatar answered Jan 21 '23 02:01

Robert Westerlund