0

I'm looking for some help with sorting XML files in Python 3.12. I've found some examples of sorting, but so far none have been very applicable to what I'm looking to do (whether focusing on sorting elements, sorting only specific attributes, etc.). The closest example I could find was this, but it didn't really seem to work for what I'm trying to do, I think because it seems to be looking based on specific attributes rather than any attribute.

I want to sort every attribute within each element by the attribute name (not value), without changing the order of any elements. (I think I used the right names to describe the parts of the XML file, but if there's confusion, I can clarify).

Here's an example:

Starting file:

<PowerStyle IconFile="textures/ui/wolverine_all.png" IconColumns="2" IconRows="2" CanSteal="true" exclusive="wolverine">
    <FightMove Name="fastball" lockangles="true" sequenced="true" animenum="ea_lunge" playspeed="2" handler="ch_fastball">
        <trigger time="-1" tag="1" name="punch" PowerAttack="true" arc="180" Knockback="K3" Damage="L3">
            <damageMod name="dmgmod_auto_knockback" />
        </trigger>
        <chain action="special2" result="fastballhit" />
        <chain action="touch" result="fastballhit" />
        <chain action="idle" result="fastballland" />
    </FightMove>
    <FightMove Name="fastballhit" lockangles="true" animenum="ea_jump_smash_land" lockchaining="true" startchaintime="0.8">
        <chain action="idle" result="idle" />
    </FightMove>
...

Desired Output:

<PowerStyle CanSteal="true" exclusive="wolverine" IconColumns="2" IconFile="textures/ui/wolverine_all.png" IconRows="2">
    <FightMove animenum="ea_lunge" handler="ch_fastball" lockangles="true" Name="fastball" playspeed="2" sequenced="true" >
        <trigger arc="180" Damage="L3" Knockback="K3" name="punch" PowerAttack="true" tag="1" time="-1">
            <damageMod name="dmgmod_auto_knockback" />
        </trigger>
        <chain action="special2" result="fastballhit" />
        <chain action="touch" result="fastballhit" />
        <chain action="idle" result="fastballland" />
    </FightMove>
    <FightMove animenum="ea_jump_smash_land" lockangles="true" lockchaining="true" Name="fastballhit" startchaintime="0.8">
        <chain action="idle" result="idle" />
    </FightMove>
...

I know that the order of the attributes doesn't really matter, but this is mainly for readability and for aligning the style with file content from a similar game. I'd like for capitalization to be maintained, but I'd like for the attributes to be sorted without considering the capitalization (purely alphabetically). So for example, in the first FightMove element, the order of attributes goes from Name, lockangles, sequenced, animenum, playspeed, handler to animenum, handler, lockangles, Name, playspeed, sequenced. However, the child elements of the first FightMove element (trigger and 3 chain elements), do not change their order.

If any additional clarity is needed to help define the problem, I can definitely provide it.

2 Answers 2

2

You can write a helper method which will sort a xml for you .

import xml.etree.ElementTree as ET

def sort_attributes(element):
    sorted_attrib = dict(sorted(element.attrib.items(), key=lambda item: item[0].lower()))
    element.attrib.clear()
    element.attrib.update(sorted_attrib)

    for child in element:
        sort_attributes(child)

def sort_xml(xml_string):
    root = ET.fromstring(xml_string)

    sort_attributes(root)

    return ET.tostring(root, encoding="unicode")

The method sort_xml should sort attributes alphabetically case insensitive.

If you need a copy of the XML, make sure to use copy.deepcopy on the original data, as the dict type is a reference type.

Sign up to request clarification or add additional context in comments.

Comments

1

In addition to @ViAchKoN you can use iter() the tree:

import xml.etree.ElementTree as ET

xml_ = """<PowerStyle IconFile="textures/ui/wolverine_all.png" IconColumns="2" IconRows="2" CanSteal="true" exclusive="wolverine">
    <FightMove Name="fastball" lockangles="true" sequenced="true" animenum="ea_lunge" playspeed="2" handler="ch_fastball">
        <trigger time="-1" tag="1" name="punch" PowerAttack="true" arc="180" Knockback="K3" Damage="L3">
            <damageMod name="dmgmod_auto_knockback" />
        </trigger>
        <chain action="special2" result="fastballhit" />
        <chain action="touch" result="fastballhit" />
        <chain action="idle" result="fastballland" />
    </FightMove>
    <FightMove Name="fastballhit" lockangles="true" animenum="ea_jump_smash_land" lockchaining="true" startchaintime="0.8">
        <chain action="idle" result="idle" />
    </FightMove></PowerStyle>"""

root = ET.fromstring(xml_)

# Sort the dictionary as the other answer do.
for elem in root.iter():
    e = dict(sorted(elem.attrib.items(), key=lambda item: item[0].lower()))
    elem.attrib = e
    
# Pretty Print and write to file
tree = ET.ElementTree(root)
ET.indent(tree, space="  ")
tree.write("test.xml", xml_declaration=True, encoding="utf-8")

ET.dump(tree)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.