ChatGPT解决这个技术问题 Extra ChatGPT

XPath - Get node with no child of specific type

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>
Without seeing your XML I would say that "/A[not(B)]" is the way to go. What's wrong with that?
Also, there should be only one root element, so you're either getting all your XML back or none. Maybe //A[not(B)] or /*/A[not(B)]?
With the "abstract" syntax /A/B, first line after "XML:", I meant ,just as Tomalak and alamar thought.

a
alamar

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" />

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.
@MichaelKupietz why don't you create a new question about that?
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.
e
eozzy

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


"Try this" answers are low-value on Stackoverflow because they do very little to education/empower the OP and thousands of future researchers.
A
AnthonyWJones

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.


S
SO User

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.


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. ;-)
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.
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)
C
Cobaia

Use this:

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