2012-09-05 20 views
8

ho qualche XML formattato come segue:XSLT: ordine di minore tra 2 valori

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

devo ordinare i prodotti utilizzando XSLT 1.0 (in ordine crescente o decrescente) basati sulla loro attuale prezzo. La mia difficoltà sta nel fatto che ho bisogno di ordinare sul più basso dei due possibili valori di prezzo <orig> e <offer>se entrambi esistono.

Per l'esempio precedente l'ordine corretto sarebbe:

  • Prodotto 1 (valore minimo = 10)
  • Prodotto 3 (valore minimo = 11)
  • prodotto 2 (valore minimo = 12)

Qualsiasi aiuto sarebbe molto apprezzato, in quanto non riesco a trovare una domanda simile attraverso la ricerca.

risposta

5

I. C'è un XSLT generale e pura 1,0 soluzione - semplice come questo:

<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.Se price ha altri bambini oltre a offer e orig - in questo caso la soluzione generale I. sopra (così come le altre due risposte a questa domanda) non funziona correttamente.

ecco una soluzione corretta per questo 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. Se sappiamo che offer mai supera 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. Verifica:

Tutte e tre le trasformazioni di cui sopra, se applicato al documento XML fornito:

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

produrre il desiderato, risultato corretto:

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

Solution II è l'unico di i tre che producono ancora il risultato corretto quando applicati a questo documento XML (aggiunto unfiglio a 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> 

Do atto che nessuna delle altre risposte elabora correttamente questo documento XML.

+0

+1 Molto elegante - bel lavoro, @DimitreNovatchev. Vorrei tuttavia sostenere che la soluzione II non è appropriata alla domanda (poiché affronta una situazione mai descritta dal PO) e pertanto non dovrebbe essere utilizzata come prova di inadeguatezza in altre risposte. :) – ABach

+0

@ABach, prego. Per quanto riguarda la rilevanza, entrambe le soluzioni 1. e 3. stanno seguendo esattamente il documento XML dell'OP. La soluzione 2 ci dà la conoscenza su cosa fare in una situazione leggermente diversa, quando altre soluzioni non funzionano. La conoscenza è potere, non ti capisci così? –

+0

Lo comprerò. :) – ABach

6

(risposta aggiornato per includere pensieri su entrambi XSLT 1.0 e 2,0)

I. XSLT 1.0:

Nota che XSLT 1.0 non ha un built-in pari a min(); supponendo che il parser supporti EXSLT, è possibile utilizzare la sua funzione math:min() per ottenere una soluzione del tutto simile alla sotto variante XSLT 2.0.


II. XSLT 2.0:

Ecco una soluzione che utilizza la funzione di aggregazione XPath 2.0 min().

Quando questa soluzione XSLT 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> 

..è applicate al XML fornito:

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

..il risultato voluto è prodotto:

<?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 - Ho aggiornato la mia risposta per fornire una possibile soluzione XSLT 1.0. Sai: il tuo parser XSLT implementa le funzioni di estensione EXSLT (o, in particolare, il sottoinsieme 'math' di quelle funzioni)? – ABach

+0

Lo fa. Sembra che questa potrebbe essere la soluzione. Avrò un gioco prima di dare qualsiasi feedback. Grazie molto! – MarkS

+0

@MarkS: suona bene; Tienici aggiornati! – ABach

5

Una XSLT 1.0 soluzione che non richiede 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 Ottima soluzione. – ABach