将 XML 转换为 CSV,每个条目换行
Converting XML to CSV, each entry on new line
我正在使用 XSLT 将 XML 转换为 CSV,但是当将 XSL 应用于以下内容时 XML
<?xml version="1.0" encoding="UTF-8"?>
<Report>
<Dates>
<Current>02/02/2014</Current>
<Previous>02/02/2013</Previous>
</Dates>
<Entry>
<Category>1</Category>
<Data1>10.00</Data1>
<Data2>20.00</Data2>
</Entry>
<Entry>
<Category>2</Category>
<Data1>30.00</Data1>
<Data2>40.00</Data2>
</Entry>
<Entry>
<Category>3</Category>
<Data1>50.00</Data1>
<Data2>60.00</Data2>
</Entry>
<Entry>
<Category>4</Category>
<Data1>70.00</Data1>
<Data2>80.00</Data2>
</Entry>
<Totals>
<TCurrent>160.00</TCurrent>
<TPrevious>200.00</TPrevious>
</Totals>
</Report>
我正在使用的 XSL 文件如下:
<xsl:for-each select="Report/Dates/child::*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
Number, Data1 , Data2
<xsl:for-each select="Report/Entry/child::*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
<xsl:for-each select="Report/Totals/child::*">
<xsl:if test="position() = 1">Totals,</xsl:if>
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
我收到一条包含所有条目的长线,例如
02/02/2014, 02/02/2013
1, 10.00, 20.00, 2, 30.00, 40.00, 3, 50.00, 60.00, 4, 70.00, 80.00
Totals, 160.00, 200.00
问题:如何在新行上获取每个条目,我需要像
这样的结果
02/02/2014, 02/02/2013
1, 10.00, 20.00
2, 30.00, 40.00
3, 50.00, 60.00
4, 70.00, 80.00
Totals, 160.00, 200.00
<xsl:for-each select="Report/Entry/child::*">
所以对于 all 的所有子项,条目项
每个条目需要一节车厢 return,因此:
<xsl:for-each select="Report/Entry">
<xsl:for-each select="*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
</xsl:for-each>
这样你就'breaking up'设置成词条然后在每个
之后放置CR
NB child::为默认轴,即Report/Totals/child::*
<=> Report/Totals/*
与所有编程语言一样,存在一些有效的编程风格,但被认为是解决问题的糟糕或不规范的方法。在代码中仅匹配 /
并大量使用 for-each
循环就是这种情况。
因此,我认为您问题的公认答案只是触及了问题的表面,并没有真正带来改进。如果您可以使用 XSLT 2.0,我会建议一种更简单的方法。
XSLT 样式表 (2.0)
在同一模板中匹配 Report
的所有子元素,使用 |
。然后,string-join()
它们的所有子元素,用 ,
分隔内容。使用 XPath 2.0 条件表达式来决定是否输出回车 return.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Dates|Entry|Totals">
<xsl:if test="self::Totals">
<xsl:text>Totals, </xsl:text>
</xsl:if>
<xsl:value-of select="string-join(*,', ')"/>
<xsl:value-of select="if (position() != last()) then '
' else ''"/>
</xsl:template>
</xsl:transform>
文本输出
如您所见,输出正是您所要求的。
02/02/2014, 02/02/2013
1, 10.00, 20.00
2, 30.00, 40.00
3, 50.00, 60.00
4, 70.00, 80.00
Totals, 160.00, 200.00
此外,也许更重要的是,为什么您的原始代码不起作用?它看起来像这样:
<xsl:for-each select="Report/Entry/child::*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
问题是 for-each
遍历一系列节点,在本例中是 Entry
元素的所有子节点。在这个新序列中,只有 一个 最后一个元素,而不是您预期的几个。所以,这个 if
:
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
只触发一次,针对最后一个 Entry
元素的最后一个子元素。这就是为什么所有内容都在同一行,而行尾只有一个
。
I thought that Report/Entry/child::*
is the same as <xsl:for-each select="Report/Entry"> <xsl:for-each select="*">
是的,它们针对相同的节点,但 position()
的行为不同。
我正在使用 XSLT 将 XML 转换为 CSV,但是当将 XSL 应用于以下内容时 XML
<?xml version="1.0" encoding="UTF-8"?>
<Report>
<Dates>
<Current>02/02/2014</Current>
<Previous>02/02/2013</Previous>
</Dates>
<Entry>
<Category>1</Category>
<Data1>10.00</Data1>
<Data2>20.00</Data2>
</Entry>
<Entry>
<Category>2</Category>
<Data1>30.00</Data1>
<Data2>40.00</Data2>
</Entry>
<Entry>
<Category>3</Category>
<Data1>50.00</Data1>
<Data2>60.00</Data2>
</Entry>
<Entry>
<Category>4</Category>
<Data1>70.00</Data1>
<Data2>80.00</Data2>
</Entry>
<Totals>
<TCurrent>160.00</TCurrent>
<TPrevious>200.00</TPrevious>
</Totals>
</Report>
我正在使用的 XSL 文件如下:
<xsl:for-each select="Report/Dates/child::*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
Number, Data1 , Data2
<xsl:for-each select="Report/Entry/child::*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
<xsl:for-each select="Report/Totals/child::*">
<xsl:if test="position() = 1">Totals,</xsl:if>
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
我收到一条包含所有条目的长线,例如
02/02/2014, 02/02/2013
1, 10.00, 20.00, 2, 30.00, 40.00, 3, 50.00, 60.00, 4, 70.00, 80.00
Totals, 160.00, 200.00
问题:如何在新行上获取每个条目,我需要像
这样的结果02/02/2014, 02/02/2013
1, 10.00, 20.00
2, 30.00, 40.00
3, 50.00, 60.00
4, 70.00, 80.00
Totals, 160.00, 200.00
<xsl:for-each select="Report/Entry/child::*">
所以对于 all 的所有子项,条目项
每个条目需要一节车厢 return,因此:
<xsl:for-each select="Report/Entry">
<xsl:for-each select="*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
</xsl:for-each>
这样你就'breaking up'设置成词条然后在每个
之后放置CRNB child::为默认轴,即Report/Totals/child::*
<=> Report/Totals/*
与所有编程语言一样,存在一些有效的编程风格,但被认为是解决问题的糟糕或不规范的方法。在代码中仅匹配 /
并大量使用 for-each
循环就是这种情况。
因此,我认为您问题的公认答案只是触及了问题的表面,并没有真正带来改进。如果您可以使用 XSLT 2.0,我会建议一种更简单的方法。
XSLT 样式表 (2.0)
在同一模板中匹配 Report
的所有子元素,使用 |
。然后,string-join()
它们的所有子元素,用 ,
分隔内容。使用 XPath 2.0 条件表达式来决定是否输出回车 return.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Dates|Entry|Totals">
<xsl:if test="self::Totals">
<xsl:text>Totals, </xsl:text>
</xsl:if>
<xsl:value-of select="string-join(*,', ')"/>
<xsl:value-of select="if (position() != last()) then '
' else ''"/>
</xsl:template>
</xsl:transform>
文本输出
如您所见,输出正是您所要求的。
02/02/2014, 02/02/2013
1, 10.00, 20.00
2, 30.00, 40.00
3, 50.00, 60.00
4, 70.00, 80.00
Totals, 160.00, 200.00
此外,也许更重要的是,为什么您的原始代码不起作用?它看起来像这样:
<xsl:for-each select="Report/Entry/child::*">
<xsl:if test="position() != last()"><xsl:value-of select="."/>,</xsl:if>
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
</xsl:for-each>
问题是 for-each
遍历一系列节点,在本例中是 Entry
元素的所有子节点。在这个新序列中,只有 一个 最后一个元素,而不是您预期的几个。所以,这个 if
:
<xsl:if test="position() = last()"><xsl:value-of select="."/><xsl:text>
</xsl:text></xsl:if>
只触发一次,针对最后一个 Entry
元素的最后一个子元素。这就是为什么所有内容都在同一行,而行尾只有一个
。
I thought that
Report/Entry/child::*
is the same as<xsl:for-each select="Report/Entry"> <xsl:for-each select="*">
是的,它们针对相同的节点,但 position()
的行为不同。