2012-09-05 21 views
8

He algunos XML que está formateado como sigue:XSLT: Ordenar por el menor de 2 valores

<products> 
    <product> 
    <name>Product 1</name> 
    <price> 
     <orig>15</orig> 
     <offer>10</offer> 
    </price> 
    </product> 
    <product> 
    <name>Product 2</name> 
    <price> 
     <orig>13</orig> 
     <offer>12</offer> 
    </price> 
    </product> 
    <product> 
    <name>Product 3</name> 
    <price> 
     <orig>11</orig> 
    </price> 
    </product> 
</products> 

necesito para ordenar los productos utilizando XSLT 1.0 (ya sea en orden ascendente o descendente) en base a su actual precio. Mi dificultad radica en el hecho de que tengo que ordenar en el menor de los dos posibles valores de precio <orig> y <offer>si ambos existen.

Para el ejemplo anterior el orden correcto sería:

  • Producto 1 (valor más bajo = 10)
  • Producto 3 (valor más bajo = 11)
  • Producto 2 (valor más bajo = 12)

Cualquier ayuda sería muy apreciada, ya que no puedo encontrar una pregunta similar a través de la búsqueda.

Respuesta

5

I. Hay una XSLT general y pura solución 1,0 - tan simple como esto:

<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="/*"> 
    <products> 
    <xsl:apply-templates select="*"> 
    <xsl:sort data-type="number" select= 
    "price/*[not(../* &lt; .)]"/> 
    </xsl:apply-templates> 
    </products> 
</xsl:template> 
</xsl:stylesheet> 

II.Si price tiene otros hijos además de offer y orig - en este caso, la solución general I. arriba (al igual que las otras dos respuestas a esta pregunta) no funciona correctamente.

Aquí es una solución correcta para este caso:

<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="/*"> 
    <products> 
    <xsl:apply-templates select="*"> 
    <xsl:sort data-type="number" select= 
    "sum(price/orig[not(../offer &lt;= .)]) 
    + 
    sum(price/offer[not(../orig &lt; .)]) 
    "/> 
    </xsl:apply-templates> 
    </products> 
</xsl:template> 
</xsl:stylesheet> 

III. Si sabemos que offer nunca excede orig:

<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="/*"> 
    <products> 
    <xsl:apply-templates select="*"> 
    <xsl:sort data-type="number" 
     select="price/offer | price/orig[not(../offer)]"/> 
    </xsl:apply-templates> 
    </products> 
</xsl:template> 
</xsl:stylesheet> 

IV. Verificación:

Las tres transformaciones anteriores, cuando se aplica al documento XML proporcionado:

<products> 
    <product> 
    <name>Product 1</name> 
    <price> 
     <orig>15</orig> 
     <offer>10</offer> 
    </price> 
    </product> 
    <product> 
    <name>Product 2</name> 
    <price> 
     <orig>13</orig> 
     <offer>12</offer> 
    </price> 
    </product> 
    <product> 
    <name>Product 3</name> 
    <price> 
     <orig>11</orig> 
    </price> 
    </product> 
</products> 

producen el, resultado correcto deseada:

<products> 
    <product> 
     <name>Product 1</name> 
     <price> 
     <orig>15</orig> 
     <offer>10</offer> 
     </price> 
    </product> 
    <product> 
     <name>Product 3</name> 
     <price> 
     <orig>11</orig> 
     </price> 
    </product> 
    <product> 
     <name>Product 2</name> 
     <price> 
     <orig>13</orig> 
     <offer>12</offer> 
     </price> 
    </product> 
</products> 

solución II es el único de los tres que todavía producen el resultado correcto cuando se aplica en este documento XML (se agregó un hijo minAcceptable al price):

