arve0
arve0

Reputation: 3647

Inherit or mock pythons etree.ElementTree.Element possible?

I'm trying to create a small addon for ElementTree and Element in python's xml.etree.ElementTree which would allow for notation like:

root = ET.fromstring('<root><a><b attr1="2"/></a></root>')
element1 = root.a
element2 = root.a.b
attr = root.a.b.attr

I've tried this:

import xml.etree.ElementTree as _ET

def __getattr__(self, key):
    return self.find('./' + key)

class ElementTree(_ET.ElementTree):
    __getattr__ = __getattr__


class Element(_ET.Element):
    __getattr__ = __getattr__

But this does not work because ET.parse, ET.fromstring, ET.ElementTree.find etc will always return the internal Element and ElementTree objects.

Monkeypatching the classes does not work either:

>>> xml.etree.ElementTree.Element.__getattr__ = lambda x: x
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-bacb0a64c2e5> in <module>()
----> 1 xml.etree.ElementTree.Element.__getattr__ = lambda x: x

TypeError: can't set attributes of built-in/extension type 'xml.etree.ElementTree.Element'

Is this doable without copying the python etree module and then adding __getattr__?

Related: How to inherit ElementTree.Element class in python?

Upvotes: 1

Views: 1679

Answers (2)

Matteo Ferla
Matteo Ferla

Reputation: 2348

I had a similar issue and I accidentally found a quick workaround:
I imported xmlschema in a version prior to Python 3.7 and had no issues monkeypatching ET.Element with setattr.

EDIT: As this was intriguing I have since looked into it further. Here is how to monkeypatch ElementTree.

Upvotes: 1

arve0
arve0

Reputation: 3647

Overriding the class is not possible due to import of the Element and XMLParser's C implementation:

# Import the C accelerators
try:
    # Element is going to be shadowed by the C implementation. We need to keep
    # the Python version of it accessible for some "creative" by external code
    # (see tests)
    _Element_Py = Element

    # Element, SubElement, ParseError, TreeBuilder, XMLParser
    from _elementtree import *
except ImportError:
    pass

The module _elementtree is found in Modules/_elementtree.c.

A solution would be to copy the python etree module, and change the import to:

_Element_Py = Element

I made a simple implementation of this: https://github.com/arve0/objectifiedetree

Use like this:

from objectifiedetree import *

tree = ET.parse('/path/to/file.xml')
# dot notation :-)
el = tree.xpath.to.your.element

# use normal etree attributes
print(el.attrib)

# access name crashes
attrib_el = el.find('./attrib')

Upvotes: 2

Related Questions