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...
<Doc>
<Ele1>
<Ele11>
<Ele111>
</Ele111>
</Ele11>
</Ele1>
<Ele2>
</Ele2>
</Doc>
In XSLT, if my context is the Element "Ele111", how can I get XSLT to output the full path? I would want it to output: "/Doc/Ele1/Ele11/Ele111".
The context of this question: I have a very large, very deep document that I want to traverse exhaustively (generically using recursion), and if I find an element with a particular attribute, I want to know where I found it. I suppose I could carry along my current path as I traverse, but I would think XSLT/XPath should know.
XSLT <xsl:value-of> Element The <xsl:value-of> element is used to extract the value of a selected node.
XSLT current() Function The current() function returns a node-set that contains only the current node. Usually the current node and the context node are the same.
The method attribute can have the value xml , html , or text for XML, HTML, and text output, respectively. XSLT 2.0 will support a value of xhtml for XHTML support.
The <xsl:text> element writes literal text to the output tree.
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
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.
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.
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.
You can use the ancestor XPath Axes to walk all parent, and grandparents.
<xsl:for-each select="ancestor::*">...
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