I have an XML document similar to the following:
<tt>
<a text="1"/>
<a text="2"/>
...
<a text="n"/>
<b text="14">data</b>
<b text="2">data</b>
...
</tt>
How can I select all <b> elements that have text attribute not equal to the text attribute of any of the <a> elements? I’m using XPath 1.0.
I’ve thinking about something like tt/b[not (tt/a[@text = xxx::@text])], where xxx should refer to the tt/b element being examined. I don’t know how exactly it may be done.
An answer such as
/tt/b[@text != ../a/@text]is wrong and selects the wrong set of nodes:As we see, the second selected node’s
textattribute is2and there is anaelement whosetextattribute is2.Here is a correct XPath expression:
When evaluated against the provided XML document:
it correctly selects only one node:
Explanation:
By definition the XPath
!=operator has a very unintuitive behavior whenever at least one of its arguments is a node-set:From the W3C XPath 1.0 Recommendation:
In this particular case for the element:
The comparison:
is
true()even though there exists:because there exist at least one
../aelement (and actually more than one), the string (or numeric) value of whosetextattribute isn’t equal to"2".This is a well-known fact and a FAQ: Always avoid using the
!=operator unless you absolutely know what you are doing!The correct solution of this problem is to use the
not()function like this:This expression evaluates to
true()only if@text = ../a/@textisfalse()— that is only if there isn’t even a single../a/@textwhose string value is equal to the string value of thetextattribute of the context node.XSLT-based verification:
when this transformation is applied on the provided XML document (above), the correct result is produced: