2010-04-19 2 views
7

Come impedire l'inserimento di voci duplicate in un elenco e quindi, idealmente, ordinare l'elenco? Quello che sto facendo è quando mancano le informazioni a un livello, prendendo le informazioni da un livello sotto di esso, per costruire l'elenco mancante, nel livello superiore. Attualmente, ho XML simile a questo:Come impedire i duplicati, in XSL?

<c03 id="ref6488" level="file"> 
    <did> 
     <unittitle>Clinic Building</unittitle> 
     <unitdate era="ce" calendar="gregorian">1947</unitdate> 
    </did> 
    <c04 id="ref34582" level="file"> 
     <did> 
      <container label="Box" type="Box">156</container> 
      <container label="Folder" type="Folder">3</container> 
     </did> 
    </c04> 
    <c04 id="ref6540" level="file"> 
     <did> 
      <container label="Box" type="Box">156</container> 
      <unittitle>Contact prints</unittitle> 
     </did> 
    </c04> 
    <c04 id="ref6606" level="file"> 
     <did> 
      <container label="Box" type="Box">154</container> 
      <unittitle>Negatives</unittitle> 
     </did> 
    </c04> 
</c03> 

ho quindi applicare la seguente XSL:

<xsl:template match="c03/did"> 
    <xsl:choose> 
     <xsl:when test="not(container)"> 
      <did> 
       <!-- If no c03 container item is found, look in the c04 level for one --> 
       <xsl:if test="../c04/did/container"> 

        <!-- If a c04 container item is found, use the info to build a c03 version --> 
        <!-- Skip c03 container item, if still no c04 items found --> 
        <container label="Box" type="Box"> 

         <!-- Build container list --> 
         <!-- Test for more than one item, and if so, list them, --> 
         <!-- separated by commas and a space --> 
         <xsl:for-each select="../c04/did"> 
          <xsl:if test="position() &gt; 1">, </xsl:if> 
          <xsl:value-of select="container"/> 
         </xsl:for-each> 
        </container>      
      </did> 
     </xsl:when> 

     <!-- If there is a c03 container item(s), list it normally --> 
     <xsl:otherwise> 
      <xsl:copy-of select="."/> 
     </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

Ma sto ottenendo il risultato "contenitore" di

<container label="Box" type="Box">156, 156, 154</container> 

quando ciò che Voglio è

<container label="Box" type="Box">154, 156</container> 

Be basso è il risultato completo che sto cercando di ottenere:

<c03 id="ref6488" level="file"> 
    <did> 
     <container label="Box" type="Box">154, 156</container> 
     <unittitle>Clinic Building</unittitle> 
     <unitdate era="ce" calendar="gregorian">1947</unitdate> 
    </did> 
    <c04 id="ref34582" level="file"> 
     <did> 
      <container label="Box" type="Box">156</container> 
      <container label="Folder" type="Folder">3</container> 
     </did> 
    </c04> 
    <c04 id="ref6540" level="file"> 
     <did> 
      <container label="Box" type="Box">156</container> 
      <unittitle>Contact prints</unittitle> 
     </did> 
    </c04> 
    <c04 id="ref6606" level="file"> 
     <did> 
      <container label="Box" type="Box">154</container> 
      <unittitle>Negatives</unittitle> 
     </did> 
    </c04> 
</c03> 

Grazie in anticipo per qualsiasi aiuto!

+0

buona domanda (+1). Vedi la mia risposta per una soluzione XSLT 1.0 che è più corta della soluzione XSLT 2.0 attualmente selezionata. :) –

risposta

1

provare il seguente codice:

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

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

    <xsl:template match="c03/did"> 
    <xsl:choose> 
     <xsl:when test="not(container)"> 
     <did> 
      <!-- If no c03 container item is found, look in the c04 level for one --> 
      <xsl:if test="../c04/did/container"> 
      <xsl:variable name="foo" select="../c04/did/container[@type='Box']/text()"/> 
      <!-- If a c04 container item is found, use the info to build a c03 version --> 
      <!-- Skip c03 container item, if still no c04 items found --> 
      <container label="Box" type="Box"> 

       <!-- Build container list --> 
       <!-- Test for more than one item, and if so, list them, --> 
       <!-- separated by commas and a space --> 
       <xsl:for-each select="distinct-values($foo)"> 
       <xsl:sort /> 
       <xsl:if test="position() &gt; 1">, </xsl:if> 
       <xsl:value-of select="." /> 
       </xsl:for-each> 
      </container> 
      <xsl:apply-templates select="*" /> 
      </xsl:if> 
     </did> 
     </xsl:when> 

     <!-- If there is a c03 container item(s), list it normally --> 
     <xsl:otherwise> 
     <xsl:copy-of select="."/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:template> 

</xsl:stylesheet> 

Sembra più o meno come l'uscita che si desidera:

