Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write 'xsi:' in front of attribute with lxml for python 3

I'm adding elements to an xml file.

The document's root is as follows

<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

And elements to add look like

<Element xsi:type="some type">
  <Sub1>Some text</Sub1>
  <Sub2>More text</Sub2>
  ...
</Element>

I'm trying to find a way for lxml to write 'xsi:' in front of my Element's attibute. This xml file is used by a program to which's source code I do not have access to. I read in a few other questions how to do it by declaring the nsmap of the xml's root, and then again in the child's attribute, which I tried but it didn't work. So far I have (that's what didn't work, the ouput file did not contain the xsi prefix):

element = SubElement(_parent=parent, 
                     _tag='some tag', 
                     attrib={'{%s}type' % XSI: 'some type'}
                     nsmap={'xsi': XSI})  # Where XSI = namespace address

The namespace is declared properly in the xml file I parse, so I don't know why this isn't working. The output I get is the element as shown above without the 'xsi:' prefix and all on one line:

<Element type="some type"><Sub1>Some text</Sub1><Sub2>More text</Sub2>...</Element>

If anyone can also point out why in this line

self.tree.write(self.filename, pretty_print=True, encoding='utf-8')

the 'pretty_print' option doesn't work (all printed out in one line), it would be greatly appreciated.

Here is a code example of my script:

from math import floor
from lxml import etree
from lxml.etree import SubElement


def Element(root, sub1: str):
    if not isinstance(sub1, str):
        raise TypeError
    else:
        element = SubElement(root, 'Element')
        element_sub1 = SubElement(element, 'Sub1')
        element_sub1.text = sub1
        # ...
        # Omitted additional SubElements
        # ...
        return element


def Sub(root, sub5_sub: str):
    XSI = "http://www.w3.org/2001/XMLSchema-instance"
    if not isinstance(sub5_sub, str):
        raise TypeError
    else:
        sub = SubElement(root, 'Sub5_Sub', {'{%s}type' % XSI: 'SomeType'}, nsmap={'xsi': XSI})
        # ...
        # Omitted additional SubElements
        # ...
        return sub


class Generator:
    def __init__(self) -> None:
        self.filename = None
        self.csv_filename = None
        self.csv_content = []
        self.tree = None
        self.root = None
        self.panel = None
        self.panels = None

    def mainloop(self) -> None:
        """App's mainloop"""
        while True:
            # Getting files from user
            xml_filename = input('Enter path to xml file : ')

            # Parsing files
            csv_content = [{'field1': 'ElementSub1', 'field2': 'something'},
                           {'field1': 'ElementSub1', 'field2': 'something'},
                           {'field1': 'ElementSub2', 'field2': 'something'}]  # Replaces csv file that I use
            tree = etree.parse(xml_filename)
            root = tree.getroot()

            elements = root.find('Elements')

            for element in elements:
                if element.find('Sub1').text in ['ElementSub1', 'ElementSub2']:
                    for line in csv_content:
                        if element.find('Sub5') is not None:
                            Sub(root=element.find('Sub5'),
                                sub5_sub=line['field2'])

            tree.write(xml_filename, pretty_print=True, encoding='utf-8')

            if input('Continue? (Y) Quit (n)').upper().startswith('Y'):
                elements.clear()
                continue
            else:
                break

    @staticmethod
    def get_x(x: int) -> str:
        if not isinstance(x, int):
            x = int(x)
        return str(int(floor(9999 / 9 * x)))

    @staticmethod
    def get_y(y: int) -> str:
        if not isinstance(y, int):
            y = int(y)
        return str(int(floor(999 / 9 * y)))

    def quit(self) -> None:
        quit()


if __name__ == "__main__":
    app = Generator()
    app.mainloop()
    app.quit()

Here is what it outputs:

<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Elements>
    <Element>
      <Sub1>ElementSub1</Sub1>
      <Sub5>
        <Sub5_Sub xsi:type="SomeType"/>
      <Sub5_Sub xsi:type="SomeType"/><Sub5_Sub xsi:type="SomeType"/><Sub5_Sub xsi:type="SomeType"/></Sub5>
    </Element>
    <Element>
      <Sub1>ElementSub1</Sub1>
      <Sub5>
        <Sub5_Sub xsi:type="SomeType"/>
        <Sub5_Sub xsi:type="SomeType"/>
      <Sub5_Sub xsi:type="SomeType"/><Sub5_Sub xsi:type="SomeType"/><Sub5_Sub xsi:type="SomeType"/></Sub5>
    </Element>
    <Element>
      <Sub1>ElementSub1</Sub1>
    </Element>
  </Elements>
</Root>

For some reason, this piece of code does what I want but my real code doesn't. I've come to realize that it does put a prefix on some sub elements with the type attribute, but not all and on those it puts the prefix, it isn't always just 'xsi:'. I found a quick and dirty way to fix this problem which is less than ideal (find and replace through the file for xsi-type -> accepted by lxml's api to xsi:type). What still isn't working though is that it's all printed out in one line despite the pretty_print parameter being true.

like image 410
Narkael Avatar asked Feb 04 '26 05:02

Narkael


1 Answers

I just recently encountered this scenario and was able to successfully create an attribute with the xsi:

qname = etree.QName("http://www.w3.org/2001/XMLSchema-instance", "type")
element = etree.Element('Element', {qname: "some type")

root.append(element)

this outputs something like

<Element xsi:type="some type">
like image 191
catzilla Avatar answered Feb 05 '26 21:02

catzilla



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!