speedyrazor
speedyrazor

Reputation: 3205

Python - lxml to re-order xml tags

I have some sections in my xml which I need to re-order, I know xml doesn't need re-ordering, but this is what I need to do, but can't figure out the 'correct' way to do it. I am using lxml and have been using the .insert command to re-order. I need to re-order each of the tags inside each <asset type="preview"> so that it looks like this:

    <asset type="preview">
        <territories>
            <territory>SE</territory>
        </territories>
        <data_file role="source">
            <locale name="es"/>
            <file_name>some_name_nor-preview-sv.mov</file_name>
            <size>1715119116</size>
            <checksum type="md5">55cd94d051700be34014b2892e925fa1</checksum>
            <attribute name="crop.top">25</attribute>
            <attribute name="crop.bottom">25</attribute>
            <attribute name="crop.left">4</attribute>
            <attribute name="crop.right">4</attribute>
            <attribute name="image.burned_subtitles.locale">sv</attribute>
            <attribute name="image.textless_master">false</attribute>
        </data_file>
    </asset>

I sometime have multiple <asset type="preview">, sometimes not. Also sometimes each <asset type="preview"> does not contain all of the the tags listed here. Here is the section of xml I am trying to re-order as above.

    <asset type="preview">
        <data_file role="source">
            <size>1657800204</size>
            <file_name>some_name_nor-preview.mov</file_name>
            <checksum type="md5">c61dfa7139ab04560cac41cf5ba8a1f2</checksum>
            <locale name="es"/>
            <attribute name="crop.top">25</attribute>
            <attribute name="crop.right">4</attribute>
            <attribute name="crop.bottom">25</attribute>
            <attribute name="crop.left">4</attribute>
        </data_file>
        <territories>
            <territory>WW</territory>
        </territories>
        <data_file role="notes">
            <size>9642</size>
            <file_name>some_name_nor-preview-notes.pdf</file_name>
            <checksum type="md5">4d0dc3534cd1d0f9885afbfda9be8b71</checksum>
        </data_file>
    </asset>
    <asset type="preview">
        <data_file role="source">
            <size>1715119116</size>
            <file_name>some_name_nor-preview-sv.mov</file_name>
            <checksum type="md5">55cd94d051700be34014b2892e925fa1</checksum>
            <locale name="es"/>
            <attribute name="image.burned_subtitles.locale">sv</attribute>
            <attribute name="crop.top">25</attribute>
            <attribute name="crop.right">4</attribute>
            <attribute name="image.textless_master">false</attribute>
            <attribute name="crop.left">4</attribute>
            <attribute name="crop.bottom">25</attribute>
        </data_file>
        <territories>
            <territory>SE</territory>
        </territories>
    </asset>
    <asset type="preview">
        <data_file role="source">
            <size>1709158524</size>
            <file_name>some_name_nor-preview-fi.mov</file_name>
            <checksum type="md5">58c5fcfa718393f76cb9b2d8f7c10362</checksum>
            <locale name="es"/>
            <attribute name="crop.bottom">25</attribute>
            <attribute name="crop.top">25</attribute>
            <attribute name="crop.left">4</attribute>
            <attribute name="image.textless_master">false</attribute>
            <attribute name="crop.right">4</attribute>
            <attribute name="image.burned_subtitles.locale">fi</attribute>
        </data_file>
        <territories>
            <territory>FI</territory>
        </territories>
    </asset>
    <asset type="preview">
        <territories>
            <territory>NO</territory>
        </territories>
        <data_file role="source">
            <size>1718632572</size>
            <file_name>some_name_nor-preview-no.mov</file_name>
            <checksum type="md5">41734d9d8dd4165416a4369f4ce9c8e1</checksum>
            <locale name="es"/>
            <attribute name="crop.left">4</attribute>
            <attribute name="crop.top">25</attribute>
            <attribute name="crop.bottom">25</attribute>
            <attribute name="image.textless_master">false</attribute>
            <attribute name="image.burned_subtitles.locale">no</attribute>
            <attribute name="crop.right">4</attribute>
        </data_file>
    </asset>
    <asset type="preview">
        <territories>
            <territory>DK</territory>
        </territories>
        <data_file role="source">
            <size>1721312028</size>
            <file_name>some_name_nor-preview-da.mov</file_name>
            <checksum type="md5">919abd17baf680161a220dbae8409918</checksum>
            <locale name="es"/>
            <attribute name="image.textless_master">false</attribute>
            <attribute name="crop.bottom">25</attribute>
            <attribute name="image.burned_subtitles.locale">da</attribute>
            <attribute name="crop.right">4</attribute>
            <attribute name="crop.left">4</attribute>
            <attribute name="crop.top">25</attribute>
        </data_file>
    </asset>

