如何XPath sum()XSL for-each循环中的所有先前节点?

时间:2011-10-19 08:53:06

标签: xslt xpath

源XML(这只是foobar数据,实际上它是数千行,可以是正面和负面):

<accounting>
    <entry id="1">
        <accounting_date>2010-10-29</accounting_date>
        <transfer_date>2010-10-29</transfer_date>
        <description>Start balance</description>
        <vat>0</vat>
        <sum>87287</sum>
    </entry>
    <entry id="2">
        <accounting_date>2011-01-24</accounting_date>
        <transfer_date>2011-02-17</transfer_date>
        <description>Bill 1</description>
        <vat>175</vat>
        <sum>875</sum>
    </entry>
    <entry id="3">
        <accounting_date>2011-01-31</accounting_date>
        <transfer_date>2011-01-18</transfer_date>
        <description>Bill 2</description>
        <vat>350</vat>
        <sum>1750</sum>
    </entry>
</accounting>

我想将此XML转换为HTML表格以显示给用户。大多数转型只是将价值观放在正确的位置,但平衡场让我很头疼。

我的XSLT(不起作用):

<table>
    <tr>
        <th>Accounting date</th>
        <th>Description</th>
        <th>Sum</th>
        <th>Balanche</th>
    </tr>
    <xsl:for-each select="/accounting/entry">
        <tr>
            <td><xsl:value-of select="accounting_date" /></td>
            <td><xsl:value-of select="description" /></td>
            <td><xsl:value-of select="sum" /></td>
            <td><xsl:value-of select="sum(../entry[position() &lt; current()/position()]/sum)" /></td><!-- This XPath is the problem! -->
        </tr>
    </xsl:for-each>
</table>

预期结果:

<table>
    <tr>
        <th>Accounting date</th>
        <th>Description</th>
        <th>Sum</th>
        <th>Balanche</th>
    </tr>
    <tr>
        <td>2010-10-29</td>
        <td>Start balance</td>
        <td>87287</td>
        <td>87287</td>
    </tr>
    <tr>
        <td>2011-01-24</td>
        <td>Bill 1</td>
        <td>875</td>
        <td>88162</td>
    </tr>
    <tr>
        <td>2011-01-31</td>
        <td>Bill 2</td>
        <td>1750</td>
        <td>89912</td>
    </tr>
</table>

Chrome是空白的,Firefox给了我:

Error loading stylesheet: XPath parse failure: Name or Nodetype test expected:

我被困住了,请帮忙。 :)

3 个答案:

答案 0 :(得分:4)

最佳解决方案可能取决于您是使用XSLT 1.0还是XSLT 2.0。你真的需要说,因为目前在这个领域使用两者大致均匀。 (看来你在浏览器中运行它,这表明你想要一个1.0解决方案,所以这就是我给你的。)

但无论如何,递归是你的朋友。在这种情况下,“兄弟递归”,你写一个模板来处理一个条目,它确实应用模板来处理下一个条目,传递总数到目前为止参数:像这样

<xsl:template match="entry">
  <xsl:param name="total-so-far" select="0"/>
         <tr>
            <td><xsl:value-of select="accounting_date" /></td>
            <td><xsl:value-of select="description" /></td>
            <td><xsl:value-of select="sum" /></td>
            <td><xsl:value-of select="$total-so-far + sum"/></td><
        </tr>  
    <xsl:apply-templates select="following-sibling::entry[1]">
      <xsl:with-param name="total-so-far" select="$total-so-far + sum"/>
    </xsl:apply-templates>
</xsl:template>

然后你需要用

开始这个过程
<xsl:template match="accounting">
 <table>
   <xsl:apply-templates select="entry[1]"/>
 </table>
</xsl:template>

如果有数千行,那么这可能导致XSLT处理器中的堆栈溢出,而不执行尾调用优化。我不知道今天的浏览器中的XSLT处理器是否实现了这种优化。

答案 1 :(得分:1)

或者,您可以使用前一个兄弟轴

<table>
    <tr>
        <th>Accounting date</th>
        <th>Description</th>
        <th>Sum</th>
        <th>Balanche</th>
    </tr>
    <xsl:for-each select="/accounting/entry">
        <tr>
            <td>
                <xsl:value-of select="accounting_date" />
            </td>
            <td>
                <xsl:value-of select="description" />
            </td>
            <td>
                <xsl:value-of select="sum" />
            </td>
            <td>
                <xsl:value-of select="sum(preceding-sibling::*/sum)+sum" />
            </td>
        </tr>
    </xsl:for-each>
</table>

答案 2 :(得分:0)

除了@Michael Kay的正确答案之外,这里还有一个来自 FXSL 的通用模板/函数,用于计算运行总计。由于堆栈溢出,其DVC变体永远不会(出于实际目的)崩溃。使用DVC(Divide and Conquer)递归,处理1000000(1M)项的序列需要最大堆栈深度仅为19。

以下是使用scanl模板的示例:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
  <xsl:import href="scanlDVC.xsl"/>
  <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <myAdd:myAdd/>

  <myParam:myParam>0</myParam:myParam>

  <xsl:template match="/">

    <xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
    <xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>


    <xsl:call-template name="scanl">
      <xsl:with-param name="pFun" select="$vFun"/>
      <xsl:with-param name="pQ0" select="$vZero" />
      <xsl:with-param name="pList" select="/*/num"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="myAdd:*" mode="f:FXSL">
    <xsl:param name="pArg1" select="0"/>
    <xsl:param name="pArg2" select="0"/>

    <xsl:value-of select="$pArg1 + $pArg2"/>
  </xsl:template>
</xsl:stylesheet>

将此转换应用于以下XML文件

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

生成正确的结果(正在运行的总计)

<el>0</el>
<el>1</el>
<el>3</el>
<el>6</el>
<el>10</el>
<el>15</el>
<el>21</el>
<el>28</el>
<el>36</el>
<el>45</el>
<el>55</el>

将其用于提供的XML文档

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
  <xsl:import href="scanlDVC.xsl"/>
  <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <myAdd:myAdd/>

  <myParam:myParam>0</myParam:myParam>

  <xsl:template match="/">

    <xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
    <xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>


    <xsl:call-template name="scanl">
      <xsl:with-param name="pFun" select="$vFun"/>
      <xsl:with-param name="pQ0" select="$vZero" />
      <xsl:with-param name="pList" select="/*/*/sum"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="myAdd:*" mode="f:FXSL">
    <xsl:param name="pArg1" select="0"/>
    <xsl:param name="pArg2" select="0"/>

    <xsl:value-of select="$pArg1 + $pArg2"/>
  </xsl:template>
</xsl:stylesheet>

并生成正确的结果:

<el>0</el>
<el>87287</el>
<el>88162</el>
<el>89912</el>