XPath/XSLT: how to select all elements that satisfy a condition involving another set of elements
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:
<b text="14">data</b>
<b text="2">data</b>
As we see, the second selected node's text attribute is 2 and there is an a element whose text attribute is 2.
Here is a correct XPath expression:
/tt/b[not(@text = ../a/@text)]
When evaluated against the provided XML document:
<tt>
<a text="1"/>
<a text="2"/>
...
<a text="n"/>
<b text="14">data</b>
<b text="2">data</b>
...
</tt>
it correctly selects only one node:
<b text="14">data</b>
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:
"If one object to be compared is a node-set and the other is a number, then the comparison will be true if and only if there is a node in the node-set such that the result of performing the comparison on the number to be compared and on the result of converting the string-value of that node to a number using the number function is true. If one object to be compared is a node-set and the other is a string, then the comparison will be true if and only if there is a node in the node-set such that the result of performing the comparison on the string-value of the node and the other string is true"
In this particular case for the element:
<b text="2">data</b>
The comparison:
@text != ../a/@text
is true() even though there exists:
<a text="2"/>
because there exist at least one ../a element (and actually more than one), the string (or numeric) value of whose text attribute 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:
not(@text = ../a/@text)
This expression evaluates to true() only if @text = ../a/@text is false() -- that is only if there isn't even a single ../a/@text whose string value is equal to the string value of the text attribute of the context node.
XSLT-based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select="/tt/b[not(@text = ../a/@text)]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document (above), the correct result is produced:
<b text="14">data</b>
加载中,请稍侯......
精彩评论