通用XML到CSV转换
我正在尝试将动态XML转换为CSV。我搜索了各种选项来实现这一点,但没有找到合适的答案 XML的结构是动态的——它可以是产品数据、地理数据或任何类似的东西。因此,我不能使用预定义的XSL或castor转换 标记名应构成CSV的标题。 例如:通用XML到CSV转换,xml,xslt,csv,export-to-csv,Xml,Xslt,Csv,Export To Csv,我正在尝试将动态XML转换为CSV。我搜索了各种选项来实现这一点,但没有找到合适的答案 XML的结构是动态的——它可以是产品数据、地理数据或任何类似的东西。因此,我不能使用预定义的XSL或castor转换 标记名应构成CSV的标题。 例如: <Ctry> <datarow> <CtryName>Ctry1</CtryName> <CtryID>12361</CtryID> <State&
<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>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>
T:\ftemp>type xml2csv.xsl
;
;
,
"
"
请注意,上述代码根据RFC4180转义各个字段
我的个人资料有一个指向我的网站的链接,在那里你可以找到一个免费的XML资源目录,其中包括一个XSLT样式表,用于将RFC4180 CSV文件转换为XML文件
根据原始海报的要求,这是答案的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>
t:\ftemp>type xml2csv1.xsl
,
;
;
,
"
"
""
下面是一份演示执行通用样式表以进行此类转换的转录本。样式表所做的唯一假设是元素
。给定的结构意味着根据请求的结果使用子元素:
数据:
样式表:
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>
T:\ftemp>type xml2csv.xsl
;
;
,
"
"
请注意,上述代码根据RFC4180转义各个字段
我的个人资料有一个指向我的网站的链接,在那里你可以找到一个免费的XML资源目录,其中包括一个XSLT样式表,用于将RFC4180 CSV文件转换为XML文件
根据原始海报的要求,这是答案的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>
t:\ftemp>type xml2csv1.xsl
,
;
;
,
"
"
""
听起来你在要求一个程序读懂你对XML结构的想法。-1你写过任何可能对你有帮助的代码吗?如果你只是想让别人为你写代码,那么在一个自由职业者的网站上你可能会有更好的运气。我已经编写了一个基于SAX的解析器,但它不能完全解决这个问题。因此,我一直在寻找任何一个java库,它可以很容易地做到这一点。我有XML的XSD。基于此,我正试图编写一个通用XSLT,将其转换为CSV。任何编写通用XSLT的指针都会有所帮助。我刚刚为这个问题编写了一个通用XSLT解决方案。我不明白为什么它必须被标记为XSLT的非主题。我还在我的网站上免费提供了一些从CSV到XML的开发人员资源。这里发布的原始需求似乎非常通用:我唯一的假设是
元素定义了要在CSV中捕获的字段集。我对元素名没有其他假设,所以这是一个非常通用的解决方案。为什么每个人都认为这不仅仅是另一项XSLT样式表编写任务?听起来你是在要求一个程序读懂你对XML结构的想法。-1你写过任何可能对你有帮助的代码吗?如果你只是想让别人为你写代码,那么在一个自由职业者的网站上你可能会有更好的运气。我已经编写了一个基于SAX的解析器,但它不能完全解决这个问题。因此,我一直在寻找任何一个java库,它可以很容易地做到这一点。我有XML的XSD。基于此,我正试图编写一个通用XSLT,将其转换为CSV。任何编写通用XSLT的指针都会有所帮助。我刚刚为这个问题编写了一个通用XSLT解决方案。我不明白为什么它必须被标记为XSLT的非主题。我还在我的网站上免费提供了一些从CSV到XML的开发人员资源。这里发布的原始需求似乎非常通用:我唯一的假设是
元素定义了要在CSV中捕获的字段集。我对元素名没有其他假设,所以这是一个非常通用的解决方案。为什么每个人都认为这不仅仅是另一个XSLT样式表编写任务?嗨,肯,谢谢你的回复。。。我试图运行我的java程序,但出现了此异常。。“发生异常:javax.xml.transform.TransformerConfiguration异常:javax.xml.transform.TransformerConfiguration异常:javax.xml.transform.TransformerException:javax.xml.transform.TransformerException:找不到函数:不同的值”。。。我尝试了不同版本的xalan.jar。您能告诉我如何解决这个问题吗?您所谓的“问题”只是您没有在原始问题中指出您仅限于XSLT 1.0。我使用XSLT2.0为您提供了答案。由于我在用户界面中看不到发布替代答案的方法,因此我编辑了此答案,以包含解决方案的XSLT1.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>