ElementTree – findall to recursively select all child elements

Question:

Python code:

import xml.etree.ElementTree as ET
root = ET.parse("h.xml")
print root.findall('saybye')

h.xml code:

<hello>
  <saybye>
   <saybye>
   </saybye>
  </saybye>
  <saybye>
  </saybye>
</hello>

Code outputs,

[<Element 'saybye' at 0x7fdbcbbec690>, <Element 'saybye' at 0x7fdbcbbec790>]

saybye which is a child of another saybye is not selected here. So, how to instruct findall to recursively walk down the DOM tree and collect all three saybye elements?

Asked By: user2879704

||

Answers:

Quoting findall,

Element.findall() finds only elements with a tag which are direct children of the current element.

Since it finds only the direct children, we need to recursively find other children, like this

>>> import xml.etree.ElementTree as ET
>>> 
>>> def find_rec(node, element, result):
...     for item in node.findall(element):
...         result.append(item)
...         find_rec(item, element, result)
...     return result
... 
>>> find_rec(ET.parse("h.xml"), 'saybye', [])
[<Element 'saybye' at 0x7f4fce206710>, <Element 'saybye' at 0x7f4fce206750>, <Element 'saybye' at 0x7f4fce2067d0>]

Even better, make it a generator function, like this

>>> def find_rec(node, element):
...     for item in node.findall(element):
...         yield item
...         for child in find_rec(item, element):
...             yield child
... 
>>> list(find_rec(ET.parse("h.xml"), 'saybye'))
[<Element 'saybye' at 0x7f4fce206a50>, <Element 'saybye' at 0x7f4fce206ad0>, <Element 'saybye' at 0x7f4fce206b10>]
Answered By: thefourtheye

Element.findall() finds only elements with a tag which are direct children of the current element.

we need to recursively traversing all childrens to find elements matching your element.

def find_rec(node, element):
    def _find_rec(node, element, result):
        for el in node.getchildren():
            _find_rec(el, element, result)
        if node.tag == element:
            result.append(node)
    res = list()
    _find_rec(node, element, res)
    return res
Answered By: deimos

From version 2.7 on, you can use xml.etree.ElementTree.Element.iter:

import xml.etree.ElementTree as ET
root = ET.parse("h.xml")
print root.iter('saybye')

See 19.7. xml.etree.ElementTree — The ElementTree XML API

Answered By: Ingo Schalk-Schupp

If you aren’t afraid of a little XPath, you can use the // syntax that means find any descendant node:

import xml.etree.ElementTree as ET
root = ET.parse("h.xml")
print(root.findall('.//saybye'))

Full XPath isn’t supported, but here’s the list of what is:
https://docs.python.org/2/library/xml.etree.elementtree.html#supported-xpath-syntax

Answered By: Lowell

Here is another way to do this:

from xml.dom.minidom import parse, Node
def find_id_attribute(parent, attribute_name="XMetresPerPixel"):
    #inspired https://realpython.com/python-xml-parser/
    if parent.nodeType == Node.ELEMENT_NODE:
        # print(attribute_name)
        if parent.hasAttribute(attribute_name):
            print(parent) #parent.setIdAttribute(attribute_name)
        if parent.tagName == attribute_name:
            print(parent.firstChild.data) #parent.setIdAttribute(attribute_name)
    for child in parent.childNodes:
        find_id_attribute(child, attribute_name)

document = parse("image0043.jpg.cal.xml")
find_id_attribute(document)
Answered By: Eugene
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.