I have this xslt to convert a csv to xml, works fine, except the tag is the same for all columns. I need it to increment like this
It looks as though csvtoxml
is being called with a large string and it recursively works it's way through that string. position()
won't work in this case because you're not working with a set of nodes.
Instead you might be able to achieve what you're after with a counting param:
<xsl:template name="csvtoxml">
<!-- import $StringToTransform-->
<xsl:param name="StringToTransform" select="''"/>
<xsl:param name="ColumnNum" select="1"/>
<xsl:choose>
<!-- string contains linefeed-->
<xsl:when test="contains($StringToTransform,',')">
<!-- Get everything up to the first carriage return-->
<xsl:element name="{concat('column', $ColumnNum)}">
<xsl:value-of select="substring-before($StringToTransform,',')"/>
</xsl:element>
<!-- repeat for the remainder of the original string-->
<xsl:call-template name="csvtoxml">
<xsl:with-param name="StringToTransform" select="substring-after($StringToTransform,',')" />
<xsl:with-param name="ColumnNum" select="$ColumnNum + 1" />
</xsl:call-template>
</xsl:when>
<!-- string does not contain newline, so just output it-->
<xsl:otherwise>
<xsl:element name="{concat('column', $ColumnNum)}">
<xsl:value-of select="$StringToTransform" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Although the OP is probably looking for an XSLT 1.0 solution, for interest here is an XSLT 2.0 solution. This solution requires you to download my csv-to-xml library style-sheet from here, which is discussed in this blog entry.
With this as the input document, referred to in the style-sheet with uri 'gangt.csv' (use a parameter or adapt as you require)...
3779490,916705,CS,60,34.89,Sauce/Cholula
5918104,918958,CS,6,20.63,Pasta/Fresh/Cavatelli/6#/Frozen
5064774,920723,CS,10,45.5,Cheese/Oaxaca
3422752,925230,EA,8,69.6,Chipotle/Powder/Ground
5955640,BB171,CS,30,50.7,Butter/Unsalted
5295326,BC110005,CS,6000,54.95,Oil/Olive/Finishing
...this XSLT 2.0 style-sheet...
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:clib="http://www.seanbdurkin.id.au/xslt/csv-to-xml.xslt"
xmlns:xcsv="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
exclude-result-prefixes="xsl clib xcsv">
<xsl:import href="csv-to-xml.xslt" />
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/" >
<data-set>
<xsl:apply-templates select="clib:csv-to-xml('gangt.csv')/xcsv:row" />
</data-set>
</xsl:template>
<xsl:template match="xcsv:row">
<row>
<xsl:apply-templates select="xcsv:value" />
</row>
</xsl:template>
<xsl:template match="xcsv:value">
<xsl:element name="column{position()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
...produces this output document...
<data-set>
<row>
<column1>3779490</column1>
<column2>916705</column2>
<column3>CS</column3>
<column4>60</column4>
<column5>34.89</column5>
<column6>Sauce/Cholula</column6>
</row>
<row>
<column1>5918104</column1>
<column2>918958</column2>
<column3>CS</column3>
<column4>6</column4>
<column5>20.63</column5>
<column6>Pasta/Fresh/Cavatelli/6#/Frozen</column6>
</row>
<row>
<column1>5064774</column1>
<column2>920723</column2>
<column3>CS</column3>
<column4>10</column4>
<column5>45.5</column5>
<column6>Cheese/Oaxaca</column6>
</row>
<row>
<column1>3422752</column1>
<column2>925230</column2>
<column3>EA</column3>
<column4>8</column4>
<column5>69.6</column5>
<column6>Chipotle/Powder/Ground</column6>
</row>
<row>
<column1>5955640</column1>
<column2>BB171</column2>
<column3>CS</column3>
<column4>30</column4>
<column5>50.7</column5>
<column6>Butter/Unsalted</column6>
</row>
<row>
<column1>5295326</column1>
<column2>BC110005</column2>
<column3>CS</column3>
<column4>6000</column4>
<column5>54.95</column5>
<column6>Oil/Olive/Finishing</column6>
</row>
</data-set>
I'm not sure why you're opting for XSLT in this case. It's not the first choice for string handling operations such as this, especially since your source data isn't XML, you're just wrapping it in a node to make it XML. A more obvious approach would be to handle the CSV via something like PHP.
In any case, to answer the question, if you have access to EXSLT (this is often available with XSLT processors, e.g. PHP's):
Runnable demo here (see output source).
<!-- break into rows -->
<xsl:variable name='rows' select='str:split(root, "
")' />
<!-- root - kick things off -->
<xsl:template match='/'>
<root>
<xsl:apply-templates select='$rows' mode='row' />
</root>
</xsl:template>
<!-- rows -->
<xsl:template match='token' mode='row'>
<xsl:variable name='cols' select='str:split(., ",")' />
<row>
<xsl:apply-templates select='$cols' mode='col' />
</row>
</xsl:template>
<!-- columns -->
<xsl:template match='token' mode='col'>
<xsl:element name='col{position()}'>
<xsl:value-of select='.' />
</xsl:element>
</xsl:template>
Although XSLT is capable of processing non-XML content, it seems to not be intended as a general-purpose text transformation tool. As a result, most of the tools available to you are about manipulating XML infoset constructs like elements and attributes. There is a little support for text strings, but not much. So position()
is defined in terms of input nodes.
http://www.w3.org/TR/xpath/#section-Node-Set-Functions:
The position function returns a number equal to the context position from the expression evaluation context.
http://www.w3.org/TR/xslt#section-Expressions:
the context position comes from the position of the current node in the current node list; the first position is 1
Since your input is just a text string, you are always in position 1. I can think of only one way to do this with XSLT. Transform twice. The first transform gives you the basic structure with un-numbered column elements. The second transform numbers the column elements. Because you are selecting nodes from an XML document the second time, position()
should have the values you want.