2009-05-07 10 views
26

Sto elaborando un file XML in cui voglio mantenere il conteggio del numero di nodi, in modo che possa usarlo come ID mentre scrivo nuovi nodi.In XSLT come si incrementa una variabile globale da un diverso ambito?

Al momento ho una variabile globale chiamata 'contatore'. Sono in grado di accedervi all'interno di un modello, ma non ho trovato un modo di manipolarlo all'interno di un modello.

Ecco una versione condensata del mio file XSLT:

<xsl:variable name="counter" select="1" as="xs:integer"/> 

<xsl:template match="/"> 
    <xsl:for-each select="section"> 
     <xsl:call-template name="section"></xsl:call-template> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template name="section"> 

    <!-- Increment 'counter' here --> 

    <span class="title" id="title-{$counter}"><xsl:value-of select="title"/></span> 
</xsl:template> 

Eventuali suggerimenti su come andare da qui?

risposta

44

Altri hanno già spiegato come le variabili sono immutabili - che non ci sono istruzioni di assegnazione in XSLT (come con linguaggi di programmazione puramente funzionali in generale).

Ho un'alternativa alle soluzioni che sono state proposte finora. Evita il passaggio dei parametri (che è verboso e brutto in XSLT - anche io lo ammetterò).

In XPath, si può semplicemente contare il numero di <section> elementi che precedono quella attuale:

<xsl:template name="section"> 
    <span class="title" id="title-{1 + count(preceding-sibling::section)}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 

(Nota: la formattazione del codice spazi non apparirà nel vostro risultato, come spazi bianchi solo i nodi di testo viene rimosso automaticamente dal foglio di stile.)

Un grande vantaggio di questo approccio (al contrario di usare position()) è che dipende solo dal nodo corrente, non sull'elenco dei nodi corrente. Se in qualche modo hai modificato l'elaborazione (ad esempio, così <xsl:for-each> elaborava non solo sezioni ma anche qualche altro elemento), il valore di position() non corrisponderebbe più necessariamente alla posizione degli elementi <section> nel documento. D'altra parte, se si utilizza count() come sopra, corrisponderà sempre alla posizione di ciascun elemento <section>. Questo approccio riduce l'accoppiamento con altre parti del codice, che in genere è una cosa molto buona.

Un'alternativa a count() sarebbe quella di utilizzare l'istruzione <xsl:number>. E 'il comportamento di default sarà numerare tutti gli elementi come il nome allo stesso livello, che risulta essere ciò che si vuole:

<xsl:template name="section"> 
    <xsl:variable name="count"> 
    <xsl:number/> 
    </xsl:variable> 
    <span class="title" id="title-{$count}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 

