将 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>&#xD;</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>&#xD;</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>&#xD;</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>&#xD;</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 '&#xD;' 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>&#xD;</xsl:text></xsl:if>   
</xsl:for-each>

问题是 for-each 遍历一系列节点,在本例中是 Entry 元素的所有子节点。在这个新序列中,只有 一个 最后一个元素,而不是您预期的几个。所以,这个 if:

<xsl:if test="position()  = last()"><xsl:value-of select="."/><xsl:text>&#xD;</xsl:text></xsl:if>

只触发一次,针对最后一个 Entry 元素的最后一个子元素。这就是为什么所有内容都在同一行,而行尾只有一个&#xD;

I thought that Report/Entry/child::* is the same as <xsl:for-each select="Report/Entry"> <xsl:for-each select="*">

是的,它们针对相同的节点,但 position() 的行为不同。