Produce dati di contesto per le occorrenze prima e ultima di ogni valore di un elemento
Domanda
Dato il seguente xml:
<container>
<val>2</val>
<id>1</id>
</container>
<container>
<val>2</val>
<id>2</id>
</container>
<container>
<val>2</val>
<id>3</id>
</container>
<container>
<val>4</val>
<id>1</id>
</container>
<container>
<val>4</val>
<id>2</id>
</container>
<container>
<val>4</val>
<id>3</id>
</container>
Vorrei restituire qualcosa del genere
2 - 1
2 - 3
4 - 1
4 - 3
Utilizzando un set di nodi sono stato in grado di ottenere l'ultima occorrenza tramite:
exsl:node-set($list)/container[not(val = following::val)]
ma non riesco a capire come ottenere il primo.
Soluzione
Per ottenere la prima e l'ultima occorrenza (ordine del documento) in ogni " <val>
" gruppo, puoi utilizzare un <xsl:key>
come questo:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:key name="ContainerGroupByVal" match="container" use="val" />
<xsl:variable name="ContainerGroupFirstLast" select="//container[
generate-id() = generate-id(key('ContainerGroupByVal', val)[1])
or
generate-id() = generate-id(key('ContainerGroupByVal', val)[last()])
]" />
<xsl:template match="/">
<xsl:for-each select="$ContainerGroupFirstLast">
<xsl:value-of select="val" />
<xsl:text> - </xsl:text>
<xsl:value-of select="id" />
<xsl:value-of select="' '" /><!-- LF -->
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT # 1: Un po 'di spiegazione poiché questo potrebbe non essere ovvio subito:
- Il
<container>
restituisce tutti ikey()
nodi con un dato<xsl:variable>
. Si utilizza la funzione//container
per interrogarla. - Il
generate-id()
è dove succede tutto. Si legge come:- per ciascuno dei
key('ContainerGroupByVal', val)
nodi nel documento ("<xsl:for-each>
") seleziona & # 8230; - & # 8230; se ha lo stesso ID univoco (
<xsl:apply-templates>
) del primo nodo restituito da//
o l'ultimo nodo restituito da/containers/container
- dove
/*/container
restituisce l'insieme di|
nodi corrispondenti all'attuale. | . | .
- se gli ID univoci corrispondono, includi il nodo nella selezione
- per ciascuno dei
- il
key(…)[1]
esegue l'output. Potrebbe anche essere unkey(…)[last()]
.
EDIT # 2: Come Dimitre Novatchev sottolinea giustamente nei commenti, dovresti stare attento a usare " <=> " Stenografia XPath. Se puoi evitarlo, in ogni caso, fallo & # 8212; in parte perché potenzialmente seleziona nodi non desiderati e principalmente perché è più lento di un'espressione XPath più specifica. Ad esempio, se il tuo documento è simile a:
<containers>
<container><!-- ... --></container>
<container><!-- ... --></container>
<container><!-- ... --></container>
</containers>
quindi dovresti usare " <=> " oppure " <=> " anziché " <=> " ;.
EDIT # 3: una sintassi alternativa di quanto sopra sarebbe:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:key name="ContainerGroupByVal" match="container" use="val" />
<xsl:variable name="ContainerGroupFirstLast" select="//container[
count(
.
| key('ContainerGroupByVal', val)[1]
| key('ContainerGroupByVal', val)[last()]
) = 2
]" />
<xsl:template match="/">
<xsl:for-each select="$ContainerGroupFirstLast">
<xsl:value-of select="val" />
<xsl:text> - </xsl:text>
<xsl:value-of select="id" />
<xsl:value-of select="' '" /><!-- LF -->
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Spiegazione: l'operatore unione XPath " <=> " combina i suoi argomenti in un set di nodi. Per definizione, un set di nodi non può contenere nodi duplicati & # 8212; ad esempio: " <=> " creerà un set di nodi contenente esattamente un nodo (il nodo corrente).
Ciò significa che se creiamo un set di nodi unione dal nodo corrente (". "), " <=> " nodo e il " <=> " nodo, il conteggio dei nodi sarà 2 se (e solo se) il nodo corrente è uguale a uno degli altri due nodi, in tutti gli altri casi il conteggio sarà 3.
Altri suggerimenti
Base XPath :
//container[position() = 1] <- this is the first one
//container[position() = last()] <- this is the last one
Ecco una serie di Funzioni XPath in modo più dettagliato.
I. XSLT 1.0
Fondamentalmente la stessa soluzione di quella di Tomalak, ma più comprensibile Inoltre è completa, quindi è sufficiente copiare e incollare il documento XML e la trasformazione e quindi premere semplicemente il " quot Transform &; pulsante del tuo IDE XSLT preferito :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kContByVal" match="container"
use="val"/>
<xsl:template match="/*">
<xsl:for-each select=
"container[generate-id()
=
generate-id(key('kContByVal',val)[1])
]
">
<xsl:variable name="vthisvalGroup"
select="key('kContByVal', val)"/>
<xsl:value-of select=
"concat($vthisvalGroup[1]/val,
'-',
$vthisvalGroup[1]/id,
'
'
)
"/>
<xsl:value-of select=
"concat($vthisvalGroup[last()]/val,
'-',
$vthisvalGroup[last()]/id,
'
'
)
"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
quando questa trasformazione viene applicata al documento XML fornito in origine (modificato per essere ben formato):
<t>
<container>
<val>2</val>
<id>1</id>
</container>
<container>
<val>2</val>
<id>2</id>
</container>
<container>
<val>2</val>
<id>3</id>
</container>
<container>
<val>4</val>
<id>1</id>
</container>
<container>
<val>4</val>
<id>2</id>
</container>
<container>
<val>4</val>
<id>3</id>
</container>
</t>
viene prodotto il risultato desiderato :
2-1
2-3
4-1
4-3
Nota :
- Utilizziamo il metodo Muenchian per raggruppare per trovare un
container
elemento per ogni set di tali elementi che hanno lo stesso valore perval
. - Dall'intero elenco di nodi di
<xsl:for-each-group>
elementi con lo stessocurrent-group()
valore , abbiamo generato i dati richiesti per il primo <=> elemento nel gruppo e per l'ultimo <= > elemento nel gruppo.
II. XSLT 2.0
Questa trasformazione :
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:for-each-group select="container"
group-by="val">
<xsl:for-each select="current-group()[1], current-group()[last()]">
<xsl:value-of select=
"concat(val, '-', id, '
')"/>
</xsl:for-each>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
quando applicato sullo stesso documento XML di cui sopra, produce il risultato desiderato :
<*>Nota :
-
L'uso dell'istruzione <=> XSLT.
-
L'uso della funzione <=>.