Si tratta di un trade-off in prolissità (che prescrivono una dichiarazione variabile aggiuntiva se si desidera continuare a utilizzare il modello di valore dell'attributo parentesi graffe), ma solo leggermente, in quanto semplifica drasticamente anche l'espressione XPath.

C'è ancora più spazio per migliorare. Mentre abbiamo rimosso la dipendenza dall'elenco dei nodi corrente, dipendiamo ancora dal nodo corrente. Questo, in sé e per sé, non è una cosa negativa, ma non è immediatamente chiaro dal punto di vista del template quale sia il nodo corrente. Tutto ciò che sappiamo è che il modello è denominato "section"; per sapere con certezza cosa viene elaborato, dobbiamo cercare altrove nel nostro codice. Ma anche questo non deve essere il caso.

Se mai senti portato a utilizzare <xsl:for-each> e <xsl:call-template> insieme (come nel tuo esempio), un passo indietro e capire come utilizzare <xsl:apply-templates> invece.

<xsl:template match="/doc"> 
    <xsl:apply-templates select="section"/> 
</xsl:template> 

<xsl:template match="section"> 
    <xsl:variable name="count"> 
    <xsl:number/> 
    </xsl:variable> 
    <span class="title" id="title-{$count}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 

Non solo questo approccio meno dettagliato (<xsl:apply-templates/> sostituisce sia <xsl:for-each> e <xsl:call-template/>), ma diventa anche immediatamente chiaro quale sia il nodo attuale. Tutto quello che devi fare è esaminare l'attributo match e immediatamente sai che stai elaborando un elemento <section> e che gli elementi <section> sono ciò che stai contando.

Per una spiegazione sintetica di come funzionano le regole modello (ovvero gli elementi <xsl:template> con attributo match), vedere "How XSLT Works".

+0

Grazie mille !! Questo post e la risposta è stata incredibilmente utile – anpatel

+0

Prego! Mi fa piacere che l'abbia trovato utile. –

+0

Mi dispiace, Evan, ma questa è una soluzione molto inefficiente (O (N^2)). Una soluzione che utilizza il passaggio dei parametri può essere solo O (N). Tutto questo parlare di "verbosità" è proprio questo - verbosità e non menzionare una parola sull'efficienza. Potresti rendere questa risposta più utile al lettore se menzioni la complessità temporale della soluzione proposta e la confronta con altre possibili soluzioni. Per questi motivi considero questa risposta come di tipo light-tutorial e non pratica per il lavoro di produzione. –

2

le variabili sono localmente con ambito e lette solo in xslt.

+0

vedo.Conoscete un approccio che posso adottare per ottenere ciò che cerco? – Marcel

+0

Prima di tutto vorrei dire che dovresti evitare di usare il costrutto foreach e il modello di chiamata. Si tratta di istruzioni procedurali e XSLT è ricorsivo. Quindi dovresti pensarlo ricorsivamente invece procedurale. L'utente che @Bewarned sta mostrando è un modo valido per incrementare il contatore tramite un parametro. Quindi meglio utilizzare il modello di applicazione con un parametro che aggiunge 1 ogni volta che viene chiamato. Commenta questo se non sono chiaro. – Luixv

8

Le variabili XSLT non possono essere modificate. Avrai passato il valore da modello a modello.

Se si utilizza XSLT 2.0, è possibile avere parametri e utilizzare il tunneling per propagare la variabile ai modelli corretti.

Il modello sarà simile a questo:

<xsl:template match="a"> 
<xsl:param name="count" select="0"> 
    <xsl:apply-templates> 
    <xsl:with-param select="$count+1"/> 
    </xsl:apply-templates> 
</xsl:template> 

Anche guardare utilizzando generare-id(), se si desidera creare ID.

+2

+1 per generare-id() grazie! –

0

Non ho provato da solo, ma è possibile provare a passare un parametro al modello. Nel tuo primo modello imposti il ​​parametro su count() (o current() forse?) All'interno dell'istruzione for-each e poi passa quel valore al tuo template "section".

Ecco più su passing parameters to templates

2

A seconda del processore XSLT, si può essere in grado di introdurre le funzioni di script nella vostra XLST. Ad esempio, la libreria Microsoft XML supporta l'inclusione di javascript. Vedere http://msdn.microsoft.com/en-us/library/aa970889(VS.85).aspx per un esempio. Questa tattica ovviamente non funzionerà se hai intenzione di implementare/eseguire XSLT su browser client pubblici; deve essere eseguito da un processore XSLT specifico.

+0

Ho già usato questo trucco, ma dovrebbe essere fatto solo come ultima risorsa, dove strutturarlo lungo linee immutabili/funzionali sarebbe proibitivo. Ma funziona. In alcuni scenari (come .NET) è possibile utilizzare gli oggetti di estensione per fare la stessa cosa al di fuori di xslt, ma ancora: questo non lo rende una grande idea. –

1

È possibile utilizzare la funzione position() per fare ciò che si desidera. Assomiglierebbe a questo.

<xsl:template match="/"> 
    <xsl:for-each select="section"> 
    <xsl:call-template name="section"> 
     <xsl:with-param name="counter" select="{position()}"/> 
    </xsl:call-template> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template name="section"> 
    <xsl:param name="counter"/> 
    <span class="title" id="title-{$counter}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 
+0

L'attributo select di xsl: with-param è un'espressione, non una stringa che può utilizzare AVT. – jelovirt

+0

Inoltre, non è necessario passare il valore di position(), poiché non cambierà l'elenco dei nodi corrente. Puoi facilmente accedere allo stesso valore, usando position(), all'interno del modello "sezione". –

6

Le variabili in XSLT sono immutabili, pertanto è necessario valutare il problema tenendo conto di ciò. Si potrebbe usare sia position() direttamente:

<xsl:template match="/"> 
    <xsl:for-each select="section"> 
     <xsl:call-template name="section"/> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template name="section"> 
    <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span> 
</xsl:template> 

o in un modello orientato modo più:

<xsl:template match="/"> 
    <xsl:apply-templates select="section"/> 
</xsl:template> 

<xsl:template match="section"> 
    <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span> 
</xsl:template> 
0

Usa <xsl:variable name="RowNum" select="count(./preceding-sibling::*)" /> e $ ROWNUM come valore di incremento.

Esempio: <xsl:template name="ME-homeTiles" match="Row[@Style='ME-homeTiles']" mode="itemstyle"> <xsl:variable name="RowNum" select="count(./preceding-sibling::*)" /> ...<a href="{$SafeLinkUrl}" class="tile{$RowNum}"><img ....></a>

Questo creerà le classi per legame con i valori tile1, tile2, tile3 ecc ...