RoryMcGuinness RoryMcGuinness - 1 month ago 22
Python Question

Sorting XML tags by child elements Python

I have a number of 'root' tags with children 'name'. I want to sort the 'root' blocks, ordered alphabetically by the 'name' element. Have tried lxml / etree / minidom but can't get it working...
I can't get it to parse the value inside the tags, and then sort the parent root tags.

<?xml version='1.0' encoding='UTF-8'?>
<roots>
<root>
<path>//1.1.1.100/Alex</path>
<name>Alex Space</name>
</root>
<root>
<path>//1.1.1.101/Steve</path>
<name>Steve Space</name>
</root>
<root>
<path>//1.1.1.150/Bethany</path>
<name>Bethanys</name>
</root>
</roots>


Here is what I have tried:

import xml.etree.ElementTree as ET

def sortchildrenby(parent, child):
parent[:] = sorted(parent, key=lambda child: child)


tree = ET.parse('data.xml')
root = tree.getroot()

sortchildrenby(root, 'name')
for child in root:
sortchildrenby(child, 'name')


tree.write('output.xml')

Answer

If you want to put the name nodes first:

x = """
  <roots>
    <root>
      <path>//1.1.1.100/Alex</path>
      <name>Alex Space</name>
    </root>
    <root>
      <path>//1.1.1.101/Steve</path>
          <name>Bethanys</name>
    </root>
    <root>
      <path>//1.1.1.150/Bethany</path>
        <name>Steve Space</name>
    </root>
</roots>"""

import lxml.etree as et
tree = et.fromstring(x)

for r in tree.iter("root"):
    r[:] = sorted(r, key=lambda ch: -(ch.tag == "name"))

print(et.tostring(tree).decode("utf-8"))

Which would give you:

<roots>
    <root>
      <name>Alex Space</name>
    <path>//1.1.1.100/Alex</path>
      </root>
    <root>
      <name>Bethanys</name>
    <path>//1.1.1.101/Steve</path>
          </root>
    <root>
      <name>Steve Space</name>
    <path>//1.1.1.150/Bethany</path>
        </root>
</roots>

But there is no need to sort if you just want to add them first, you can just remove and reinsert the name into index 0:

import lxml.etree as et
tree = et.fromstring(x)

for r in tree.iter("root"):
    ch = r.find("name")
    r.remove(ch)
    r.insert(0, ch)

print(et.tostring(tree).decode("utf-8"))

If the nodes are actually not in sorted order and you want to rearrange the roots node alphabetically:

x = """
  <roots>
    <root>
      <path>//1.1.1.100/Alex</path>
      <name>Alex Space</name>
    </root>
    <root>
      <path>//1.1.1.101/Steve</path>
         <name>Steve Space</name>
    </root>
    <root>
       <path>//1.1.1.150/Bethany</path>
       <name>Bethanys</name>
    </root>
</roots>"""
import lxml.etree as et
tree = et.fromstring(x)

tree[:] = sorted(tree, key=lambda ch: ch.xpath("name/text()"))

print(et.tostring(tree).decode("utf-8"))

Which would give you:

 <roots>
    <root>
      <path>//1.1.1.100/Alex</path>
      <name>Alex Space</name>
    </root>
    <root>
       <path>//1.1.1.150/Bethany</path>
       <name>Bethanys</name>
    </root>
    <root>
      <path>//1.1.1.101/Steve</path>
         <name>Steve Space</name>
    </root>
</roots>

You can also combine with either of the first two approach two also rearrange the root nodes putting name first.