Find the position of an element within its parent with XSLT / XPath

30,700

You could use

<xsl:value-of select="count(preceding-sibling::record)" />

or even, generically,

<xsl:value-of select="count(preceding-sibling::*[name() = name(current())])" />

Of course this approach will not work if you process a list of nodes that is not uniform, i.e.:

<xsl:apply-templates select="here/foo|/somewhere/else/bar" />

Position information is lost in such a case, unless you store it in a variable and pass that to the called template:

<xsl:variable name="pos" select="position()" />
<xsl:for-each select="$record">
  <xsl:call-template name="SomeTemplate">
    <xsl:with-param name="pos" select="$pos" />
  </xsl:call-template>
</xsl:for-each>

but obviously that would mean some code rewriting, which I realize you want to avoid.


Final hint: position() does not tell you the position of the node within its parent. It tells you the position of the current node relative to the list of nodes you are processing right now.

If you only process (i.e. "apply templates to" or "loop over") nodes within one parent, this happens to be the same thing. If you don't, it's not.

Final hint #2: This

<xsl:for-each select="/path/to/record">
  <xsl:variable name="record" select="."/>
  <xsl:for-each select="$record">
    <xsl:call-template name="SomeTemplate"/>
  </xsl:for-each>
</xsl:for-each>

is is equivalent to this:

<xsl:for-each select="/path/to/record">
  <xsl:call-template name="SomeTemplate"/>
</xsl:for-each>

but the latter works without destroying the meaning of position(). Calling a template does not change context, so . will refer to the correct node withing the called template.

Share:
30,700

Related videos on Youtube

Lukas Eder
Author by

Lukas Eder

I am the founder and CEO at Data Geekery, the company behind jOOQ.

Updated on July 21, 2020

Comments

  • Lukas Eder
    Lukas Eder almost 4 years

    Apart from rewriting a lot of XSLT code (which I'm not going to do), is there a way to find the position of an element within its parent, when the context is arbitrarily set to something else? Here's an example:

    <!-- Here are my records-->
    <xsl:for-each select="/path/to/record">
      <xsl:variable name="record" select="."/>
    
      <!-- At this point, I could use position() -->
      <!-- Set the context to the current record -->
      <xsl:for-each select="$record">
    
        <!-- At this point, position() is meaningless because it's always 1 -->
        <xsl:call-template name="SomeTemplate"/>
      </xsl:for-each>
    </xsl:for-each>
    
    
    <!-- This template expects the current context being set to a record -->
    <xsl:template name="SomeTemplate">
    
      <!-- it does stuff with the record's fields -->
      <xsl:value-of select="SomeRecordField"/>
    
      <!-- How to access the record's position in /path/to or in any other path? -->
    </xsl:template>
    

    NOTE: This is a simplified example. I have several constraints keeping me from implementing obvious solutions, such as passing new parameters to SomeTemplate, etc. I can really only modify the internals of SomeTemplate.

    NOTE: I'm using Xalan 2.7.1 with EXSLT. So those tricks are available

    Any ideas?

  • Lukas Eder
    Lukas Eder almost 13 years
    You're the man! I adapted your suggestion to 1 + count(preceding-sibling::*) and it worked like a charm!
  • Lukas Eder
    Lukas Eder almost 13 years
    About your update: In the real-world example, the records are always uniform (though not always called record) as they model a table. So there is no union operator of two "incompatible" XPath node-sets. The variable solution wouldn't work because of the complexity of the real-world code
  • Lukas Eder
    Lukas Eder almost 13 years
    Thanks for the additional hints about position(). Since I'm always looping over uniform elements, in my case, the looping position does coincide with the element index within its parent.
  • Lukas Eder
    Lukas Eder almost 13 years
    ;-)... You can stop adding hints now. The example is really simplified to explain the question. The real-world code is much too complex to put in a Stack Overflow question
  • Tomalak
    Tomalak almost 13 years
    @Lukas: Okay, no more hints. ;-) I was more thinking of "the next guy who sees this question", though.