2010-08-26 13 views
5

Soy nuevo en las transformaciones xsl y tengo una pregunta. estoy bucle a través de un xml de esta manera:transformación xsl

<PO> 
<Items> 
    <Item> 
    <Price>2</Price> 
    <Quantity>5</Quantity> 
    </Item> 
    <Item> 
    <Price>3</Price> 
    <Quantity>2</Quantity> 
    </Item>  
</Items> 
<QuantityTotal></QuantityTotal> 
</PO> 

Ahora quiero insertar un valor en el nodo QuantityTotal:
El valor es la suma de la cantidad de precio * de todos los elementos, en este caso (2 * 5) + (3 * 2) = 16 ¿Cómo puedo hacer esto? Lo intenté con un bucle y variables, pero las variables son inmutables, así que no sé cómo puedo lograrlo.

Thx por su ayuda

+1

+1 buena pregunta –

+0

Buena pregunta (+1). Vea mi respuesta para las soluciones en XSLT 1.0 (no se requieren extensiones) y XSLT 2.0 –

+0

Vea también http://stackoverflow.com/questions/436998/multiply-2-numeros-y- luego-sum-with-xslt y http ://desbordamiento de pila.com/questions/1333558/xslt-to-suma-producto-de-dos-atributos – harpo

Respuesta

4

Aquí es una solución XSLT - no hay funciones de extensión requieren:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<xsl:template match="node()|@*"> 
    <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="QuantityTotal"> 
    <xsl:copy> 
    <xsl:call-template name="sumProducts"> 
    <xsl:with-param name="pNodes" select="../Items/Item"/> 
    </xsl:call-template> 
    </xsl:copy> 
</xsl:template> 