<products> 
    <product> 
    <name>Product 1</name> 
    <price> 
     <orig>15</orig> 
     <offer>10</offer> 
     <minAcceptable>8</minAcceptable> 
    </price> 
    </product> 
    <product> 
    <name>Product 2</name> 
    <price> 
     <orig>13</orig> 
     <offer>12</offer> 
     <minAcceptable>6</minAcceptable> 
    </price> 
    </product> 
    <product> 
    <name>Product 3</name> 
    <price> 
     <orig>11</orig> 
     <minAcceptable>7</minAcceptable> 
    </price> 
    </product> 
</products> 

hacer la nota que ninguna de las otras respuestas procesa este documento XML correctamente.

+0

+1 Muy elegante, buen trabajo, @DimitreNovatchev. Sin embargo, argumentaría que la Solución II no es apropiada para la pregunta (ya que aborda una situación nunca descrita por el PO) y, por lo tanto, no debe utilizarse como evidencia de insuficiencia en otras respuestas. :) – ABach

+0

@ABach, de nada. En cuanto a la relevancia, tanto la solución 1. como la 3. siguen exactamente el documento XML del OP. La solución 2 nos da conocimientos sobre qué hacer en una situación ligeramente diferente, cuando otras soluciones no funcionan. El conocimiento es poder, ¿no crees? –

+0

Lo compraré. :) – ABach

6

(respuesta actualizada para incluir pensamientos en tanto XSLT 1.0 y 2,0)

I. XSLT 1.0:

Tenga en cuenta que XSLT 1.0 no tiene incorporado un equivalente a min(); suponiendo que su analizador es compatible con EXSLT, puede hacer uso de su función math:min() para lograr una solución bastante similar a la variante XSLT 2.0 siguiente.


II. XSLT 2.0:

Aquí hay una solución que hace uso de la función de agregación XPath 2.0 min().

Cuando esto XSLT solución 2,0:

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

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

    <xsl:template match="products"> 
    <products> 
     <xsl:apply-templates select="product"> 
     <xsl:sort select="min(price/offer|price/orig)" 
      data-type="number" order="ascending" /> 
     </xsl:apply-templates> 
    </products> 
    </xsl:template> 

</xsl:stylesheet> 

..es aplicadas al XML proporcionado:

<products> 
    <product> 
    <name>Product 1</name> 
    <price> 
     <orig>15</orig> 
     <offer>10</offer> 
    </price> 
    </product> 
    <product> 
    <name>Product 2</name> 
    <price> 
     <orig>13</orig> 
     <offer>12</offer> 
    </price> 
    </product> 
    <product> 
    <name>Product 3</name> 
    <price> 
     <orig>11</orig> 
    </price> 
    </product> 
</products> 

.the quería resultado se produce:

<?xml version="1.0" encoding="UTF-8"?> 
<products> 
    <product> 
     <name>Product 1</name> 
     <price> 
     <orig>15</orig> 
     <offer>10</offer> 
     </price> 
    </product> 
    <product> 
     <name>Product 3</name> 
     <price> 
     <orig>11</orig> 
     </price> 
    </product> 
    <product> 
     <name>Product 2</name> 
     <price> 
     <orig>13</orig> 
     <offer>12</offer> 
     </price> 
    </product> 
</products> 
+0

@MarkS - He actualizado mi respuesta para proporcionar una posible solución XSLT 1.0. ¿Sabe usted: su analizador XSLT implementa las funciones de extensión EXSLT (o, en particular, el subconjunto 'matemático 'de esas funciones)? – ABach

+0

Lo hace. Parece que esta podría ser la solución. Jugaré antes de dar ningún comentario. ¡Muchas gracias! – MarkS

+0

@MarkS - suena bien; ¡que nos mantengais! – ABach

5

Una solución XSLT 1.0 que no requiere EXSLT:

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

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

    <xsl:template match="products"> 
     <products> 
      <xsl:apply-templates select="product"> 
       <xsl:sort select="(price/*[not(. > ../*)])[1]" 
        data-type="number" order="ascending" /> 
      </xsl:apply-templates> 
     </products> 
    </xsl:template> 

</xsl:stylesheet> 
+0

+1 Gran solución. – ABach

Cuestiones relacionadas