XPath - Get node with no child of specific type

Xpath

Xpath Problem Overview


XML: /A/B or /A

I want to get all A nodes that do not have any B children.

I've tried

/A[not(B)]  
/A[not(exists(B))]

without success

I prefer a solution with the syntax /*[local-name()="A" and .... ], if possible. Any ideas that works?

Clarification. The xml looks like:

<WhatEver>
  <A>
    <B></B>
  </A>
</WhatEver> 

or

<WhatEver>
  <A></A>
</WhatEver>

Xpath Solutions


Solution 1 - Xpath

Maybe *[local-name() = 'A' and not(descendant::*[local-name() = 'B'])]?

Also, there should be only one root element, so for /A[...] you're either getting all your XML back or none. Maybe //A[not(B)] or /*/A[not(B)]?

I don't really understand why /A[not(B)] doesn't work for you.

~/xml% xmllint ab.xml
<?xml version="1.0"?>
<root>
    <A id="1">
            <B/>
    </A>
    <A id="2">
    </A>
    <A id="3">
            <B/>
            <B/>
    </A>
    <A id="4"/>
</root>
~/xml% xpath ab.xml '/root/A[not(B)]'
Found 2 nodes:
-- NODE --
<A id="2">
    </A>
-- NODE --
<A id="4" />

Solution 2 - Xpath

Try this "/A[not(.//B)]" or this "/A[not(./B)]".

Solution 3 - Xpath

The first / causes XPath to start at the root of the document, I doubt that is what you intended.

Perhaps you meant //A[not(B)] which would find all A nodes in the document at any level that do not have a direct B child.

Or perhaps you are already at a node that contains A nodes in which case you just want A[not(B)] as the XPath.

Solution 4 - Xpath

If you are trying to get A anywhere in the hierarchy from the root, this works (for xslt 1.0 as well as 2.0 in case its used in xslt)

//descendant-or-self::node()[local-name(.) = 'a' and not(count(b))]

OR you can also do

//descendant-or-self::node()[local-name(.) = 'a' and not(b)]

OR also

//descendant-or-self::node()[local-name(.) = 'a' and not(child::b)]

There are n no of ways in xslt to achieve the same thing.

Note: XPaths are case-sensitive, so if your node names are different (which I am sure, no one is gonna use A, B), then please make sure the case matches.

Solution 5 - Xpath

Use this:

/*[local-name()='A' and not(descendant::*[local-name()='B'])]

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMartin BringView Question on Stackoverflow
Solution 1 - XpathalamarView Answer on Stackoverflow
Solution 2 - XpathSerhiyView Answer on Stackoverflow
Solution 3 - XpathAnthonyWJonesView Answer on Stackoverflow
Solution 4 - XpathSO UserView Answer on Stackoverflow
Solution 5 - XpathCobaiaView Answer on Stackoverflow