95

How to get a counter inside xsl:for-each loop that would reflect the number of current element processed.
For example my source XML is

<books>
    <book>
        <title>The Unbearable Lightness of Being </title>
    </book>
    <book>
        <title>Narcissus and Goldmund</title>
    </book>
    <book>
        <title>Choke</title>
    </book>
</books>

What I want to get is:

<newBooks>
    <newBook>
        <countNo>1</countNo>
        <title>The Unbearable Lightness of Being </title>
    </newBook>
    <newBook>
        <countNo>2</countNo>
        <title>Narcissus and Goldmund</title>
    </newBook>
    <newBook>
        <countNo>3</countNo>
        <title>Choke</title>
    </newBook>
</newBooks>

The XSLT to modify:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <newBooks>
            <xsl:for-each select="books/book">
                <newBook>
                    <countNo>???</countNo>
                    <title>
                        <xsl:value-of select="title"/>
                    </title>
                </newBook>
            </xsl:for-each>
        </newBooks>
    </xsl:template>
</xsl:stylesheet>

So the question is what to put in place of ???. Is there any standard keyword or do I simply must declare a variable and increment it inside the loop?

As the question is pretty long I should probably expect one line or one word answer :)

kristof
  • 52,923
  • 24
  • 87
  • 110

5 Answers5

155

position(). E.G.:

<countNo><xsl:value-of select="position()" /></countNo>
redsquare
  • 78,161
  • 20
  • 151
  • 159
  • 10
    This is all fine and dandy until you have to add a filter like xsl:if inside your xsl:for-each. Then position() is USELESS and you need proper counter. – Mike Starov Mar 19 '12 at 16:00
  • 4
    @Mike Stavrov That was not part of the question! Cannot cover every situation – redsquare Mar 19 '12 at 21:10
  • @redsquare Correct. Just adding my two cents. I had to write an XSL extension function to resolve my described situation. Maybe I should ask people here about better solutions. – Mike Starov Mar 21 '12 at 16:05
  • 2
    @MikeStarov then how to do if there is a `xsl:if` inside the `xsl:for-each`? What is a "proper counter"? Can you point to some resource? – lajarre Dec 10 '13 at 18:53
  • @lajarre You write a custom extension function to be called from XSL. You can also do two pass processing. Process once and save to xsl:variable. Then apply templates on xsl variable contents and add numbering using position() – Mike Starov Dec 12 '13 at 01:02
13

Try inserting <xsl:number format="1. "/><xsl:value-of select="."/><xsl:text> in the place of ???.

Note the "1. " - this is the number format. More info: here

m_pGladiator
  • 8,462
  • 7
  • 43
  • 61
10

Try:

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

Edit - had a brain freeze there, position() is more straightforward!

Luke Bennett
  • 32,786
  • 3
  • 30
  • 57
  • 3
    Still could be useful, especially if you're selecting based on criteria other than position(). +1 – jsuddsjr Jan 30 '15 at 22:10
8

You can also run conditional statements on the Postion() which can be really helpful in many scenarios.

for eg.

 <xsl:if test="(position( )) = 1">
     //Show header only once
    </xsl:if>
Arun Arangil
  • 81
  • 1
  • 1
  • 2
    This fails if you have a filter such as a xsl:sort because then the first item may not be the first to be processed. – Alexis Wilke May 27 '12 at 07:27
5
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <newBooks>
                <xsl:for-each select="books/book">
                        <newBook>
                                <countNo><xsl:value-of select="position()"/></countNo>
                                <title>
                                        <xsl:value-of select="title"/>
                                </title>
                        </newBook>
                </xsl:for-each>
        </newBooks>
    </xsl:template>
</xsl:stylesheet>
Santiago Cepas
  • 4,044
  • 2
  • 25
  • 31