This is my current 'not working' code, it's not re-ordering the attribute[@name= tags, not sure this is the correct approach:

        a = 0
        b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag='locale'):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/locale")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag='file_name'):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/file_name")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1           
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag='size'):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/size")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag='checksum'):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/checksum")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag="attribute[@name='crop.top']"):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/attribute[@name='crop.top']")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag="attribute[@name='crop.bottom']"):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/attribute[@name='crop.bottom']")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag="attribute[@name='crop.left']"):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/attribute[@name='crop.left']")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag="attribute[@name='crop.right']"):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/attribute[@name='crop.right']")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag="attribute[@name='image.burned_forced_narrative.locale']"):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/attribute[@name='image.burned_forced_narrative.locale']")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag="attribute[@name='image.burned_subtitles.locale']"):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/attribute[@name='image.burned_subtitles.locale']")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0
        for node_search in tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']"):
            for element in node_search.iter(tag="attribute[@name='image.textless_master']"):
                node_products = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']")[b]
                node_type = tree.xpath("//video/assets/asset[@type='preview']/data_file[@role='source']/attribute[@name='image.textless_master']")[b]
                node_products.insert(a, node_type)
                b = b+1
            a = a+1
            b = 0

Upvotes: 1

Views: 473

Answers (1)

Robᵩ
Robᵩ

Reputation: 168596

I'm not completely clear on your requirements. The following code sorts each asset_preview in this order:

unknown tags
<territories>
unknown <data_file> roles
<data_file role=source>
<data_file role=notes>

And sorts each data_file like so:

unknown tags
<locale>
<file_name>
<size>
<checksum>
unknown attributes
<attribute name="crop.top">
other <attributes>, in a specific order.

The key to understanding this technique is to realize that a node is a list, and can be reordered the way you reorder any list. In my case, I used sorted() with a custom key.

Here you go:

from lxml import etree

def preview_key(et):
    major_ordering = ['territories', 'data_file']
    minor_ordering = ['source', 'notes']
    try:
        major = major_ordering.index(et.tag)
    except ValueError:
        major = -1
    try:
        minor = minor_ordering.index(et.get('role', None))
    except ValueError:
        minor = -1
    return major, minor

def data_file_key(et):
    major_ordering = ['locale', 'file_name', 'size', 'checksum', 'attribute']
    minor_ordering = [
            "crop.top",
            "crop.bottom",
            "crop.left",
            "crop.right",
            "image.burned_subtitles.locale",
            "image.textless_master"]
    try:
        major = major_ordering.index(et.tag)
    except ValueError:
        major = -1
    try:
        minor = minor_ordering.index(et.get('name', None))
    except ValueError:
        minor = -1
    return major, minor



with open('input.xml') as input_file:
    parser = etree.XMLParser(remove_blank_text=True)
    tree = etree.parse(input_file, parser)
root = tree.getroot()

for preview in tree.xpath("//asset[@type='preview']"):
    preview[:] = sorted(preview, key=preview_key)

for data_file in tree.xpath("//data_file"):
    data_file[:] = sorted(data_file, key=data_file_key)

with open('output.xml', 'w') as output_file:
    output_file.write(etree.tostring(tree, pretty_print = True))

Upvotes: 1

Related Questions