<?xml version="1.0" encoding="UTF-8"?> 
<c03 id="ref6488" level="file"> 
    <did> 
     <container label="Box" type="Box">154, 156</container> 
     <unittitle>Clinic Building</unittitle> 
     <unitdate era="ce" calendar="gregorian">1947</unitdate> 
    </did> 
    <c04 id="ref34582" level="file"> 
     <did> 
     <container label="Box" type="Box">156</container> 
     <container label="Folder" type="Folder">3</container> 
     </did> 
    </c04> 
    <c04 id="ref6540" level="file"> 
     <did> 
     <container label="Box" type="Box">156</container> 
     <unittitle>Contact prints</unittitle> 
     </did> 
    </c04> 
    <c04 id="ref6606" level="file"> 
     <did> 
     <container label="Box" type="Box">154</container> 
     <unittitle>Negatives</unittitle> 
     </did> 
    </c04> 
</c03> 

Il trucco è quello di utilizzare <xsl:sort> e distinct-values() insieme. Vedere la (IMHO) grande libro da Michael Key "XSLT 2.0 e XPath 2.0"

+0

Sto usando XSLT2, quindi sono andato con questa soluzione, e ha funzionato benissimo. L'unica cosa era che dovevo commentare il \ Per qualche ragione, stava duplicando i nodi "unittitle". Grazie mille! – LOlliffe

+0

Condivido la tua alta opinione sul libro di Michael Kay. Sfortunatamente, poche persone/organizzazioni sono passate a XSLT 2.0. –

0

Il seguente XSLT 1.0 trasformazione fa quello che cerchi

