XSLT:多次添加数字和打印小计

时间:2015-04-18 19:13:09

标签: xml xslt

我是XSLT的初学者,并且发现我不能只是将数字加到变量中并以任何方式更改其值。

我有一个XML文档,其中包含我需要添加的数字列表,直到元素与特定属性值匹配,然后打印该数字将其重置为0并继续累加其余部分,直到我再次看到该特定属性。 / p>

例如我有这个XML:

<list>
 <entry>
  <field type="num" value="189.5" />
 </entry>
 <entry>
  <field type="num" value="1.5" />
 </entry>
 <entry>
  <field type="summary" />
 </entry>
 <entry>
  <field type="num" value="9.5" />
 </entry>
 <entry>
  <field type="num" value="11" />
 </entry>
 <entry>
  <field type="num" value="10" />
 </entry>
 <entry>
  <field type="summary" />
 </entry>
</list>

现在我希望我的XSLT打印出来:

189.5
1.5
#191#
9.5
11
10
#30.5#

我已经读过,我可以通过在条件下使用sum()来做到这一点。我知道如何使用for-each并相对指向元素,iam也可以通过简单地总结所有类型= num来使用sum(),但是如何仅将第一个num加起来直到type = summary出现,然后仅下一个sum从最后一个类型=摘要到下一个?

我希望这样的事情:

<xsl:for-each select="list/entry">
 <xsl:if test="field[@type='summary']">
  <!-- we are now at a type=summary element, now sum up -->
  #<xsl:value-of select="sum(WHAT_TO_PUT_HERE?)" />#
 </xsl:if>
 <xsl:if test="field[@type='num']">
  <xsl:value-of select="field/@value" />
 </xsl:if>
</xsl:for-each>

感谢任何帮助。

4 个答案:

答案 0 :(得分:6)

<强>予。这是一个简单的,仅向前的解决方案 - 请注意,没有使用反向轴,时间复杂度只是 O(N),空间复杂度只是 O (1)

这可能是所有解决方案中最简单,最快速的方法:

所有 ......

都不需要怪异的复杂性或分组

没有变量,没有密钥(没有空间用于缓存key-&gt;值),没有sum()......

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

  <xsl:template match="/*"><xsl:apply-templates select="*[1]"/></xsl:template>

  <xsl:template match="entry[field/@type = 'num']">
    <xsl:param name="pAccum" select="0"/>
    <xsl:value-of select="concat(field/@value, '&#xA;')"/>
    <xsl:apply-templates select="following-sibling::entry[1]">
      <xsl:with-param name="pAccum" select="$pAccum+field/@value"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="entry[field/@type = 'summary']">
    <xsl:param name="pAccum" select="0"/>
    <xsl:value-of select="concat('#', $pAccum, '#&#xA;')"/>  
    <xsl:apply-templates select="following-sibling::entry[1]"/>
  </xsl:template>
</xsl:stylesheet>

这是流式转换的一个示例 - 它不需要完整的XML文档树存在于内存中,并且可以用于处理无限长或无限长的文档。

在提供的源XML文档上应用转换

<list>
    <entry>
        <field type="num" value="189.5" />
    </entry>
    <entry>
        <field type="num" value="1.5" />
    </entry>
    <entry>
        <field type="summary" />
    </entry>
    <entry>
        <field type="num" value="9.5" />
    </entry>
    <entry>
        <field type="num" value="11" />
    </entry>
    <entry>
        <field type="num" value="10" />
    </entry>
    <entry>
        <field type="summary" />
    </entry>
</list>

产生了想要的正确结果

189.5
1.5
#191#
9.5
11
10
#30.5#

<强> II。更新

当运行在足够大的XML文档上并且使用不优化尾递归的XSLT处理器时,上面的转换会导致堆栈溢出,这是由于<xsl:apply-templates>的长链

下面是另一个转换,即使使用非常大的XML文档也不会导致堆栈溢出。同样,没有反向轴,没有键,没有“分组”,没有条件指令,没有count(),没有<xsl:variable> ......

