Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT concat string, remove last comma

I need to build up a string using XSLT and separate each string with a comma but not include a comma after the last string. In my example below I will have a trailing comma if I have Distribution node and not a Note node for instance. I don't know of anyway to build up a string as a variable and then truncate the last character in XSLT. Also this is using the Microsoft XSLT engine.

My String =

<xsl:if test="Locality != ''">   <xsl:value-of select="Locality"/>, </xsl:if> <xsl:if test="CollectorAndNumber != ''">   <xsl:value-of select="CollectorAndNumber"/>, </xsl:if> <xsl:if test="Institution != ''">   <xsl:value-of select="Institution"/>, </xsl:if> <xsl:if test="Distribution != ''">   <xsl:value-of select="Distribution"/>, </xsl:if> <xsl:if test="Note != ''">   <xsl:value-of select="Note"/> </xsl:if> 

[Man there's gotta be a better way to enter into this question text box :( ]

like image 544
Craig Avatar asked Apr 28 '09 14:04

Craig


2 Answers

This is very easy to accomplish with XSLT (No need to capture the results in a variable, or to use special named templates):

I. XSLT 1.0:

<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=       "Locality/text() | CollectorAndNumber/text()      | Institution/text() | Distribution/text()      | Note/text()       "       >         <xsl:value-of select="."/>         <xsl:if test="not(position() = last())">,</xsl:if>       </xsl:for-each>     </xsl:template> </xsl:stylesheet> 

when this transformation is applied on the following XML document:

<root>     <record>         <Locality>Locality</Locality>         <CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>         <Institution>Institution</Institution>         <Distribution>Distribution</Distribution>         <Note></Note>         <OtherStuff>Unimportant</OtherStuff>     </record> </root> 

the wanted result is produced:

Locality,CollectorAndNumber,Institution,Distribution 

If the wanted elements should be produced not in document order (something not required in the question, but raised by Tomalak), it is still quite easy and elegant to achieve this:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">     <xsl:output method="text"/>      <xsl:param name="porderedNames"      select="' CollectorAndNumber Locality Distribution Institution Note '"/>      <xsl:template match="/*/*">         <xsl:for-each select=          "*[contains($porderedNames, concat(' ',name(), ' '))]">           <xsl:sort data-type="number"           select="string-length(                      substring-before($porderedNames,                                       concat(' ',name(), ' ')                                       )                                 )"/>              <xsl:value-of select="."/>             <xsl:if test="not(position() = last())">,</xsl:if>         </xsl:for-each>     </xsl:template> </xsl:stylesheet> 

Here the names of the wanted elements and their wanted order are provided in the string parameter $porderedNames, which contains a space-separated list of all wanted names.

When the above transformation is applied on the same XML document, the wanted result is produced:

CollectorAndNumber,Locality,Distribution,Institution 

II. XSLT 2.0:

In XSLT this task is even simpler (again, no special function is necessary):

<xsl:stylesheet version="2.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <xsl:output method="text"/>      <xsl:template match="/*/*">     <xsl:value-of separator="," select=     "(Locality, CollectorAndNumber,      Institution, Distribution,      Note)[text()]" />     </xsl:template> </xsl:stylesheet> 

When this transformation is applied on the same XML document, the same correct result is produced:

Locality,CollectorAndNumber,Institution,Distribution 

Do note that the wanted elements will be produced in any desired order, because we are using the XPath 2.0 sequence type (vs the union in the XSLT 1.0 solution), which by definition contains items in any desired (specified) order.

like image 167
Dimitre Novatchev Avatar answered Oct 10 '22 19:10

Dimitre Novatchev


I would prefer a short call-template to join the node values together. This also works if a node in the middle of your concatenated list, e.g. Institution, is missing:

<xsl:template name="join">     <xsl:param name="list" />     <xsl:param name="separator"/>      <xsl:for-each select="$list">         <xsl:value-of select="." />         <xsl:if test="position() != last()">             <xsl:value-of select="$separator" />         </xsl:if>     </xsl:for-each> </xsl:template> 

Here is a short example how to use it:

Sample input document:

<?xml version="1.0" encoding="utf-8"?> <items>   <item>     <Locality>locality1</Locality>     <CollectorAndNumber>collectorAndNumber1</CollectorAndNumber>     <Distribution>distribution1</Distribution>     <Note>note1</Note>   </item>   <item>     <Locality>locality2</Locality>     <CollectorAndNumber>collectorAndNumber2</CollectorAndNumber>     <Institution>institution2</Institution>     <Distribution>distribution2</Distribution>     <Note>note2</Note>   </item>   <item>     <Locality>locality3</Locality>     <CollectorAndNumber>collectorAndNumber3</CollectorAndNumber>     <Institution>institution3</Institution>     <Distribution>distribution3</Distribution>   </item> </items> 

XSL transformation:

<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="xml" indent="yes"/>    <xsl:template match="/">     <summary>       <xsl:apply-templates />     </summary>   </xsl:template>    <xsl:template match="item">     <item>       <xsl:call-template name="join">         <xsl:with-param name="list" select="Locality | CollectorAndNumber | Institution | Distribution | Note" />         <xsl:with-param name="separator" select="','" />       </xsl:call-template>     </item>   </xsl:template>    <xsl:template name="join">     <xsl:param name="list" />     <xsl:param name="separator"/>      <xsl:for-each select="$list">       <xsl:value-of select="." />       <xsl:if test="position() != last()">         <xsl:value-of select="$separator" />       </xsl:if>     </xsl:for-each>   </xsl:template> </xsl:stylesheet> 

Generated output document:

<?xml version="1.0" encoding="utf-8"?> <summary>   <item>locality1,collectorAndNumber1,distribution1,note1</item>   <item>locality2,collectorAndNumber2,institution2,distribution2,note2</item>   <item>locality3,collectorAndNumber3,institution3,distribution3</item> </summary> 

NB: If you were using XSLT/XPath 2.0 then there would be fn:string-join

fn:string-join**($operand1 as string*, $operand2 as string*) as string 

which could be used as follows:

fn:string-join({Locality, CollectorAndNumber, Distribution, Note}, ",")  
like image 29
Dirk Vollmar Avatar answered Oct 10 '22 21:10

Dirk Vollmar