Tutorial :How do I generate a comma-separated list with XSLT/XPath?


Given this XML data:

  <root>    <item>apple</item>    <item>orange</item>    <item>banana</item>  </root>  

I can use this XSLT markup:

  ...  <xsl:for-each select="root/item">    <xsl:value-of select="."/>,  </xsl:for-each>  ...  

to get this result:

apple, orange, banana,

but how do I produce a list where the last comma is not present? I assume it can be done doing something along the lines of:

  ...  <xsl:for-each select="root/item">    <xsl:value-of select="."/>    <xsl:if test="...">,</xsl:if>  </xsl:for-each>  ...  

but what should the test expression be?

I need some way to figure out how long the list is and where I currently am in the list, or, alternatively, if I am currently processing the last element in the list (which means I don't care how long it is or what the current position is).


Take a look at the position(), count() and last() functions; e.g., test="position() &lt; last()".


This is a pretty common pattern:

<xsl:for-each select="*">     <xsl:value-of select="."/>     <xsl:if test="position() != last()">        <xsl:text>,</xsl:text>     </xsl:if>  </xsl:for-each>  


For an XSLT 2.0 option, you can use the separator attribute on xsl:value-of.

This xsl:value-of:

<xsl:value-of select="/root/item" separator=", "/>  

would produce this output:

apple, orange, banana  

You could also use more than just a comma for a separator. For example, this:

<xsl:text>'</xsl:text>  <xsl:value-of select="/root/item" separator="', '"/>  <xsl:text>'</xsl:text>  

Would produce the following output:

'apple', 'orange', 'banana'  

Another XSLT 2.0 option is string-join()...

<xsl:value-of select="string-join(/*/item,', ')"/>  


<xsl:if test="following-sibling::*">,</xsl:if>  

or (perhaps more efficient, but you'd have to test):

<xsl:for-each select="*[1]">     <xsl:value-of select="."/>     <xsl:for-each select="following-sibling::*">         <xsl:value-of select="concat(',',.)"/>     </xsl:for-each>  </xsl:for-each>  


Robert gave the classis not(position() = last()) answer. This requires you to process the whole current node list to get context size, and in large input documents this might make the conversion consume more memory. Therefore, I normally invert the test to be the first thing

<xsl:for-each select="*">    <xsl:if test="not(position() = 1)>, </xsl:if>    <xsl:value-of select="."/>     </xsl:for-each>  


A simple XPath 1.0 one-liner:

     concat(., substring(',', 2 - (position() != last())))

Put it into this transformation:

<xsl:stylesheet version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="text"/>        <xsl:template match="/*">        <xsl:for-each select="*">          <xsl:value-of select=           "concat(., substring(',', 2 - (position() != last())))"           />        </xsl:for-each>      </xsl:template>  </xsl:stylesheet>  

and apply it to the XML document:

<root>      <item>apple</item>      <item>orange</item>      <item>banana</item>  </root>  

to get the wanted result:



Here is a comment from Robert Rossney to this answer:

That's pretty opaque code for a human to read. It requires you to know two non-obvious things about XSLT: 1) what the substring function does if its index is out of range and 2) that logical values can be implicitly converted to numerical ones.

and here is my answer:

Guys, never shy from learning something new. In fact this is all Stack Overflow is about, isn't it? :)


This is the way I got it working for me. I tested this against your list:

<?xml version="1.0"?>  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <xsl:output method="text" />    <xsl:template match="root">      <xsl:call-template name="comma-join"><xsl:with-param name="list" select="item"/></xsl:call-template>  </xsl:template>    <xsl:template name="comma-join">      <xsl:param name="list" />      <xsl:for-each select="$list">          <xsl:value-of select="." />          <xsl:if test="position() != last()">              <xsl:text>, </xsl:text>          </xsl:if>      </xsl:for-each>  </xsl:template>   </xsl:stylesheet>  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Next Post »