<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
> 
    <xsl:output encoding="utf-8" /> 

    <!-- key to index containers by these three distinct qualities: 
     1: their ancestor <c??> node (represented as its unique ID) 
     2: their @type attribute value 
     3: their node value (i.e. their text) --> 
    <xsl:key 
    name = "kContainer" 
    match = "container" 
    use = "concat(generate-id(../../..), '|', @type, '|', .)" 
    /> 

    <!-- identity template to copy everything as is by default --> 
    <xsl:template match="node()|@*"> 
    <xsl:copy> 
     <xsl:apply-templates select="node()|@*" /> 
    </xsl:copy> 
    </xsl:template> 

    <!-- special template for <did>s without a <container> child --> 
    <xsl:template match="did[not(container)]"> 
    <xsl:copy> 
     <xsl:copy-of select="@*" /> 
     <container label="Box" type="Box"> 
     <!-- from subordinate <container>s of type Box, use the ones 
      that are *the first* to have that certain combination 
      of the three distinct qualities mentioned above --> 
     <xsl:apply-templates mode="list-values" select=" 
      ../*/did/container[@type='Box'][ 
      generate-id() 
      = 
      generate-id(
       key(
       'kContainer', 
       concat(generate-id(../../..), '|', @type, '|', .) 
      )[1] 
      ) 
      ] 
     "> 
      <!-- sort them by their node value --> 
      <xsl:sort select="." data-type="number" /> 
     </xsl:apply-templates> 
     </container> 
     <xsl:apply-templates select="node()" /> 
    </xsl:copy> 
    </xsl:template> 

    <!-- generic template to make list of values from any node-set --> 
    <xsl:template match="*" mode="list-values"> 
    <xsl:value-of select="." /> 
    <xsl:if test="position() &lt; last()"> 
     <xsl:text>, </xsl:text> 
    </xsl:if> 
    </xsl:template> 

</xsl:stylesheet> 

Returns

<c03 id="ref6488" level="file"> 
    <did> 
    <container label="Box" type="Box">154, 156</container> 
    <unittitle>Clinic Building</unittitle> 
    <unitdate era="ce" calendar="gregorian">1947</unitdate> 
    </did> 
    <c04 id="ref34582" level="file"> 
    <did> 
     <container label="Box" type="Box">156</container> 
     <container label="Folder" type="Folder">3</container> 
    </did> 
    </c04> 
    <c04 id="ref6540" level="file"> 
    <did> 
     <container label="Box" type="Box">156</container> 
     <unittitle>Contact prints</unittitle> 
    </did> 
    </c04> 
    <c04 id="ref6606" level="file"> 
    <did> 
     <container label="Box" type="Box">154</container> 
     <unittitle>Negatives</unittitle> 
    </did> 
    </c04> 
</c03> 

Il generate-id() = generate-id(key(...)[1]) parte è il cosiddetto raggruppamento di Muenchian. A meno che tu non possa usare XSLT 2.0, questa è la strada da percorrere.

2

Non è necessaria una soluzione XSLT 2.0 per questo problema.

Ecco una soluzione XSLT 1.0, che è più compatta di quella XSLT attualmente selezionato 2.0 soluzione (35 linee vs. 43 linee):

<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:key name="kBoxContainerByVal" 
    match="container[@type='Box']" use="."/> 

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

<xsl:template match="c03/did[not(container)]"> 
    <xsl:copy> 

    <xsl:variable name="vContDistinctValues" select= 
    "/*/*/*/container[@type='Box'] 
      [generate-id() 
      = 
      generate-id(key('kBoxContainerByVal', .)[1]) 
      ] 
      "/> 

    <container label="Box" type="Box"> 
     <xsl:for-each select="$vContDistinctValues"> 
     <xsl:sort data-type="number"/> 

     <xsl:value-of select= 
     "concat(., substring(', ', 1 + 2*(position() = last())))"/> 
     </xsl:for-each> 
    </container> 
    <xsl:apply-templates/> 
    </xsl:copy> 
</xsl:template> 
</xsl:stylesheet> 

Quando questa trasformazione viene applicata sulla inizialmente previsto documento XML, il corretto, ha voluto risultato viene prodotto:

<c03 id="ref6488" level="file"> 
    <did> 
     <container label="Box" type="Box">156, 154</container> 
     <unittitle>Clinic Building</unittitle> 
     <unitdate era="ce" calendar="gregorian">1947</unitdate> 
    </did> 
    <c04 id="ref34582" level="file"> 
     <did> 
     <container label="Box" type="Box">156</container> 
     <container label="Folder" type="Folder">3</container> 
     </did> 
    </c04> 
    <c04 id="ref6540" level="file"> 
     <did> 
     <container label="Box" type="Box">156</container> 
     <unittitle>Contact prints</unittitle> 
     </did> 
    </c04> 
    <c04 id="ref6606" level="file"> 
     <did> 
     <container label="Box" type="Box">154</container> 
     <unittitle>Negatives</unittitle> 
     </did> 
    </c04> 
</c03> 

Aggiornamento:

Non ho notato il requisito che i numeri di contenitore debbano apparire ordinati. Ora la soluzione riflette questo.

+0

La soluzione non ordina l'elenco, che è stato richiesto nella domanda. Facilmente rimediato aggiungendo '' all'interno del ciclo 'xsl: for-each'. – markusk

+0

@markusk: Grazie, di solito sono assonnato al mattino presto. '' ha anche bisogno di 'data-type =" numero "' in questo caso. –

1

Una versione XSLT 2.0 leggermente più corta, che combina gli approcci di altre risposte. Si noti che l'ordinamento è alfabetico, in modo che se vengono trovate le etichette "54" e "156", l'output sarà "156, 54". Se è necessario un ordinamento numerico, utilizzare <xsl:sort select="number(.)"/> anziché <xsl:sort/>. soluzione

<xsl:stylesheet version="2.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="c03/did[not(container)]"> 
     <xsl:variable name="containers" 
         select="../c04/did/container[@label='Box'][text()]"/> 
     <xsl:copy> 
      <xsl:copy-of select="@*"/> 
      <xsl:if test="$containers"> 
       <container label="Box" type="Box"> 
        <xsl:for-each select="distinct-values($containers)"> 
         <xsl:sort/> 
         <xsl:if test="position() != 1">, </xsl:if> 
         <xsl:value-of select="."/> 
        </xsl:for-each> 
       </container> 
      </xsl:if> 
      <xsl:apply-templates select="node()"/> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 
+0

+1. Questa è ancora la soluzione XSLT 2.0 più corta! :) –

1

Un vero XSLT 2.0, anche abbastanza breve:

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    exclude-result-prefixes="xs" 
> 
    <xsl:output omit-xml-declaration="yes" indent="yes"/> 

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

    <xsl:template match="c03/did[not(container)]"> 
    <xsl:copy> 
     <xsl:copy-of select="@*"/> 

     <xsl:variable name="vContDistinctValues" as="xs:integer*"> 
     <xsl:perform-sort select= 
      "distinct-values(/*/*/*/container[@type='Box']/text()/xs:integer(.))"> 
      <xsl:sort/> 
     </xsl:perform-sort> 
     </xsl:variable> 

     <xsl:if test="$vContDistinctValues"> 
     <container label="Box" type="Box"> 
      <xsl:value-of select="$vContDistinctValues" separator=","/> 
     </container> 
     </xsl:if> 
     <xsl:apply-templates/> 
    </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 

Do atto:

  1. L'uso di tipi evita la necessità di specificare la data-type in <xsl:sort/>.

  2. L'uso dell'attributo separator di <xsl:value-of/>

+1

+1 Ben fatto. Tuttavia, sappiamo che l'elemento 'c03' sarà la radice? Il poster affermava solo che l'input sarebbe "simile", quindi mi sento un po 'più a mio agio con gli XPath relativi (es. '../ c04/container', o forse' ../*/ container') invece di quelli assoluti ('/ */*/*/container'). In questo modo, il foglio di stile funziona anche se l'elemento 'c03' appare più in basso nella struttura del documento. – markusk

+0

@markusk Bel commento ancora e grazie per l'upvote! Sì, siamo testimoni di come gli OP cambiano costantemente la definizione dei loro problemi. A volte sono tentato di agire come un indovino, ma anche questo ha un rischio in sé. Inoltre, il codice XSLT diventa sempre più non direttamente correlato all'XML effettivo e quindi più difficile da comprendere. Questo è il motivo per cui, in casi come questi, generalmente preferisco rimanere il più vicino possibile all'XML pubblicato. Ho scritto molte volte il codice XSLT più generale, ad es. XPath Visualizer/FXSL, ma lo scopo qui è di essere il più specifico/utile possibile. :) –