access ElementTree node parent node
Asked Answered
S

12

91

I am using the builtin Python ElementTree module. It is straightforward to access children, but what about parent or sibling nodes? - can this be done efficiently without traversing the entire tree?

Simplehearted answered 31/1, 2010 at 5:2 Comment(1)
See #374745Diploma
P
71

There's no direct support in the form of a parent attribute, but you can perhaps use the patterns described here to achieve the desired effect. The following one-liner is suggested (updated from the linked-to post to Python 3.8) to create a child-to-parent mapping for a whole tree, using the method xml.etree.ElementTree.Element.iter:

parent_map = {c: p for p in tree.iter() for c in p}
Poirier answered 31/1, 2010 at 8:4 Comment(3)
Syntax update, 2017 / python3 parent_map = {(c,p) for p in tree.iter( ) for c in p}Dietetic
Correction: parent_map = {c:p for p in root.iter( ) for c in p}Dietetic
What about if you cannot read the whole XML file in one go, but must iterate over a file with iter()?Brotherson
P
32

Vinay's answer should still work, but for Python 2.7+ and 3.2+ the following is recommended:

parent_map = {c:p for p in tree.iter() for c in p}

getiterator() is deprecated in favor of iter(), and it's nice to use the new dict list comprehension constructor.

Secondly, while constructing an XML document, it is possible that a child will have multiple parents, although this gets removed once you serialize the document. If that matters, you might try this:

parent_map = {}
for p in tree.iter():
    for c in p:
        if c in parent_map:
            parent_map[c].append(p)
            # Or raise, if you don't want to allow this.
        else:
            parent_map[c] = [p]
            # Or parent_map[c] = p if you don't want to allow this
Pocketknife answered 21/11, 2013 at 21:31 Comment(3)
What if you don't have access to the tree? Like after a .find()Ginger
I don't know of any way to get the root node (and thus parents/ancestors) if you didn't save a reference to it. But I don't understand how .find() has anything to do with that.Pocketknife
i just used .find() as an example function that just returns an elementGinger
T
28

You can use xpath ... notation in ElementTree.

<parent>
     <child id="123">data1</child>
</parent>

xml.findall('.//child[@id="123"]...')
>> [<Element 'parent'>]
Trapani answered 22/10, 2015 at 12:18 Comment(7)
This is fantastic solution, works with find() also if you know there's just a single element that you are looking for. Like so: root.find(".//*[@testname='generated_sql']...")Eurydice
I could not find anything about this ... XPath syntax. What does it do? Are there docs on it?Ragnar
@Ragnar ... expression comes from XPath 1.0. Python Std Library have limited support for XPath expressions, lxml have more support.Trapani
The code in the answer does work, but I cannot find any reference to this "triple dot" syntax anywhere. It is not mentioned in the XPath 1.0 recommendation.Halloran
What about elements that do not have an id attribute?Magi
@ioannis-filippidis Oh, you just need a valid XPath followed by an ... You can use any attribute All children: xml.findall('.//child...') Some other attribute: xml.findall('.//child[@other="123"]...')Trapani
Attention: This code works perfectly with only two dots. There is no such thing as "triple dot syntax". It's not in the docs, as others mentioned. It's just a combination of . (select current node) and .. (get parent).Overpowering
S
15

As mentioned in Get parent element after using find method (xml.etree.ElementTree) you would have to do an indirect search for parent. Having xml:

<a>
 <b>
  <c>data</c>
  <d>data</d>    
 </b>
</a>

Assuming you have created etree element into xml variable, you can use:

 In[1] parent = xml.find('.//c/..')
 In[2] child = parent.find('./c')

Resulting in:

Out[1]: <Element 'b' at 0x00XXXXXX> 
Out[2]: <Element 'c' at 0x00XXXXXX>

Higher parent would be found as:secondparent=xml.find('.//c/../..') being <Element 'a' at 0x00XXXXXX>

Seducer answered 24/11, 2015 at 22:19 Comment(1)
this is brilliant, would upvote more if I couldTupelo
C
7

Pasting here my answer from https://mcmap.net/q/245875/-get-parent-element-after-using-find-method-xml-etree-elementtree:

I had a similar problem and I got a bit creative. Turns out nothing prevents us from adding the parent info ourselves. We can later strip it once we no longer need it.

def addParentInfo(et):
    for child in et:
        child.attrib['__my_parent__'] = et
        addParentInfo(child)

