使用 xslt 样式表将 xml 文档转换为逗号分隔 (CSV) 文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3056579/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
convert xml document to comma delimited (CSV) file using xslt stylesheet
提问by Brad H_KC
I need some assistance converting an xml document to a CSV file using an xslt stylesheet. I am trying to use the following xsl and I can't seem to get it right. I want my comma delimited file to include column headings, followed by the data. My biggest issues are removing the final comma after the last item and inserting a carriage return so each group of data appears on a separate line. I have been using XML Notepad.
我需要一些帮助来使用 xslt 样式表将 xml 文档转换为 CSV 文件。我正在尝试使用以下 xsl,但似乎无法正确使用。我希望我的逗号分隔文件包含列标题,然后是数据。我最大的问题是删除最后一项后的最后一个逗号并插入回车符,以便每组数据出现在单独的行上。我一直在使用 XML 记事本。
<xsl:template match="/">
<xsl:element name="table">
<xsl:apply-templates select="/*/*[1]" mode="header" />
<xsl:apply-templates select="/*/*" mode="row" />
</xsl:element>
</xsl:template>
<xsl:template match="*" mode="header">
<xsl:element name="tr">
<xsl:apply-templates select="./*" mode="column" />
</xsl:element>
</xsl:template>
<xsl:template match="*" mode="row">
<xsl:element name="tr">
<xsl:apply-templates select="./*" mode="node" />
</xsl:element>
</xsl:template>
<xsl:template match="*" mode="column">
<xsl:element name="th">
<xsl:value-of select="translate(name(.),'qwertyuiopasdfghjklzxcvbnm_','QWERTYUIOPASDFGHJKLZXCVBNM ')" />
</xsl:element>,
</xsl:template>
<xsl:template match="*" mode="node">
<xsl:element name="td">
<xsl:value-of select="." />
</xsl:element>,
</xsl:template>
回答by Flynn1179
I use this to simple XSLT to convert XML to CSV; it assumes all child nodes of the root node are to be rows in the CSV, taking the element names of the first child of the root to be field names.
我用它来简单的 XSLT 将 XML 转换为 CSV;它假设根节点的所有子节点都是 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"/>
<xsl:template match="/">
<xsl:for-each select="*/*[1]/*">
<xsl:value-of select="name()" />
<xsl:if test="not(position() = last())">,</xsl:if>
</xsl:for-each>
<xsl:text> </xsl:text>
<xsl:apply-templates select="*/*" mode="row"/>
</xsl:template>
<xsl:template match="*" mode="row">
<xsl:apply-templates select="*" mode="data" />
<xsl:text> </xsl:text>
</xsl:template>
<xsl:template match="*" mode="data">
<xsl:choose>
<xsl:when test="contains(text(),',')">
<xsl:text>"</xsl:text>
<xsl:call-template name="doublequotes">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
<xsl:text>"</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,'"')">
<xsl:value-of select="concat(substring-before($text,'"'),'""')" />
<xsl:call-template name="doublequotes">
<xsl:with-param name="text" select="substring-after($text,'"')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
So this XML:
所以这个 XML:
<csv>
<row>
<field1>foo</field1>
<field2>ba"r</field2>
</row>
<row>
<field1>foo,2</field1>
<field2>bar,"2</field2>
</row>
</csv>
Converts to:
转换为:
field1,field2
foo,ba"r
"foo,2","bar,""2"
Not sure if this helps though, it depends how your XML is laid out.
不确定这是否有帮助,这取决于您的 XML 的布局方式。
Edit: Here's a more thorough transform:
编辑:这是一个更彻底的转换:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="field" match="/*/*/*" use="name()" />
<xsl:output method="text"/>
<xsl:template match="/">
<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> </xsl:text>
<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:if test="position() != last()">,</xsl:if>
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:template>
<xsl:template match="*" mode="data">
<xsl:choose>
<xsl:when test="contains(text(),',')">
<xsl:text>"</xsl:text>
<xsl:call-template name="doublequotes">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
<xsl:text>"</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,'"')">
<xsl:value-of select="concat(substring-before($text,'"'),'""')" />
<xsl:call-template name="doublequotes">
<xsl:with-param name="text" select="substring-after($text,'"')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This one will create a column in your CSV for all tag names that exist in all 'rows', and populate the appropriate column in each row.
这将在您的 CSV 中为所有“行”中存在的所有标签名称创建一列,并在每一行中填充相应的列。
回答by ShoeLace
I was using the "more thorough transform" of the xslt from @flynn1179 's answer.. but found that it was taking an extraordinary amount of time when the number of rows increased. (this is using oracle dbms_xmlgen xslt transform )
我正在使用来自@flynn1179 的答案的 xslt 的“更彻底的转换”......但发现当行数增加时它花费了大量的时间。(这是使用 oracle dbms_xmlgen xslt 转换)
I found that because I know what my 2 levels of data are called (ROWSET and ROW in this case) i was able to optimize the xslt and gain a significant performance increase.
我发现因为我知道我的 2 个级别的数据称为什么(在这种情况下为 ROWSET 和 ROW),所以我能够优化 xslt 并获得显着的性能提升。
My modified version (specifically aimed at oracle's dbms_xmlgen package) is below. it also includes bits from other csv transforms I found around the internet.
我的修改版本(专门针对 oracle 的 dbms_xmlgen 包)如下。它还包括我在互联网上找到的其他 csv 转换的位。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<!-- some variables for unprintable charcaters -->
<xsl:variable name="CRLF">
<xsl:text> </xsl:text>
</xsl:variable>
<xsl:variable name="CR">
<xsl:text> </xsl:text>
</xsl:variable>
<xsl:variable name="LF">
<xsl:text> </xsl:text>
</xsl:variable>
<xsl:variable name="apos">'</xsl:variable>
<xsl:template match="/ROWSET">
<xsl:for-each select="ROW[1]/*">
<xsl:value-of select="local-name()" />
<xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>
<xsl:value-of select="$LF"/>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="ROW">
<xsl:apply-templates />
<xsl:value-of select="$LF"/>
</xsl:template>
<xsl:template match="ROW/*">
<xsl:choose>
<xsl:when test="contains( text(), ',' ) or
contains( text(), $apos ) or
contains( text(), $CRLF ) or
contains( text(), $CR ) or
contains( text(), $LF )">
<!-- Field contains a comma, apostrophe and/or a linefeed, so quote -->
<xsl:text>"</xsl:text>
<xsl:call-template name="doublequotes">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
<xsl:text>"</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,'"')">
<!-- recursive call -->
<xsl:value-of select="concat(substring-before($text,'"'),'""')" />
<xsl:call-template name="doublequotes">
<xsl:with-param name="text" select="substring-after($text,'"')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