而且,最重要的是,与基于密钥的Muenchian分组“高效”相比,当在具有105000(105000)行的XML文档上运行时,此转换仅占后者的61%:< / p>

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:template match="/*">
  <xsl:apply-templates select=
  "*[1] | entry[field/@type = 'summary']/following-sibling::*[1]"/>
 </xsl:template>

  <xsl:template match="entry[field/@type = 'num']">
    <xsl:param name="pAccum" select="0"/>

    <xsl:value-of select="concat(field/@value, '&#xA;')"/>

    <xsl:apply-templates select="following-sibling::entry[1]">
        <xsl:with-param name="pAccum" select="$pAccum+field/@value"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="entry[field/@type = 'summary']">
    <xsl:param name="pAccum" select="0"/>

    <xsl:value-of select="concat('#', $pAccum, '#&#xA;')"/>
 </xsl:template>
</xsl:stylesheet>

此外,通过仅用*替换每个元素名称,可以加快这种转换,使得Muenchian分组转换所花费的时间少于50%(即,使其快两倍以上)。

我们所有人都需要学习的课程:非键解决方案有时可能比基于密钥的解决方案更有效。

答案 1 :(得分:2)

正如作为评论建议的分组的不同解决方案 - 您也可以使用匹配模式来获得总和:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="field[@type='num']">
  <xsl:value-of select="@value"/>
<xsl:text>&#x0A;</xsl:text>
  </xsl:template>
  <xsl:template match="entry[field[@type='summary']]">
  <xsl:variable name="sumCount" select="count(preceding-sibling::entry[field[@type='summary']])"/>
     <xsl:text>#</xsl:text>
     <xsl:value-of select="sum(preceding-sibling::entry[count(preceding-sibling::entry[field[@type='summary']]) = $sumCount]/field[@type='num']/@value)"/>
    <xsl:text>#&#x0A;</xsl:text>     
  </xsl:template>
</xsl:transform>

当应用于输入XML时,会产生输出

189.5
1.5
#191#
9.5
11
10
#30.5#

模板匹配field[@type='num']打印值并添加换行符,匹配entry[field[@type='summary']]的模板使用变量

<xsl:variable name="sumCount" select="count(preceding-sibling::entry[field[@type='summary']])"/>

检查summary类型的先前字段数。然后,只打印具有相同数量的前num字段的summary类型的所有条目值的总和:

<xsl:value-of select="sum(preceding-sibling::entry[
                      count(preceding-sibling::entry[field[@type='summary']]) = $sumCount
                      ]/field[@type='num']/@value)"/>

更新要更详细地说明其工作方式如何:在匹配entry[field[@type='summary']]的模板中,变量sumCount计算所有以前具有{{1}类型字段的条目{1}}:

summary

因此,当模板与第一个count(preceding-sibling::entry[field[@type='summary']]) 字段匹配时,summary的值为sumCount,当匹配第二个0字段时,summarysumCount
第二行使用1函数

sum

将所有前一个(前一个)条目的所有sum( preceding-sibling::entry [ count(preceding-sibling::entry[field[@type='summary']]) = $sumCount ] /field[@type='num']/@value ) field[@type='num']/@value类型的当前字段具有相同数量的先前字段summary相加:

summary

因此,当第二个count(preceding-sibling::entry[field[@type='summary']]) = $sumCount 匹配时,只有值为summarynum9.5的{​​{1}}字段的值将归纳为它们与当前10字段具有相同数量的先前11字段 对于值为summarysummary的<{1}}字段,

num

189.5,因此1.5函数中省略了这些字段。

答案 2 :(得分:1)

您需要Muenchian grouping的变体。首先将定义为:

<xsl:key name="numbers" match="entry[field/@type='num']" use="generate-id(following-sibling::entry[field/@type='summary'][1])" />

然后使用:

#<xsl:value-of select="sum(key('numbers', generate-id())/field/@value)" />#

将当前组中的数字相加。

答案 3 :(得分:0)

派对太晚了,和matthias_h几乎一样:

<?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text"/>

  <xsl:template match="//field[@type='num']">
    <xsl:value-of select="concat(@value,'&#x0a;')"/>
  </xsl:template>

  <xsl:template match="//field[@type='summary']">
    <xsl:variable name="prevSumCnt" select="count(preceding::field[@type='summary'])"/>
    <xsl:variable name="sum" select="sum(preceding::field[count(preceding::field[@type='summary'])=$prevSumCnt]/@value)"/>
    <xsl:value-of select="concat('#',$sum,'#&#x0a;')"/>
  </xsl:template>

  <xsl:template match="text()"/>
</xsl:transform>

这个想法是对所有具有相同数量的汇总字段的字段进行求和,而不是实际的汇总字段......