JRR
JRR

Reputation: 6152

xpath with multiple predicates equivalence

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

Answers (1)

Mathias Müller
Mathias Müller

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

Related Questions