After playing around with position()
in vain I was googling around for a solution and arrived at this older stackoverflow question which almost describes my problem.
The difference is that the nodeset I want the position within is dynamic, rather than a contiguous section of the document.
To illustrate I'll modify the example from the linked question to match my requirements. Note that each <b>
element is within a different <a>
element. This is the critical bit.
<root>
<a>
<b>zyx</b>
</a>
<a>
<b>wvu</b>
</a>
<a>
<b>tsr</b>
</a>
<a>
<b>qpo</b>
</a>
</root>
Now if I queried, using XPath for a/b
I'd get a nodeset of the four <b>
nodes. I want to then find the position within that nodeset of the node that contains the string 'tsr'
. The solution in the other post breaks down here: count(a/b[.='tsr']/preceding-sibling::*)+1
returns 1
because preceding-sibling
is navigating the document rather than the context node-set.
Is it possible to work within the context nodeset?
XPath defines several node tests that can be used to select nodes from the source tree. Strictly speaking, any XPath expression can be considered a node test; the expression para , for example, selects all <para> elements from the context node.
The descendant axis indicates all of the children of the context node, and all of their children, and so forth. Attribute and namespace nodes are not included - the parent of an attribute node is an element node, but attribute nodes are not the children of their parents.
Here is a general solution that works on any node that belongs in any node-set of nodes in the same document:
I am using XSLT to implement the solution, but finally obtain a single XPath expression that may be used with any other hosting language.
Let $vNodeSet
be the node-set and $vNode
be the node in this node-set whose position we want to find.
Then, let $vPrecNodes
contains all nodes in the XML document preceding $vNode
.
Then, let $vAncNodes
contains all nodes in the XML document that are ancestors of $vNode
.
The set of nodes in $vNodeSet
that precede $vNode
in document order consists of all nodes in the nodeset that belong also to $vPrecNodes
and all nodes in the node-set that also belong to $vAncNodes
.
I will use the well-known Kaysian formula for intersection of two nodesets:
$ns1[count(.|$ns2) = count($ns2)]
contains exactly the nodes in the intersection of $ns1
with $ns2
.
Based on all this, let $vPrecInNodeSet
is the set of nodes in $vNodeSet
that precede $vNode
in document order. The following XPath expression defines $vPrecInNodeSet
:
$vNodeSet
[count(.|$vPrecNodes) = count($vPrecNodes)
or
count(.|$vAncNodes) = count($vAncNodes)
]
Finally, the wanted position is: count($vPrecInNodeSet) +1
Here's how this all works together:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vNodeSet" select="/*/a/b"/>
<xsl:variable name="vNode" select="$vNodeSet[. = 'tsr'][1]"/>
<xsl:variable name="vPrecNodes" select="$vNode/preceding::node()"/>
<xsl:variable name="vAncNodes" select="$vNode/ancestor::node()"/>
<xsl:variable name="vPrecInNodeSet" select=
"$vNodeSet
[count(.|$vPrecNodes) = count($vPrecNodes)
or
count(.|$vAncNodes) = count($vAncNodes)
]
"/>
<xsl:template match="/">
<xsl:value-of select="count($vPrecInNodeSet) +1"/>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on the provided XML document:
<root>
<a>
<b>zyx</b>
</a>
<a>
<b>wvu</b>
</a>
<a>
<b>tsr</b>
</a>
<a>
<b>qpo</b>
</a>
</root>
the correct result is produced:
3
Do note: This solution does not depend on XSLT (used only for illustrative purposes). You may assemble a single XPath expression, substituting the variables with their definition, until there are no more variables to substitute.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With