Find element by text with XPath in ElementTree
Asked Answered
B

3

22

Given an XML like the following:

<root>
    <element>A</element>
    <element>B</element>
</root>

How can I match the element with content A using ElementTree and its support for XPath? Thanks

Bend answered 31/5, 2012 at 15:4 Comment(0)
C
38

AFAIK ElementTree does not support XPath. Has it changed?

Anyway, you can use lxml and the following XPath expression:

import lxml.etree
doc = lxml.etree.parse('t.xml')
print doc.xpath('//element[text()="A"]')[0].text
print doc.xpath('//element[text()="A"]')[0].tag

The result will be:

A
element
Courtenay answered 31/5, 2012 at 15:12 Comment(1)
ElementTree supports xpath, see my answer below.Elexa
E
12

You can use a subset of XPath in ElementTree. It isn't necessary to install any lib.

config.findall('.//*[element="A"]/element')

As the comment bellow from @Bionicegenius explains, the expression above just works if your element has no sibilings, but you get the idea.

It is possible to use XPath in ElementTree, and it is the simplest solution.

Elexa answered 7/11, 2017 at 23:18 Comment(3)
This has a problem of selecting all the elements on the same level as the desired node. This will find both elements with values A and B. If you modify it to find, then it will only ever find the element with a value of A, even if you search for B - it will only return the first child.Abyssal
Searching by text is a recent addition, the syntax is .//element[.="A"] . The above answer wouldn't work. ElementTree only support a very limited subset of XPath.Bontebok
@Bontebok - Great, thanks! Once I knew the syntax to look for, I found it in the python docs - it says it was added in Python 3.7. It's been there long enough for me!Rau
A
11

If you want to use the standard library ElementTree, rather than lxml, you can use iteration to find all sub elements with a particular text value. For example:

import sys
import xml.etree.ElementTree as etree

s = """<root>
    <element>A</element>
    <element>B</element>
</root>"""

e = etree.fromstring(s)

if sys.version_info < (2, 7):
    found = [element for element in e.getiterator() if element.text == 'A']
else:
    found = [element for element in e.iter() if element.text == 'A']

print found[0].text # This prints 'A', honestly!

Note: you may want to perform some stripping of the text value of your elements in the list comprehension.

Edit This will work to any depth in your XML tree. For example,

s = """<root>
    <element>A</element>
    <element><sub>A</sub></element>
</root>"""

found = [element for element in e.getiterator() if element.text == 'A']

for f in found:
    print f

will print

<Element element at 7f20a882e3f8>
<Element sub at 7f20a882e4d0>
Apollo answered 31/5, 2012 at 15:58 Comment(3)
I have chosen to use cElementTree over lxml since for my task it has a much lower memory overhead (memory being more critical than CPU usage), this was the final bit of code I needed to move from lxmlWeek
Can you please explain what "element for element" means?Ned
@ElliottB It is part of list comprehensions.P

© 2022 - 2024 — McMap. All rights reserved.