StuffHappens
StuffHappens

Reputation: 6557

xpath to select all leaf nodes with given ancestors attribute

I have a batch of xmls where each tag has an attribute isOptional. An example of such xml is

<node1 isOptional="False">
    <node2 isOptional="True">
        <node22 isOptional="False">
            <node3 isOptional="False">text4</node3>
        </node22>
    </node2>
    <node4 isOptional="False">
        <node5 isOptional="False">
            <node6 isOptional="True">text3</node6>
            <node7 isOptional="False">
                <node8 isOptional="True">text2</node8>
                <node9 isOptional="False">text1</node9>
            </node7>
        </node5>
    </node4>
</node1>

I need to write xpath to select all leaf nodes which do not have ancessors with @isOptional="True". For this example the result should be:

       <node9 isOptional="False">text1</node9>

Actually I need something like this:

//*[not(*) and @isOptional="False" and count(ancestor::*[@isOptional = "True"] = 0)]

Could you please help me to get the correct xpath?

Upvotes: 0

Views: 504

Answers (2)

Mathias M&#252;ller
Mathias M&#252;ller

Reputation: 22617

I think you forgot to mention one additional requirement. What you said:

I need to write xpath to select all leaf nodes which do not have ancessors with @isOptional="True"

would map to the following XPath expression:

//*[not(*) and not(ancestor::*[@isOptional = 'True'])]
^^^                                                           "all"
   ^^^^^^^                                                    "leaf nodes"
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        "no ancestor where
                                                              @isOptional equals 'True'"

which would yield (individual results separated by ------):

<node6 isOptional="True">text3</node6>
-----------------------
<node8 isOptional="True">text2</node8>
-----------------------
<node9 isOptional="False">text1</node9>

But I assume you meant to add: Additionally, the isOptional attribute of the target nodes must not be set to True either. This results in the following path expression, already correctly suggested by potame:

//*[not(*) and not(ancestor-or-self::*[@isOptional = 'True'])]

and the only result is

<node9 isOptional="False">text1</node9>

because now the XPath expression contains the ancestor-or-self:: axis, which includes the context node itself.

Upvotes: 1

potame
potame

Reputation: 7905

An XPath like this one should work:

//*[not(child::*) and not(ancestor-or-self::*[@isOptional = 'True'])]

Upvotes: 1

Related Questions