将两个数字相乘,然后求和。

11

我试图做一件看起来非常简单的事情,但是却遇到了困难。我需要在节点中将两个数字相乘,然后对所有节点的这些数字总和进行求和。以下是我尝试过的XSLT代码:

<xsl:value-of select="sum(Parts/Part/Quantity * Parts/Part/Rate)"/>

这段代码会导致一个错误,错误信息是“函数sum的第一个参数不能转换为节点集。”

有没有人知道出了什么问题,或者我该如何完成我所尝试做的事情?


1
你应该提供足够的细节,这样别人才能考虑帮助你。需要提到XSLT版本、处理器(saxon或其他)或至少软件(Visual Studio/Altova XML Spy)。 - Rookie Programmer Aravind
2个回答

40

以下是三种可能的解决方案:

解决方案1 XSLT2:

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

    <xsl:template match="/">
      <xsl:sequence select="sum(/*/*/(rate * quantity))"/>
    </xsl:template>
</xsl:stylesheet>

当对以下XML文档应用此转换时:

<parts>
  <part>
        <rate>0.37</rate>
    <quantity>10</quantity>
  </part>
  <part>
        <rate>0.03</rate>
    <quantity>10</quantity>
  </part>
</parts>

期望的结果已生成:

4

XSLT 2.0方案利用了XPath 2.0中的一个特性, 即在最后一个"/"运算符的右边可以使用表达式或函数。这个表达式/函数将作为上下文节点,对已选择的每个节点进行应用,每次函数应用会产生一个结果。

Solution2 XSLT 1.0:

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

    <xsl:template match="/">
      <xsl:call-template name="sumProducts">
        <xsl:with-param name="pList" select="*/*"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template name="sumProducts">
        <xsl:param name="pList"/>
        <xsl:param name="pAccum" select="0"/>

        <xsl:choose>
          <xsl:when test="$pList">
            <xsl:variable name="vHead" select="$pList[1]"/>

            <xsl:call-template name="sumProducts">
              <xsl:with-param name="pList" select="$pList[position() > 1]"/>
              <xsl:with-param name="pAccum"
               select="$pAccum + $vHead/rate * $vHead/quantity"/>
            </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$pAccum"/>
          </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

应用于上述XML文档后,会生成正确结果:

4

这是一个典型的XSLT 1.0递归解决方案。请注意,sumProducts 模板如何递归调用自身,直到处理完传入参数$pList中的全部输入列表。

Solution3 FXSL(XSLT 1.0):

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:test-map-product="test-map-product"
exclude-result-prefixes="xsl ext test-map-product"
>
   <xsl:import href="sum.xsl"/>
   <xsl:import href="map.xsl"/>
   <xsl:import href="product.xsl"/>

   <!-- This transformation is to be applied on:
        salesMap.xml

        It contains the code of the "sum of products" from the 
        article "The Functional Programming Language XSLT"
     -->

   <test-map-product:test-map-product/>

   <xsl:output method="text"/>

   <xsl:template match="/">
     <!-- Get: map product /sales/sale -->
     <xsl:variable name="vSalesTotals">
         <xsl:variable name="vTestMap" select="document('')/*/test-map-product:*[1]"/>
         <xsl:call-template name="map">
           <xsl:with-param name="pFun" select="$vTestMap"/>
           <xsl:with-param name="pList1" select="/sales/sale"/>
         </xsl:call-template>
     </xsl:variable>

     <!-- Get sum map product /sales/sale -->
      <xsl:call-template name="sum">
        <xsl:with-param name="pList" select="ext:node-set($vSalesTotals)/*"/>
      </xsl:call-template>
   </xsl:template>

    <xsl:template name="makeproduct" match="*[namespace-uri() = 'test-map-product']">
      <xsl:param name="arg1"/>

      <xsl:call-template name="product">
        <xsl:with-param name="pList" select="$arg1/*"/>
      </xsl:call-template>
    </xsl:template>
</xsl:stylesheet>

当应用此转换于以下 XML 文档时:

<sales>
  <sale>
    <price>3.5</price>
    <quantity>2</quantity>
    <Discount>0.75</Discount>
    <Discount>0.80</Discount>
    <Discount>0.90</Discount>
  </sale>
  <sale>
    <price>3.5</price>
    <quantity>2</quantity>
    <Discount>0.75</Discount>
    <Discount>0.80</Discount>
    <Discount>0.90</Discount>
  </sale>
</sales>

正确的结果为:

7.5600000000000005

在最后的情况下,我们针对每个sale计算pricequantity和所有可用(变量数量的)discount的乘积。

FXSL是一个高阶函数的纯XSLT实现。在这个例子中,使用高阶函数f:map()将函数f:product()映射到每个sale元素的子列表上。然后将结果求和以产生最终结果。


谢谢。解决方案#2正是我需要创建聚合乘法函数的东西。 - Darrel Miller
1
@infant-programmer:感谢您的赞赏!如果您喜欢这篇文章,那么您一定会喜欢我博客上的很多其他内容:http://dnovatchev.spaces.live.com/Blog/ - Dimitre Novatchev

1

迪米特尔的所有解决方案都有效,他说得对,你不一定要使用扩展函数,但有时候使用它们会更方便。这并不会太有害,尤其是当你使用跨多个XSLT处理器支持的exslt扩展时。另外,你之所以出现序列错误很可能是因为你正在使用XSLT 1处理器。

如果你想坚持使用你选择的解决方案,你需要使用Saxon或其他支持XSLT 2的XSLT处理器。

否则,在XSLT 1中有另一种替代方法可以实现。这在大多数XSLT处理器中都可以工作,并且一些人可能会觉得比递归版本更容易理解。个人而言,我更喜欢递归版本(迪米特尔的第三个提案),因为它更具可移植性。

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

  <xsl:template name="GetProducts">
    <xsl:param name="left"/>
    <xsl:param name="right"/>

    <xsl:for-each select="$left/text()">
      <product>
        <xsl:value-of select="number(.) * number($right[position()])"/>
      </product>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="/">
    <xsl:variable name="products">
      <xsl:call-template name="GetProducts">
        <xsl:with-param name="left" select="Parts/Part/Rate"/>
        <xsl:with-param name="right" select="Parts/Part/Quantity"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:value-of select="sum(ex:node-set($products)/product)"/>
  </xsl:template>
</xsl:stylesheet>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接