<xsl:template name="sumProducts"> 
    <xsl:param name="pNodes"/> 
    <xsl:param name="pSum" select="0"/> 
    <xsl:param name="pEname1" select="'Price'"/> 
    <xsl:param name="pEname2" select="'Quantity'"/> 

    <xsl:choose> 
    <xsl:when test="not($pNodes)"> 
    <xsl:value-of select="$pSum"/> 
    </xsl:when> 
    <xsl:otherwise> 
    <xsl:call-template name="sumProducts"> 
     <xsl:with-param name="pNodes" select= 
     "$pNodes[position() > 1]"/> 
     <xsl:with-param name="pSum" select= 
     "$pSum 
     + 
     $pNodes[1]/*[name()=$pEname1] 
     * 
     $pNodes[1]/*[name()=$pEname2] 
     "/> 
    </xsl:call-template> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 
</xsl:stylesheet> 

cuando se aplica esta transformación en el documento XML proporcionado:

<PO> 
    <Items> 
     <Item> 
      <Price>2</Price> 
      <Quantity>5</Quantity> 
     </Item> 
     <Item> 
      <Price>3</Price> 
      <Quantity>2</Quantity> 
     </Item> 
    </Items> 
    <QuantityTotal></QuantityTotal> 
</PO> 

el resultado deseado se produce:

<PO> 
    <Items> 
     <Item> 
     <Price>2</Price> 
     <Quantity>5</Quantity> 
     </Item> 
     <Item> 
     <Price>3</Price> 
     <Quantity>2</Quantity> 
     </Item> 
    </Items> 
    <QuantityTotal>16</QuantityTotal> 
</PO> 
+0

+1 para la buena solución XSLT 1.0 y XSLT 2.0 también! –

1

He aquí una solución utilizando XSLT2, en el que conjuntos de nodos son objetos de primera clase. En XSLT1 necesitaría usar una extensión de conjunto de nodos.

Explicación continuación:

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

    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:variable name="extendedItems" as="xs:integer*"> 
     <xsl:for-each select="//Item"> 
      <xsl:value-of select="./Price * ./Quantity"/> 
     </xsl:for-each> 
    </xsl:variable> 

    <xsl:variable name="total"> 
     <xsl:value-of select="sum($extendedItems)"/> 
    </xsl:variable> 

    <xsl:template match="//QuantityTotal"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*"/> 
      <xsl:value-of select="$total"/> 
     </xsl:copy> 
    </xsl:template> 

</xsl:stylesheet> 

El enfoque aquí es el uso de una "identidad transformar" para copiar el documento, mientras que la realización de los cálculos e insertar el resultado en la plantilla QuantityTotal salida. La primera plantilla copia la entrada al resultado, pero se reemplaza por una plantilla más específica para QuantityTotal en la parte inferior. La primera declaración de variable crea una lista de costos extendidos, y la segunda definición de variable suma los costos para producir el total. El total se inserta en el nodo QuantityTotal.

La clave para entender XSL es que es de carácter declarativo. El error conceptual más común que cometen casi todos los principiantes es suponer que la hoja de estilo es un programa secuencial que procesa el documento XML de entrada. En realidad, es al revés. El motor XSL lee el documento XML. y para cada nueva etiqueta que encuentra se ve en la hoja de estilo para la "mejor" coincidencia, ejecutando esa plantilla.

EDIT:

Aquí hay una versión que funciona con xslt1.1 Saxon 6,5

<?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" 
    xmlns:ex="http://exslt.org/common" 
    extension-element-prefixes="ex" 
    version="1.1"> 
    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:variable name="extendedItems"> 
     <xsl:for-each select="//Item"> 
      <extended> 
      <xsl:value-of select="./Price * ./Quantity"/> 
      </extended> 
      </xsl:for-each> 
    </xsl:variable> 
    <xsl:variable name="total"> 
     <xsl:value-of select="sum(ex:node-set($extendedItems/extended))"/> 
    </xsl:variable> 
    <xsl:template match="//QuantityTotal"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*"/> 
      <xsl:value-of select="$total"/> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 
+0

gracias por su respuesta, pero todavía no funciona como se supone que debe. El resultado que obtengo ahora es 106, lo que hace es que calcula el primer elemento (resultado = 10) y el segundo elemento (resultado = 6) y, por lo tanto, la variable extendedItems su valor se convierte en "106". Además, cuando utilizo la función suma() para el total, arroja un error de que tengo que usar un conjunto de nodos() para que mi código para total sea así: sum (msxsl: node-set ($ extendedItems)). ¿Qué estoy haciendo mal? Thx por adelantado –

+0

Está ejecutando esto con XSLT1. ¿Tienes un transformador XSLT2 disponible? –

+0

¿Qué procesador XSL estás usando? –

2

Además de la excelente respuesta de Dimitre, esta hoja de estilo toma otro enfoque:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:template match="node()|@*"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="QuantityTotal"> 
     <xsl:copy> 
      <xsl:apply-templates select="../Items/Item[1]" mode="sum"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="Item" mode="sum"> 
     <xsl:param name="pSum" select="0"/> 
     <xsl:variable name="vNext" select="following-sibling::Item[1]"/> 
     <xsl:variable name="vSum" select="$pSum + Price * Quantity"/> 
     <xsl:apply-templates select="$vNext" mode="sum"> 
      <xsl:with-param name="pSum" select="$vSum"/> 
     </xsl:apply-templates> 
     <xsl:if test="not($vNext)"> 
      <xsl:value-of select="$vSum"/> 
     </xsl:if> 
    </xsl:template> 
</xsl:stylesheet> 

Salida:

<PO> 
    <Items> 
     <Item> 
      <Price>2</Price> 
      <Quantity>5</Quantity> 
     </Item> 
     <Item> 
      <Price>3</Price> 
      <Quantity>2</Quantity> 
     </Item> 
    </Items> 
    <QuantityTotal>16</QuantityTotal> 
</PO> 
+0

@Alejandro: Buena respuesta (+1). –

Cuestiones relacionadas