user1765902
user1765902

Reputation: 291

XSLT value() and position() give incorrect indices

Can someone please explain me why I get the following output applying the follwoing xsl-file to the xml-file.

<?xml version="1.0" encoding="ISO-8859-1"?>
<source>    
    <number>1</number> 
    <number>2</number> 
    <number>3</number> 
    <number>4</number> 
    <number>5</number> 
    <number>6</number> 
    <number>7</number> 
    <number>8</number> 
</source>

====================================

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match="number">
   <p>
   <xsl:value-of select="position()"/>
   <xsl:text> of </xsl:text>
   <xsl:value-of select="last()"/>
   </p>
</xsl:template>
</xsl:stylesheet>

======================================

<p>2 of 17</p> 
    <p>4 of 17</p> 
    <p>6 of 17</p> 
    <p>8 of 17</p> 
    <p>10 of 17</p> 
    <p>12 of 17</p> 
    <p>14 of 17</p> 
    <p>16 of 17</p> 

I don't quite get why the output is not 1 of 8, 2 of 8 and so on.

Upvotes: 5

Views: 1272

Answers (3)

Daniel Haley
Daniel Haley

Reputation: 52888

Another option is to use xsl:number. It's much more powerful than position(). (The additional capabilities of xsl:number aren't needed in this simple example, but it is good to know.)

Example:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>

    <xsl:template match="number">
        <p>
            <xsl:number/>
            <xsl:text> of </xsl:text>
            <xsl:value-of select="../number[last()]"/>
        </p>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Mathias M&#252;ller
Mathias M&#252;ller

Reputation: 22647

A good answer has already been given by @ColinE. I would like to add another way of bypassing this problem of counting nodes that are not explicitly targeted by your templates. I.e. without using strip-space.

Matching your document node (source) and using an xsl:for-each element, you make sure that only number elements are taken into consideration. In other words, the superfluous nodes are now out of context.

<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

<xsl:output method="xml" omit-xml-declaration="yes"/>

<xsl:template match="source">
  <xsl:for-each select="number">
     <p>
        <xsl:value-of select="position()"/>
        <xsl:text> of </xsl:text>
        <xsl:value-of select="last()"/>
     </p>
     <xsl:text>&#10;</xsl:text>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

This gives the same output:

<p>1 of 8</p>
<p>2 of 8</p>
<p>3 of 8</p>
<p>4 of 8</p>
<p>5 of 8</p>
<p>6 of 8</p>
<p>7 of 8</p>
<p>8 of 8</p>

On the other hand, strip-space makes your input look like this:

<source><number>1</number><number>2</number><number>3</number><number>4</number><number>5</number><number>6</number><number>7</number><number>8</number></source>

Now, if you apply your initial stylesheet to the above input XML that lacks any whitespace, you will arrive at the same result as when using the solutions put forward by ColinE and me.

Upvotes: 4

ColinE
ColinE

Reputation: 70160

Try adding strip-space as shown below:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:strip-space elements="*"/>
  <xsl:template match="number">
   <p>
   <xsl:value-of select="position()"/>
   <xsl:text> of </xsl:text>
     <xsl:value-of select="last()"/>
   </p>
</xsl:template>
</xsl:stylesheet>

This gives the following output:

<p>1 of 8</p>
<p>2 of 8</p>
<p>3 of 8</p>
<p>4 of 8</p>
<p>5 of 8</p>
<p>6 of 8</p>
<p>7 of 8</p>
<p>8 of 8</p>

This is due to whitespace issues, as described in this document. Basically the nodeset contains whitespace nodes that are not matched by your template, but contribute to the index of each node.

Upvotes: 4

Related Questions