In XSLT, is there a way to determine where you are in an XML document when processing an element?
Example: Given the following XML Doc Fragment...
&l
You can use the ancestor XPath Axes to walk all parent, and grandparents.
<xsl:for-each select="ancestor::*">...
Since XPath 3.0 as supported by Saxon 9.8 (all editions) or Saxon 9.7 with version="3.0"
in the XSLT and XmlPrime 4 (using --xt30
) as well as 2017 releases of Altova (using version="3.0"
stylesheets) there is the built-in path
function (https://www.w3.org/TR/xpath-functions-30/#func-path, https://www.w3.org/TR/xpath-functions-31/#func-path) which for an input like
<?xml version="1.0" encoding="UTF-8"?>
<Doc>
<Ele1>
<Ele11>
<Ele111>
<foo/>
<foo/>
<bar/>
<foo/>
<foo/>
<bar/>
<bar/>
</Ele111>
</Ele11>
</Ele1>
<Ele2/>
</Doc>
and a stylesheet like
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select="//*/path()" separator=" "/>
</xsl:template>
</xsl:stylesheet>
gives the output
/Q{}Doc[1]
/Q{}Doc[1]/Q{}Ele1[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[2]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[3]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[4]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[2]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[3]
/Q{}Doc[1]/Q{}Ele2[1]
That output is not as compact in case of lack of namespaces as most hand-made attempts but the format has the advantage (at least given XPath 3.0 or 3.1 support) to allow for namespaces being used and get a format for the returned path that does not require the user of the path expression to set up any namespace bindings to evaluate it.
The currently accepted answer will return incorrect paths. For example, the element Ele2
in the OP sample XML would return the path /Doc[1]/Ele2[2]
. It should be /Doc[1]/Ele2[1]
.
Here's a similar XSLT 1.0 template that returns the correct paths:
<xsl:template name="genPath">
<xsl:param name="prevPath"/>
<xsl:variable name="currPath" select="concat('/',name(),'[',
count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
<xsl:for-each select="parent::*">
<xsl:call-template name="genPath">
<xsl:with-param name="prevPath" select="$currPath"/>
</xsl:call-template>
</xsl:for-each>
<xsl:if test="not(parent::*)">
<xsl:value-of select="$currPath"/>
</xsl:if>
</xsl:template>
Here's an example that will add a path
attribute to all elements.
XML Input
<Doc>
<Ele1>
<Ele11>
<Ele111>
<foo/>
<foo/>
<bar/>
<foo/>
<foo/>
<bar/>
<bar/>
</Ele111>
</Ele11>
</Ele1>
<Ele2/>
</Doc>
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="text()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="path">
<xsl:call-template name="genPath"/>
</xsl:attribute>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template name="genPath">
<xsl:param name="prevPath"/>
<xsl:variable name="currPath" select="concat('/',name(),'[',
count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
<xsl:for-each select="parent::*">
<xsl:call-template name="genPath">
<xsl:with-param name="prevPath" select="$currPath"/>
</xsl:call-template>
</xsl:for-each>
<xsl:if test="not(parent::*)">
<xsl:value-of select="$currPath"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XML Output
<Doc path="/Doc[1]">
<Ele1 path="/Doc[1]/Ele1[1]">
<Ele11 path="/Doc[1]/Ele1[1]/Ele11[1]">
<Ele111 path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]">
<foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[1]"/>
<foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[2]"/>
<bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[1]"/>
<foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[3]"/>
<foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[4]"/>
<bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[2]"/>
<bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[3]"/>
</Ele111>
</Ele11>
</Ele1>
<Ele2 path="/Doc[1]/Ele2[1]"/>
</Doc>
Here's another version that only outputs the positional predicate if it's needed. This example is also different in that it's just outputting the path instead of adding an attribute.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="text()"/>
<xsl:template match="*">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',local-name())"/>
<!--Predicate is only output when needed.-->
<xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
<xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
using the input above, this stylesheet outputs:
/Doc
/Doc/Ele1
/Doc/Ele1/Ele11
/Doc/Ele1/Ele11/Ele111
/Doc/Ele1/Ele11/Ele111/foo[1]
/Doc/Ele1/Ele11/Ele111/foo[2]
/Doc/Ele1/Ele11/Ele111/bar[1]
/Doc/Ele1/Ele11/Ele111/foo[3]
/Doc/Ele1/Ele11/Ele111/foo[4]
/Doc/Ele1/Ele11/Ele111/bar[2]
/Doc/Ele1/Ele11/Ele111/bar[3]
/Doc/Ele2
I'm not sure which XSLT processor you're using, but if it is Saxon, you can use extension function path(). Other processors may have same functionality.
Don't think this is built into XPath, you probably need a recursive template, like the one here, which I've based this example on. It will walk every element in an XML document and output the path to that element in a style similar to the one you've described.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="/">
<paths>
<xsl:apply-templates/>
</paths>
</xsl:template>
<xsl:template match="//*">
<path>
<xsl:for-each select="ancestor-or-self::*">
<xsl:call-template name="print-step"/>
</xsl:for-each>
</path>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="print-step">
<xsl:text>/</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="1+count(preceding-sibling::*)"/>
<xsl:text>]</xsl:text>
</xsl:template>
</xsl:stylesheet>
There are a few complications; consider this tree:
<root>
<child/>
<child/>
</root>
How do you tell the difference between the two child nodes? So you need some index into your item-sequence, child1 and child[2], for example.