Reputation: 6152
I was told that the following are not the same:
a[1][@attr="foo"]
a[@attr="foo"][1]
Can someone explain why that is the case?
Upvotes: 2
Views: 608
Reputation: 22617
Think of XPath expressions as defining a result set1 - a set of nodes that fulfil all the requirements stated in the XPath expression. The predicates of XPath expressions (the parts inside []
) either have no effect on the result set or they incrementally narrow it.
Put another way, in the following expression:
//xyz[@abc="yes"]
[@abc="yes"]
reduces the result set defined to the left of it, by //xyz
.
Note that, as Michael Kay has suggested, all that is said below only applies to XPath expressions with at least one positional predicate. Positional predicates are either a number: [1]
or evaluate to a number, or contain position()
or last()
.
If no positional predicate is present, the order of predicates in XPath expressions is not significant.
Consider the following simple input document:
<root>
<a attr="other"/>
<a attr="foo"/>
<a attr="other"/>
<a attr="foo"/>
</root>
As you can see, a[@attr = 'foo']
is not the first child element of root
. If we apply
//a[1]
to this document, this will of course result in
<a attr="other"/>
Now, crucially, if we add another predicate to the expression, like so:
//a[1][@attr="foo"]
Then, [@attr="foo"]
can only influence the result set defined by //a[1]
already. In this result set, there is no a[@attr="foo"]
- and the final result is empty.
On the other hand, if we start out with
//a[@attr="foo"]
the result will be
<a attr="foo"/>
-----------------------
<a attr="foo"/>
and in this case, if we add a second predicate:
//a[@attr="foo"][1]
the second predicate [1]
can narrow down the result set of //a[@attr="foo"]
to only contain the first of those nodes.
If you know XSLT, you might find an XSLT (and XPath 2.0) proof of this helpful:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<result1>
<xsl:copy-of select="//a[1][@attr='foo']"/>
</result1>
<result2>
<xsl:copy-of select="//a[@attr='foo'][1]"/>
</result2>
</xsl:template>
</xsl:transform>
And the result will be
<result1/>
<result2>
<a attr="foo"/>
</result2>
1 Technically speaking, only XPath 1.0 calls this result a node-set. In XPath 2.0, all sets have become true sequences of nodes.
Upvotes: 2