2009-08-26 41 views
6

que tienen la siguiente estructura XML de origen:XSLT para resumir producto de dos atributos

<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> 

necesito un XSL: valor de instrucción de selección que devolverá la suma del producto del atributo tasa y la cantidad atributo para una ID de representante dada. Entonces, para rep 5 necesito ((600 x 0.5) + (200 x 0.55)).

Respuesta

8
<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> 

Da:

<val>410</val> 

Los dos <xsl:if> s pueden ser reemplazados por un solo <xsl:choose>. Esto significaría un control menos durante la recursión, pero también significa dos líneas de código adicionales.

-2

La manera más fácil de hacerlo en XSLT es probablemente utilizar enlaces de lenguaje de programación, para que pueda definir sus propias funciones XPath.

1

Esto debería hacer el truco, tendrá que hacer un trabajo adicional para seleccionar las distintas repid 's

<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> 
+0

No sabía que puede crear nodos completos, asignarlos a una variable y luego consultarlos. Limpio, gracias John, lo probará. – staterium

+0

@ Ravh: No se puede, hasta XSLT 2.0. En 1.0, necesitaría la función de extensión node-set(). – Tomalak

2

En XSLT 1.0 llanura que necesitan una plantilla recursivo para esto, por ejemplo:

<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> 
+0

De alguna manera, a esta solución le falta la parte "para un @pido dado". – Tomalak

+0

sí, me perdí la parte @repid. Aquí hay otra versión – jor

+0

+1 de mí, ahora. Enfoque limpio. – Tomalak

1

En XSLT 1.0 el uso de FXSL hace que este tipo de problemas fáciles de resolver:

<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> 

Cuando se aplica esta transformación en el documento XML de origen originalmente publicado, el resultado correcto se produce:

En XSLT 2.0 la misma solución usando FXSL 2.0 puede ser expresada por un XPath de una sola línea:

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

Toda la transformación:

<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> 

Una vez más, esta transformación produce la respuesta correcta:

Tenga en cuenta lo siguiente:

  1. La función f:zipWith() toma como argumentos una función fun() (de dos argumentos) y dos listas de elementos que tienen la misma longitud. Produce una nueva lista de la misma longitud, cuyos elementos son el resultado de la aplicación por pares de fun() en los correspondientes k -ésimo elemento de las dos listas.

  2. f:zipWith() como en la expresión toma la función f:multiply() y dos secuencias de correspondiente "ammount" y "rate" atributos. El sesult es una secuencia, cada elemento del cual es el producto del correspondiente "ammount" y "rate".

  3. Finalmente, se produce el sum de esta secuencia.

  4. No hay necesidad de escribir una recursión explícita y también se garantiza que la recursividad detrás de las escenas se utiliza dentro de f:zipWith() nunca va a chocar (para todos los casos prácticos) con el "desbordamiento de pila"

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> 

Puede hacerlo si solo necesita el valor y no xml.

Cuestiones relacionadas