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?
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>]
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
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')
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
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)
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?
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>]
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
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')
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
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)