以下是说明执行通用样式表以进行此类转换的脚本。样式表所做的唯一假设是元素<datarows>
。给出的结构意味着根据请求的结果使用子元素:
数据:
T:\ftemp>type xml2csv.xml
<Ctry>
<datarow>
<CtryName>Ctry1</CtryName>
<CtryID>12361</CtryID>
<State>
<datarow>
<StateName>State1</StateName>
<StateID>12361</StateID>
<City>
<datarow>
<CityName>City1</CityName>
<CityID>12361</CityID>
</datarow>
</City>
</datarow>
<datarow>
<StateName>State2</StateName>
<StateID>12361</StateID>
</datarow>
</State>
</datarow>
</Ctry>
执行:
T:\ftemp>call xslt2 xml2csv.xml xml2csv.xsl
CtryName,CtryID,StateName,StateID,CityName,CityID
Ctry1,12361,State1,12361,City1,12361
Ctry1,12361,State2,12361
样式表:
T:\ftemp>type xml2csv.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:variable name="fields"
select="distinct-values(//datarow/*[not(*)]/name(.))"/>
<xsl:template match="/">
<!--header row-->
<xsl:value-of select="$fields" separator=","/>
<!--body-->
<xsl:apply-templates select="*"/>
<!--final line terminator-->
<xsl:text>
</xsl:text>
</xsl:template>
<!--elements only process elements, not text-->
<xsl:template match="*">
<xsl:apply-templates select="*"/>
</xsl:template>
<!--these elements are CSV fields-->
<xsl:template match="datarow/*[not(*)]">
<!--replicate ancestors if necessary-->
<xsl:if test="position()=1 and ../preceding-sibling::datarow">
<xsl:for-each select="ancestor::datarow[position()>1]/*[not(*)]">
<xsl:call-template name="doThisField"/>
</xsl:for-each>
</xsl:if>
<xsl:call-template name="doThisField"/>
</xsl:template>
<!--put out a field ending the previous field and escaping content-->
<xsl:template name="doThisField">
<xsl:choose>
<xsl:when test="name(.)=$fields[1]">
<!--previous line terminator-->
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<!--previous field terminator-->
<xsl:text>,</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!--field value escaped per RFC4180-->
<xsl:choose>
<xsl:when test="contains(.,'"') or
contains(.,',') or
contains(.,'
')">
<xsl:text>"</xsl:text>
<xsl:value-of select="replace(.,'"','""')"/>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
请注意,上面的代码根据 RFC4180 对各个字段进行转义。
我的个人资料有一个指向我的网站的链接,您可以在其中找到免费的 XML 资源目录,包括用于将 RFC4180 CSV 文件转换为 XML 文件的 XSLT 样式表。
这是原始发布者所要求的答案的 XSLT 1.0 解决方案:
t:\ftemp>type xml2csv1.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:variable name="firstFieldName"
select="name((//datarow/*[not(*)])[1])"/>
<xsl:key name="names" match="datarow/*[not(*)]" use="name(.)"/>
<xsl:template match="/">
<!--header row-->
<xsl:for-each select="//datarow/*[not(*)]
[generate-id(.)=
generate-id(key('names',name(.))[1])]">
<xsl:if test="position()>1">,</xsl:if>
<xsl:value-of select="name(.)"/>
</xsl:for-each>
<!--body-->
<xsl:apply-templates select="*"/>
<!--final line terminator-->
<xsl:text>
</xsl:text>
</xsl:template>
<!--elements only process elements, not text-->
<xsl:template match="*">
<xsl:apply-templates select="*"/>
</xsl:template>
<!--these elements are CSV fields-->
<xsl:template match="datarow/*[not(*)]">
<!--replicate ancestors if necessary-->
<xsl:if test="position()=1 and ../preceding-sibling::datarow">
<xsl:for-each select="ancestor::datarow[position()>1]/*[not(*)]">
<xsl:call-template name="doThisField"/>
</xsl:for-each>
</xsl:if>
<xsl:call-template name="doThisField"/>
</xsl:template>
<!--put out a field ending the previous field and escaping content-->
<xsl:template name="doThisField">
<xsl:choose>
<xsl:when test="name(.)=$firstFieldName">
<!--previous line terminator-->
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<!--previous field terminator-->
<xsl:text>,</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!--field value escaped per RFC4180-->
<xsl:choose>
<xsl:when test="contains(.,'"') or
contains(.,',') or
contains(.,'
')">
<xsl:text>"</xsl:text>
<xsl:call-template name="escapeQuote"/>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--escape a double quote in the current node value with two double quotes-->
<xsl:template name="escapeQuote">
<xsl:param name="rest" select="."/>
<xsl:choose>
<xsl:when test="contains($rest,'"')">
<xsl:value-of select="substring-before($rest,'"')"/>
<xsl:text>""</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="rest" select="substring-after($rest,'"')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$rest"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>