75

XML: <A><B></B></A> or <A></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>
U. Windl
  • 3,480
  • 26
  • 54
Martin Bring
  • 1,176
  • 1
  • 7
  • 17

5 Answers5

58

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" />
alamar
  • 18,729
  • 4
  • 64
  • 97
  • I can confirm, /A[not(B)] doesn't always work as advertised. I have an example using Xalan in XMLspear where [DO STUFF HERE] produces different output from [DO STUFF HERE] In fact, in the same situation, using select="exsl:node-set($theFields)/*[name()='field']" produces output, but select="exsl:node-set($theFields)/field" doesn't at all. – John Smith Mar 29 '19 at 03:37
  • @MichaelKupietz why don't you create a new question about that? – alamar Mar 29 '19 at 09:03
  • At some point, when I have time to whittle a huge XSLT down to a minimum working demonstration, I may. At this point, though, I was just responding to the comment implying that /A(not[B]) ought to do what the OP says it doesn't, simply because I observed the same thing OP did. Evidently, it doesn't, at least, not always. That confirmation might spare some future reader of this page some confusion. – John Smith Mar 30 '19 at 03:25
24

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

eozzy
  • 66,048
  • 104
  • 272
  • 428
Serhiy
  • 4,357
  • 5
  • 37
  • 53
  • 5
    "Try this" answers are low-value on Stackoverflow because they do very little to education/empower the OP and thousands of future researchers. – mickmackusa Apr 04 '20 at 03:34
11

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.

U. Windl
  • 3,480
  • 26
  • 54
AnthonyWJones
  • 187,081
  • 35
  • 232
  • 306
3

Use this:

/*[local-name()='A' and not(descendant::*[local-name()='B'])]
Cobaia
  • 1,503
  • 3
  • 22
  • 41
3

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.

Rashmi Pandit
  • 23,230
  • 17
  • 71
  • 111
  • FYI, the question is about XPath, not XSLT. They are distinct (though related) technologies. Otherwise, your answer is technically correct. As an aside though, "/root/A[not(B)]" works just fine as @Alamar mentioned. ;-) – Cerebrus May 14 '09 at 09:16
  • Thanks for the correction. I meant xpaths in my last line. Have edited the post. As for "/root/A[not(B)]" I already ran it successfully. But yet posted alternate xpaths as it wasnt working for Martin. Which is also y i mentioned case-sensitivity. – Rashmi Pandit May 14 '09 at 09:32
  • How would one select all A with immediate children **other than** B? (A and all child nodes, as long as they contain one or more B elements only) – CodeManX Jan 15 '14 at 16:47