Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
600 views
Welcome To Ask or Share your Answers For Others

1 Answer

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>&#xA;</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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...