def stripParentInfo(et):
    for child in et:
        child.attrib.pop('__my_parent__', 'None')
        stripParentInfo(child)

def getParent(et):
    if '__my_parent__' in et.attrib:
        return et.attrib['__my_parent__']
    else:
        return None

# Example usage

tree = ...
addParentInfo(tree.getroot())
el = tree.findall(...)[0]
parent = getParent(el)
while parent:
    doSomethingWith(parent)
    parent = getParent(parent)
stripParentInfo(tree.getroot())
Carnatic answered 1/3, 2019 at 11:46 Comment(0)
B
6

The XPath '..' selector cannot be used to retrieve the parent node on 3.5.3 nor 3.6.1 (at least on OSX), eg in interactive mode:

import xml.etree.ElementTree as ET
root = ET.fromstring('<parent><child></child></parent>')
child = root.find('child')
parent = child.find('..') # retrieve the parent
parent is None # unexpected answer True

The last answer breaks all hopes...

Briard answered 4/7, 2018 at 7:56 Comment(0)
W
3

Got an answer from

https://towardsdatascience.com/processing-xml-in-python-elementtree-c8992941efd2

Tip: use '...' inside of XPath to return the parent element of the current element.


for object_book in root.findall('.//*[@name="The Hunger Games"]...'):
    print(object_book)
Wabash answered 3/1, 2021 at 21:27 Comment(2)
This is the same answer as https://mcmap.net/q/234774/-access-elementtree-node-parent-node.Halloran
https://mcmap.net/q/234774/-access-elementtree-node-parent-node doesn't say what ... is, This answer does.Bearwood
A
1

Most solutions posted so far

  • either use XPath… but Python does not support finding ancestors with XPath in general (see comment),
  • or post-process the whole tree after it is built (e.g. this answer or that one)… but this requires parsing and building the whole tree, which might be undesirable with large XML data (e.g. Wikipedia dumps).

If you are parsing XML incrementally, say with xml.etree.ElementTree.iterparse or xml.etree.ElementTree.XMLPullParser, you can keep track of the current path (up from the root node down to the current node) by tracking the opening and closing of tags (start and end events). Example:

import xml.etree.ElementTree as ET

current_path = [ ]

for event, elem in ET.iterparse('test.xml', events=['start', 'end']):
    # opening tag:
    if event == 'start':
        current_path.append(elem)
    # closing tag:
    else:
        assert event == 'end'
        assert len(current_path) > 0 and current_path[-1] is elem
        current_path.pop()
        parent = current_path[-1] if len(current_path) > 0 else None
        # `elem` is the current element (fully built),
        # `parent` is its parent (some of its children after `elem`
        # might not have been parsed yet)
        #
        # ... do something ...
Aceldama answered 1/7, 2023 at 22:46 Comment(0)
M
0

If you are using lxml, I was able to get the parent element with the following:

parent_node = next(child_node.iterancestors())

This will raise a StopIteration exception if the element doesn't have ancestors - so be prepared to catch that if you may run into that scenario.

Mercurial answered 4/12, 2014 at 4:4 Comment(1)
element object in lxml has getparent() methodByway
S
-1
import xml.etree.ElementTree as ET

f1 = "yourFile"

xmlTree = ET.parse(f1)

for root in xmlTree.getroot():
    print(root.tag)
Suzannasuzanne answered 16/9, 2022 at 6:42 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Ectopia
E
-2

Another way if just want a single subElement's parent and also known the subElement's xpath.

parentElement = subElement.find(xpath+"/..")
Excretion answered 23/2, 2014 at 2:41 Comment(2)
Doesn't work for me, I get 'None' - same if i just use subElement.find('..').Suprasegmental
Assumes that a variable called xpath already exists, so it's not helpful for most people.Senarmontite
P
-3

Look at the 19.7.2.2. section: Supported XPath syntax ...

Find node's parent using the path:

parent_node = node.find('..')
Patronize answered 13/12, 2017 at 23:29 Comment(3)
Did you test this? If you were able to make it work, please post a complete code example that demonstrates it. See this comment: #2171110Halloran
The Python 3 documentation says: "Returns None if the path attempts to reach the ancestors of the start element (the element find was called on)." (docs.python.org/3/library/…).Halloran
Works for me. The best and most consise answer.Wellread

© 2022 - 2024 — McMap. All rights reserved.