0

I want to transform my custom XML to csv by using ";" as value delimiter. Here's my XML:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <operators>
        <item>
            <lfbis>1234567</lfbis>
            <name>Stefan Maier</name>
            <company />
            <street>Testweg 7</street>
            <citycode>95131</citycode>
            <city>Hof</city>
            <controlbody>BIKO</controlbody>
            <productdata>
                <item>Rinder</item>
                <item>9</item>
            </productdata>
        </item>
        <item>
            <lfbis>5671234</lfbis>
            <name>Antom Mueller</name>
        <company>Berghof</company>
            <street>Testweg 8</street>
            <citycode>95111</citycode>
            <city>Bamberg</city>
            <controlbody>BIKO</controlbody>
            <productdata>
                <item>Rinder</item>
                <item>9</item>
            </productdata>
        </item>
    </operators>
</root> 

I have managed so far (using the stackoverflow thread XML to CSV Using XSLT, especially using the code wrote by @Tomalak in his answer ...a version with configurable parameters that you can set programmatically) to use the following xslt transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" encoding="utf-8" />

  <xsl:param name="delim" select="';'" />
  <xsl:param name="quote" select="'&quot;'" />
  <xsl:param name="break" select="'&#xA;'" />

  <xsl:template match="/">
    <xsl:apply-templates select="root/operators/item" />
  </xsl:template>

  <xsl:template match="item">
    <xsl:apply-templates />
    <!--<xsl:if test="following-sibling::*">-->
      <xsl:value-of select="concat($delim, $break)" />
    <!--</xsl:if>-->
  </xsl:template>

  <xsl:template match="*">
    <xsl:value-of select="normalize-space()" />
    <xsl:if test="following-sibling::*">
      <xsl:value-of select="$delim" />
    </xsl:if>
  </xsl:template>

  <xsl:template match="text()" />

</xsl:stylesheet>

... and I am getting the following result using xsltproc:

1234567;Stefan Maier;;Testweg 7;95131;Hof;BIKO;Rinder 9;
5671234;Antom Mueller;Berghof;Testweg 8;95111;Bamberg;BIKO;Rinder 9;

Now, what I am trying to achieve is that the item subelements of the productdata element be treated also as values in the csv result. So I need a ";" instead of " " (space) between the values Rinder and 9, such as my csv would look like:

1234567;Stefan Maier;;Testweg 7;95131;Hof;BIKO;Rinder;9;
5671234;Antom Mueller;Berghof;Testweg 8;95111;Bamberg;BIKO;Rinder;9;
Community
  • 1
  • 1
  • 2
    If you're going to use other people's code, please at least credit them instead of saying that you wrote it. You have your XSLT code from here http://stackoverflow.com/a/365372/18771. – Tomalak Mar 19 '16 at 23:57
  • Yes, sorry for that, I of course used the stackoverflow thread to write the xlst stylesheet. I apologise to the people in that thread and especially to the author of the stylesheet for not mentioning him in my question. I will be more careful in the future posts. – Homorozeanu George Mar 20 '16 at 07:29

2 Answers2

2

One simple way would be to change

<xsl:template match="*">

to

<xsl:template match="*[not(*)]">

which matches elements that don't have a child element. The effect would be that when apply-templates hits the productData element, there is no matching template rule, so the default kicks in, which is to apply templates to the children.

However this requires a change in strategy for handling delimiters. In your case it's fairly easy, because you want a semicolon after every item including the last: so just add the semicolon unconditionally after outputting each item, and don't add another semicolon at the end of the row.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
1

Here is an adaption of Tomalak's code

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

  <xsl:output method="text" encoding="utf-8" />

  <xsl:param name="delim" select="';'" />
  <xsl:param name="quote" select="'&quot;'" />
  <xsl:param name="break" select="'&#xA;'" />

  <xsl:template match="/">
    <xsl:apply-templates select="root/operators/item" />
  </xsl:template>

  <xsl:template match="root/operators/item">
    <xsl:apply-templates select=".//*[not(*)]"/>
    <xsl:value-of select="$break" />
  </xsl:template>

  <xsl:template match="*">
    <xsl:if test="position() > 1">
      <xsl:value-of select="$delim"/>
    </xsl:if>
    <xsl:value-of select="normalize-space()" />
  </xsl:template>

  <xsl:template match="text()" />

</xsl:stylesheet>

As you have two types of item elements I needed one rather explicit match pattern.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Thanks Martin, I used your adaptation of @Tomalak 's code and the xml get's transformed the way I wanted. I added `select="concat($delim,$break)"` in the template matching _root/operators/item_ elements in order to also put a semicolon after the last value in each line. – Homorozeanu George Mar 20 '16 at 10:31