Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

xml

xslt

xpath

People also ask

Does XSLT use XPath?

XSLT uses XPath to find information in an XML document. XPath is used to navigate through elements and attributes in XML documents. In the transformation process, XSLT uses XPath to define parts of the source document that should match one or more predefined templates.

How do you break for each in XSLT?

There is no such a thing as break in an XSLT for-each loop. xsl:if/@test is pretty much all you can do. The other way would be to include this condition within the for-each/@select.

How do I create a substring in XSLT?

Substring is primarily used to return a section of a string or truncating a string with the help of the length provided. XSLT is incorporated with XPath while manipulating strings of text when XSLT access. The Substring function takes a string as an argument with the numbers one or two and an optional length.

For what purpose XSLT uses XPath?

XPath is a major element in the XSLT standard. XPath can be used to navigate through elements and attributes in an XML document.


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>

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


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>

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:

apple,orange,banana

EDIT:

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? :)


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>