lxml etree find closest element before

1.1k views Asked by At

The xml document has a structure like the following

<a>
    <b>
        <d>
    </b>

    <c attr1="important"/>
    <b>
        <d>
    </b>
    <c attr1="so important" />
    <b></b>
</a>

My parser first gets all the <d> elements

from lxml import etree
xmltree = etree.parse(document)
elems = xmltree.xpath('//d')

Now the task is:

Get the attributes from the closest <c> tag before the current <d> tag, if there is one.

The naive approach would be to do something like the following

for el in elems:
    it = el.getparent()
    while it != None and it.tag != 'c':
        prev = it.getprevious()
        if prev == None:
            it = it.getparent()
        else:
            it = prev

    if it != None:
        print el, it.get("attr1")

But for me this does not look simple - am I missing something from the docs? How can I solve this without implementing my own iterator?

1

There are 1 answers

1
alecxe On BEST ANSWER

Use the preceding axis:

The preceding axis indicates all the nodes that precede the context node in the document except any ancestor, attribute and namespace nodes.

for el in elems:
    try:
        print el.xpath("preceding::c[@attr1]")[-1].get("attr1")
    except IndexError:
        print "No preceding 'c' element."

Demo:

>>> from lxml import etree
>>> 
>>> data = """
... <a>
...     <b>
...         <d/>
...     </b>
... 
...     <c attr1="important"/>
...     <b>
...         <d/>
...     </b>
...     <c attr1="so important" />
...     <b></b>
... </a>
... """
>>> xmltree = etree.fromstring(data)
>>> elems = xmltree.xpath('//d')
>>> 
>>> for el in elems:
...     try:
...         print el.xpath("preceding::c[@attr1]")[-1].get("attr1")
...     except IndexError:
...         print "No preceding 'c' element."
... 
No preceding 'c' element.
important