This CsvEscape function is XSLT 1.0 and escapes column values ,, ", and newlines like RFC 4180 or Excel. It makes use of the fact that you can recursively call XSLT templates:
- The template
EscapeQuotes replaces all double quotes with 2 double quotes, recursively from the start of the string.
- The template
CsvEscape checks if the text contains a comma or double quote, and if so surrounds the whole string with a pair of double quotes and calls EscapeQuotes for the string.
Example usage: xsltproc xmltocsv.xslt file.xml > file.csv
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template name="EscapeQuotes">
<xsl:param name="value"/>
<xsl:choose>
<xsl:when test="contains($value,'"')">
<xsl:value-of select="substring-before($value,'"')"/>
<xsl:text>""</xsl:text>
<xsl:call-template name="EscapeQuotes">
<xsl:with-param name="value" select="substring-after($value,'"')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="CsvEscape">
<xsl:param name="value"/>
<xsl:choose>
<xsl:when test="contains($value,',')">
<xsl:text>"</xsl:text>
<xsl:call-template name="EscapeQuotes">
<xsl:with-param name="value" select="$value"/>
</xsl:call-template>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:when test="contains($value,'
')">
<xsl:text>"</xsl:text>
<xsl:call-template name="EscapeQuotes">
<xsl:with-param name="value" select="$value"/>
</xsl:call-template>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:when test="contains($value,'"')">
<xsl:text>"</xsl:text>
<xsl:call-template name="EscapeQuotes">
<xsl:with-param name="value" select="$value"/>
</xsl:call-template>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<xsl:text>project,name,language,owner,state,startDate</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="projects/project">
<xsl:call-template name="CsvEscape"><xsl:with-param name="value" select="normalize-space(name)"/></xsl:call-template>
<xsl:text>,</xsl:text>
<xsl:call-template name="CsvEscape"><xsl:with-param name="value" select="normalize-space(language)"/></xsl:call-template>
<xsl:text>,</xsl:text>
<xsl:call-template name="CsvEscape"><xsl:with-param name="value" select="normalize-space(owner)"/></xsl:call-template>
<xsl:text>,</xsl:text>
<xsl:call-template name="CsvEscape"><xsl:with-param name="value" select="normalize-space(state)"/></xsl:call-template>
<xsl:text>,</xsl:text>
<xsl:call-template name="CsvEscape"><xsl:with-param name="value" select="normalize-space(startDate)"/></xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>