Abiodun Adeoye
Abiodun Adeoye

Reputation: 1095

xml conversion to csv with xslt

the following codes changes xml to csv.

when it is applied to the sample data below,

 <Addy>
<Row>
<L>1</L>
<LD>Dwelling</LD>
<Th>Passage</Th>
</Row>
</ADDY>

it produces a csv file in this formatincludng the column names

 L,LD,Th
 1,Dwelling,Passage

the idea is to do away with the column names and add a comma (,) at the end of the last value such that the expected result is

1,Dwelling,passage,

Upvotes: 1

Views: 308

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243569

Just modify the template matching / so that now it should be:

   <xsl:template match="/">
    <xsl:apply-templates select="*/*" mode="row"/>
   </xsl:template>

And if you really want a trailing comma at the end of each line produced, then replace the first occurence of:

<xsl:if test="position() != last()">,</xsl:if>

with:

<xsl:text>&#10;</xsl:text>

When only these minimal modifications are performed, the provided code becomes:

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

   <xsl:key name="field" match="/*/*/*" use="name()" />
   <xsl:output method="text"/>

   <xsl:template match="/">
    <xsl:apply-templates select="*/*" mode="row"/>
   </xsl:template>

    <xsl:template match="*" mode="row">
    <xsl:variable name="row" select="*" />
    <xsl:for-each select="/*/*/*[generate-id() = generate-id(key('field',name())[1])]">
        <xsl:variable name="name" select="name()" />
        <xsl:apply-templates select="$row[name()=$name]" mode="data" />
        <xsl:text>,</xsl:text>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
    </xsl:template>

    <xsl:template match="*" mode="data">
    <xsl:choose>
        <xsl:when test="contains(text(),',')">
            <xsl:text>&quot;</xsl:text>
            <xsl:call-template name="doublequotes">
                <xsl:with-param name="text" select="text()" />
            </xsl:call-template>
            <xsl:text>&quot;</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="." />
        </xsl:otherwise>
    </xsl:choose>
    <xsl:if test="position() != last()">,</xsl:if>
    </xsl:template>

    <xsl:template name="doublequotes">
    <xsl:param name="text" />
    <xsl:choose>
        <xsl:when test="contains($text,'&quot;')">
            <xsl:value-of select="concat(substring-before($text,'&quot;'),'&quot;&quot;')" />
            <xsl:call-template name="doublequotes">
                <xsl:with-param name="text" select="substring-after($text,'&quot;')" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text" />
        </xsl:otherwise>
    </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document (note the different order of the children in the two Row elements):

<Addy>
  <Row>
    <L>1</L>
    <LD>Dwelling</LD>
    <Th>Passage</Th>
  </Row>
  <Row>
    <Th>Bar</Th>
    <LD>Foo</LD>
    <L>2</L>
  </Row>
</Addy>

the wanted, correct result is produced:

1,Dwelling,Passage,
2,Foo,Bar,

Upvotes: 0

Ian Roberts
Ian Roberts

Reputation: 122414

If the elements are in the same order for every Row then you don't need any of the complex Muenchian Grouping, template modes, etc. Just a very simple stylesheet would suffice:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     version="1.0">
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>

  <xsl:template match="Row">
    <xsl:apply-templates select="*" />
    <xsl:text>&#10;</xsl:text>
  </xsl:template>

  <xsl:template match="Row/*">
    <xsl:value-of select="." />
    <xsl:text>,</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Running this on the following input:

<Addy>
  <Row>
    <L>1</L>
    <LD>Dwelling</LD>
    <Th>Passage</Th>
  </Row>
  <Row>
    <L>2</L>
    <LD>Foo</LD>
    <Th>Bar</Th>
  </Row>
</Addy>

produces the output you are after, including the trailing comma after the last column value:

1,Dwelling,Passage,
2,Foo,Bar,

Upvotes: 1

Michael Krelin - hacker
Michael Krelin - hacker

Reputation: 143259

Remove

<xsl:for-each select="*/*/*[generate-id() = generate-id(key('field',name())[1])]">
    <xsl:value-of select="name()" />
    <xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>
<xsl:text>&#10;</xsl:text>

from the template for / and add comma:

<xsl:text>,&#10;</xsl:text>

in the template for * in row mode.

Upvotes: 1

Related Questions