XPath to find nearest ancestor element that contai

2020-05-20 07:26发布

ancestor::foo[bar[@attr="val"]]

I thought this would work, but it's not. I need to find the nearest foo element going up in the tree that has a bar child element, which in turn has an attr attribute of val.

标签: xpath
4条回答
可以哭但决不认输i
2楼-- · 2020-05-20 08:00

This should work:

//*[ancestor::foo[bar[@attr="val"]]]

or alternatively

root/foo/bar[ancestor::foo[bar[@attr="val"]]]

matches the second <bar> element

<root>
    <foo>
        <bar attr="xxx"></bar>
    </foo>
    <foo>
        <bar attr="val"></bar>
    </foo>
    <foo>
        <bar attr="zzz"></bar>
    </foo>
</root>
查看更多
冷血范
3楼-- · 2020-05-20 08:07

If you have a file like this:

<root>                                                                                                             
<foo id="0">                                                                                                       
    <foo id="1">                                                                                                   
        <bar attr="xxx" />                                                                                         
    </foo>                                                                                                         
    <foo id="2">                                                                                                   
        <bar attr="val" />                                                                                         
    </foo>                                                                                                         
    <foo id="3">                                                                                                   
        <tar>                                                                                                      
            <bar attr="val" />                                                                                     
        </tar>                                                                                                     
    </foo>                                                                                                         
</foo>                                                                                                             
</root>

and you want the foo nodes with the ids 2 and 3, i.e. the closest foos to their bar descendants having attr=val, the following works:

//foo[descendant::bar[@attr="val"] and not(descendant::foo)]

If you omit and not(descendant::foo) you get additionally the foo with the id 0.

The other answers didn't work for me on the general case of my example.

You can test it in Python with:

from lxml import etree
tree = etree.parse('example.xml')
foos = tree.xpath('//foo[descendant::bar[@attr="val"] and not(descendant::foo)]')
for foo in foos:
    print(foo.attrib)

which prints:

{'id': '2'}
{'id': '3'}

Notice that the xpath used is not efficient. I would love to learn a more efficient one.

查看更多
在下西门庆
4楼-- · 2020-05-20 08:17

In my opinion the best answer was provided by the person who posed the question. His solution was supposed to return all the ancestor foos. He wants only the nearest one which is the one with position()=1, so his xpath expression needs to be slightly amended to:

ancestor::foo[bar[@attr="val"] and position() = 1]

If he writes that the ancestor::foo[bar[@attr="val"]] didn't return anything, so he had some other problem in his xml or in his assumption about the current element of his XPath evaluation context.

The other answers (starting with //) do not really answer the question and even if they by chance met the need of someone, so they are not efficient: they choose ALL elements in the xml file, applying filter on them afterwards. While what the relative xpath proposed by me or the person who was asking this question is just going to search through few elements starting from the current node up to the parent and it's parent etc. - it will be very efficient even with large XML files.

查看更多
爷、活的狠高调
5楼-- · 2020-05-20 08:21

Updated to reflect a solution intended by OP.

See @David's answer

For a sample xml below,

<root>
<foo id="0">
    <bar attr="val"/>
    <foo id="1">
        <bar attr="xxx"/>
    </foo>
    <foo id="2">
        <bar attr="val">
            <one>1</one>
            <two>
                <three>3</three>
            </two>
        </bar>
    </foo>
</foo>

a valid xpath on the reverse axis with <three> as the context node, a valid solution is

./ancestor::foo[bar[@attr='val']][position() = 1]

"./" is optional. position() = 1 is not optional as otherwise the ancestor foo satisfying the predicates would also be returned.

Old answer:ignore

This is an old question. but anybody who is interested, the following should get you what the original question intended.

Reverse axis

//bar[@attr='val']/ancestor::*[position()=1]

Forward axis

//*[child::bar[@attr='val']]
查看更多
登录 后发表回答