pgmank
pgmank

Reputation: 5819

Setting values without pytype - lxml objectify

Setting values to an element using the objectify API of the lxml library, assigns the auto detected pytype to that element and the required namespaces, by default.

For example, setting the root element:

root = objectify.Element('root')
print(etree.tostring(root, pretty_print=True).decode('utf-8'))

Outputs:

<root xmlns:py="http://codespeak.net/lxml/objectify/pytype"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" py:pytype="TREE"/>

or setting a value to a child element:

child = objectify.SubElement(root, 'child')
root.child = 'value'
print(etree.tostring(root, pretty_print=True).decode('utf-8'))

Outputs:

<root xmlns:py="http://codespeak.net/lxml/objectify/pytype"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" py:pytype="TREE">
  <child py:pytype="str">value</child>
</root>

Even using the setattr of ObjectPath:

path = objectify.ObjectPath('root.vader.son')
path.setattr(root, 'Luke')
print(etree.tostring(root, pretty_print=True).decode('utf-8'))

Outputs:

<root xmlns:py="http://codespeak.net/lxml/objectify/pytype"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" py:pytype="TREE">
  <child py:pytype="str">value</child>
  <vader>
    <son py:pytype="str">Luke</son>
  </vader>
</root>

There are solutions that remove the pytype and its namespaces after the element creation, using the deannotate() function (ex. When using lxml, can the XML be rendered without namespace attributes?, Remove "xmlns:py..." with lxml.objectify). There are no solutions whatsoever that create the element without the pytype and its namespaces from the beginning. Any ideas on how to do that?

Upvotes: 4

Views: 1036

Answers (2)

Lenka
Lenka

Reputation: 21

Just another workaround may help you: while setting an element, you can use the _setText() method: vader.son._setText('Luke')

Reason: 'class lxml.objectify.ObjectifiedDataElement', which bases on 'lxml.objectify.ObjectifiedElement' has a private method _setText(). This method is dedicated for use in subclasses only, i. e. lxml.objectify.StringElement-class and doesn´t affect the pytype https://lxml.de/apidoc/lxml.objectify.html

In other words: you can use it as long as you don't change the value type.

Upvotes: 1

pgmank
pgmank

Reputation: 5819

In lxml.objectify, there are two kinds of elements: the tree elements, created by the Element factory and the data elements, created by DataElement factory or by the specific data classes, for example StringElement, IntElement (for more info, see here). A solution might be to empty the namespaces and the _pytype argument of the particular element, by assigning it to the empty string and never use direct assignment from literals. To create an element from a literal, you will have to use the DataElement factory. Note that, if you have any particular namespaces, you will have to assign your namespace map, instead of the empty string to the nsmap parameter. There is a problem though. If you want to create a tree element, setting the nsmap and _pytype to the empty string, the namespaces and pytype won't be removed. I don't know why. So this solution works only for data elements.

This is how the code would be for the tree you try to build:

root = objectify.Element('root', nsmap='', _pytype='')
# sub elements do not need nsmap or _pytype to be emptied
child = objectify.SubElement(root, 'child')
root.child = objectify.DataElement('value', nsmap='', _pytype='')
path = objectify.ObjectPath('root.vader.son')
path.setattr(root, objectify.DataElement('Luke', nsmap='', _pytype=''))
print(etree.tostring(root, pretty_print=True).decode('utf-8'))

Which outputs:

<root xmlns:py="http://codespeak.net/lxml/objectify/pytype" py:pytype="">
  <child>value</child>
  <vader>
    <son>Luke</son>
  </vader>
</root>

Not what we want!

The solution lies in a workaround, using the ElementMaker factory.

# Create your ElementMaker factory, without annotations.
E = objectify.ElementMaker(annotate=False)
# If you have any namespaces you want to use, assign them to the nsmap
# parameter and assign the default namespace to the namespace parameter.
# E = objectify.ElementMaker(annotate=False, namespace=namespace, nsmap=nsmap)
root = E.root()
print(etree.tostring(root, pretty_print=True))

This outputs:

<root/>

The namespace and pytype problem introduced to the tree elements has been solved. Now we can either assign sub elements or data elements:

objectify.SubElement(root, 'child')
root.child = objectify.DataElement('value', nsmap='', _pytype='')
print(etree.tostring(root, pretty_print=True).decode('utf-8'))

which outputs:

<root>
  <child>value</child>
</root>

An example using the setattr() method is:

root = E.root()
path = objectify.ObjectPath('root.vader.son')
path.setattr(root, objectify.DataElement('Luke', nsmap='', _pytype=''))
# mysteriously, the below line works the same as the above line:
# path.setattr(root, E.whatevername('Luke'))
print(etree.tostring(root, pretty_print=True).decode('utf-8'))

the output of which is:

<root>
  <vader>
    <son>Luke</son>
  </vader>
</root>

Upvotes: 3

Related Questions