6

我有以下 XML 源结构:

<turnovers>
    <turnover repid="1" amount="500" rate="0.1"/>
    <turnover repid="5" amount="600" rate="0.5"/>
    <turnover repid="4" amount="400" rate="0.2"/>
    <turnover repid="1" amount="700" rate="0.05"/>
    <turnover repid="2" amount="100" rate="0.15"/>
    <turnover repid="1" amount="900" rate="0.25"/>
    <turnover repid="2" amount="1000" rate="0.18"/>
    <turnover repid="5" amount="200" rate="0.55"/>
    <turnover repid="9" amount="700" rate="0.40"/>
</turnovers>

我需要一个 XSL:value-of select 语句,它将返回给定代表 ID 的 rate 属性和 amount 属性的乘积之和。所以对于代表 5,我需要 ((600 x 0.5) + (200 x 0.55))。

4

6 回答 6

9
<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:template match="/turnovers">
    <val>
      <!-- call the sum function (with the relevant nodes) -->
      <xsl:call-template name="sum">
        <xsl:with-param name="nodes" select="turnover[@repid='5']" />
      </xsl:call-template>
    </val>
  </xsl:template>

  <xsl:template name="sum">  
    <xsl:param name="nodes" />
    <xsl:param name="sum" select="0" />

    <xsl:variable name="curr" select="$nodes[1]" />

    <!-- if we have a node, calculate & recurse -->
    <xsl:if test="$curr">
      <xsl:variable name="runningsum" select="
        $sum + $curr/@amount * $curr/@rate
      " />
      <xsl:call-template name="sum">
        <xsl:with-param name="nodes" select="$nodes[position() &gt; 1]" />
        <xsl:with-param name="sum"   select="$runningsum" />
      </xsl:call-template>
    </xsl:if>

    <!-- if we don't have a node (last recursive step), return sum -->
    <xsl:if test="not($curr)">
      <xsl:value-of select="$sum" />
    </xsl:if>

  </xsl:template>
</xsl:stylesheet>

给出:

<val>410</val>

两个<xsl:if>s 可以用一个 s 代替<xsl:choose>。这意味着在递归期间减少了一次检查,但这也意味着增加了两行代码。

于 2009-08-26T12:56:14.853 回答
2

在普通的 XSLT 1.0 中,您需要一个递归模板,例如:

  <xsl:template match="turnovers">
    <xsl:variable name="selectedId" select="5" />
    <xsl:call-template name="sum_turnover">
      <xsl:with-param name="turnovers" select="turnover[@repid=$selectedId]" />
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="sum_turnover">
    <xsl:param name="total" select="0" />
    <xsl:param name="turnovers"  />
    <xsl:variable name="head" select="$turnovers[1]" />
    <xsl:variable name="tail" select="$turnovers[position()>1]" />
    <xsl:variable name="calc" select="$head/@amount * $head/@rate" />
    <xsl:choose>
      <xsl:when test="not($tail)">
        <xsl:value-of select="$total + $calc" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="sum_turnover">
          <xsl:with-param name="total" select="$total + $calc" />
          <xsl:with-param name="turnovers" select="$tail" />
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
于 2009-08-26T11:48:58.193 回答
1

这应该可以解决问题,您需要做一些进一步的工作来选择 distinctrepid

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

    <xsl:template match="/">    
        <xsl:variable name="totals">
            <product>
                <xsl:for-each select="turnovers/turnover">
                    <repid repid="{@repid}">
                        <value><xsl:value-of select="@amount * @rate"/></value>
                    </repid>
                </xsl:for-each>
            </product>
        </xsl:variable>
        <totals>
            <total repid="5" value="{sum($totals/product/repid[@repid='5']/value)}"/>   
        </totals>               
    </xsl:template>

</xsl:stylesheet>
于 2009-08-26T11:19:53.527 回答
1

In XSLT 1.0 the use of FXSL makes such problems easy to solve:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:f="http://fxsl.sf.net/"
 xmlns:ext="http://exslt.org/common"
 exclude-result-prefixes="xsl f ext"
 >
 <xsl:import href="zipWith.xsl"/>
 <xsl:output method="text"/>

  <xsl:variable name="vMultFun" select="document('')/*/f:mult-func[1]"/>    

    <xsl:template match="/"> 
      <xsl:call-template name="profitForId"/>
    </xsl:template>

    <xsl:template name="profitForId">
      <xsl:param name="pId" select="1"/>

      <xsl:variable name="vrtfProducts">
          <xsl:call-template name="zipWith">
            <xsl:with-param name="pFun" select="$vMultFun"/>
            <xsl:with-param name="pList1" select="/*/*[@repid = $pId]/@amount"/>
            <xsl:with-param name="pList2" select="/*/*[@repid = $pId]/@rate"/>
          </xsl:call-template>
      </xsl:variable>

      <xsl:value-of select="sum(ext:node-set($vrtfProducts)/*)"/>
    </xsl:template>

    <f:mult-func/>
    <xsl:template match="f:mult-func" mode="f:FXSL">
     <xsl:param name="pArg1"/>
     <xsl:param name="pArg2"/>

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

When this transformation is applied on the originally posted source XML document, the correct result is produced:

310

In XSLT 2.0 the same solution using FXSL 2.0 can be expressed by an XPath one-liner:

sum(f:zipWith(f:multiply(),
          /*/*[xs:decimal(@repid) eq 1]/@amount/xs:decimal(.),
          /*/*[xs:decimal(@repid) eq 1]/@rate/xs:decimal(.)
          )
     )

The whole transformation:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:f="http://fxsl.sf.net/"
 exclude-result-prefixes="f xs"
>
 <xsl:import href="../f/func-zipWithDVC.xsl"/>
 <xsl:import href="../f/func-Operators.xsl"/>

 <!-- To be applied on testFunc-zipWith4.xml -->
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
   <xsl:value-of select=
   "sum(f:zipWith(f:multiply(),
              /*/*[xs:decimal(@repid) eq 1]/@amount/xs:decimal(.),
              /*/*[xs:decimal(@repid) eq 1]/@rate/xs:decimal(.)
              )
         )
    "/>
 </xsl:template>
</xsl:stylesheet>

Again, this transformation produces the correct answer:

310

Note the following:

  1. The f:zipWith() function takes as arguments a function fun() (of two arguments) and two lists of items having the same length. It produces a new list of the same length, whose items are the result of the pair-wise application of fun() on the corresponding k-th items of the two lists.

  2. f:zipWith() as in the expression takes the function f:multiply() and two sequences of corresponding "ammount" and "rate" attributes. The sesult is a sequence, each item of which is the product of the corresponding "ammount" and "rate".

  3. Finally, the sum of this sequence is produced.

  4. There is no need to write an explicit recursion and it is also guaranteed that the behind-the scenes recursion used within f:zipWith() is never going to crash (for all practical cases) with "stack overflow"

于 2009-08-26T14:12:55.290 回答
0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="xs"
      version="2.0">

   <xsl:variable name="repid" select="5" />

   <xsl:template match="/">
      <xsl:value-of select=
         "sum(for $x in /turnovers/turnover[@repid=$repid] return $x/@amount * $x/@rate)"/>
   </xsl:template>

</xsl:stylesheet>

如果您只需要值而不是 xml,则可以执行此操作。

于 2009-11-25T22:38:14.857 回答
-2

在 XSLT 中最简单的方法可能是使用编程语言绑定,这样您就可以定义自己的 XPath 函数。

于 2009-08-26T10:16:35.540 回答