In questa lezione esaminiamo i principali costrutti di XSLT, cercando di partire dal modo di procedere tipico dei linguaggi di programmazione e di scripting.
Materiale da trasformazione
Prima di considerare le possibilità offerte dal linguaggio di trasformazione in esame, servono dati più significativi di quelli sin'ora considerati sui quali lavorare. L'esempio, comunque elementare, di formato XML sorgente che utilizzeremo esemplifica una casella di posta, ipoteticamente frutto di una query ad opportuni database, che contiene i messaggi diretti ai vari utenti di un sistema.
Le informazioni strutturali che guideranno nella creazione del foglio di stile possono essere ricavate dal DTD (oppure lo Schema) del formato di input.
Partire dall'esame dei dati stessi, al contrario, può condurre ad errori inaspettati: un file XML deve infatti rispettare il DTD corrispondente ma non deve necessariamente presentare tutte le possibili combinazioni degli elementi che quest'ultimo prevede; non può pertanto garantire un comportamento corretto per ogni input che risulti valido.
Esaminiamo il codice del file esempio2.xml:
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="esempio2.xsl"?> <!DOCTYPE mailbox [ <!ELEMENT mailbox (message*)> <!ELEMENT message (from, to+, content)> <!ELEMENT from (#PCDATA)> <!ELEMENT to (#PCDATA)> <!ELEMENT content ANY> <!ATTLIST message id CDATA #REQUIRED date CDATA #REQUIRED > <!ENTITY % HTMLtags PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> %HTMLtags; ]> <mailbox> <message id="msg01" date="10/03/2006"> <from>Gambadilegno</from><to>Mickey</to> <content>Prova a prendermi...</content> </message> <message id="msg02" date="11/03/2006"> <from>Minnie</from><to>Mickey</to> <content><strong>Ricordati dell'appuntamento di stasera!</strong></content> </message> <message id="msg02" date="12/03/2006"> <from>Pippo</from><to>Mickey</to> <content>Domani a cena da voi ?</content> </message> </mailbox>
Abbiamo l'elemento radice mailbox che può contenere un numero, anche nullo, di elementi message (notare il simbolo '*' nel modello dei contenuti di mailbox in terza riga). Ogni messaggio è connotato da due attributi, id e date, per l'identificativo univoco e la data d'ogni messaggio; dai sottoelementi from, to e content che contengono mittente, uno o più destinatari (notare il simbolo '+' nel modello dei contenuti di message in quarta riga) e il testo del messaggio.
Quest'ultimo può infine contenere un qualsiasi elemento del linguaggio XHTML, grazie all'entità parametro %HTMLtags; in modo da consentire l'inserimento di un messaggio formattato con tutta l'espressività di una pagina Web. Nella scelta dei nomi per gli elementi ho utilizzato l'inglese e questo rappresenta la pratica consigliata: è infatti buona prassi che i tag utilizzati abbiano nomi intuitivi e comprensibili per il maggior numero di persone.
Definire un template
Per sottolineare il nuovo approccio introdotto da XSLT, conviene innanzitutto realizzare il classico modello di esecuzione procedurale di JavaScript e mostrare solo successivamente quali possibilità abbia aggiunto il nuovo linguaggio.
L'equivalente delle procedure è rappresentato dai template con nome, nei quali cioè l'attributo match è stato sostituito dall'attributo name. In questo modo si può invocare il template in un punto qualsiasi del foglio di stile semplicemente chiamandolo per nome.
Se volessimo ad esempio produrre una riga di tabella per ogni messaggio contenuto in esempio2.xml, sarebbe sufficiente creare, nel foglio di stile XLS, un apposito template dal nome arbitrario "list" che produca ogni riga e richiamarlo dopo aver generato il contesto generale della pagina:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:param name="user">Mickey</xsl:param> <xsl:output method="html" /> <xsl:template match="/"> <html> <head> <title>Il secondo foglio di stile</title> </head> <body> <table> <tr><td colspan="3">Lista messaggi per l'utente <strong><xsl:value-of select="$user" /></strong></td></tr> <tr><td>Data:</td><td>Mittente:</td><td>Messaggio:</td></tr> <!-- qui viene richiamato il template --> <xsl:call-template name="list" /> </table> </body> </html> </xsl:template> <!-- ... -->
Con l'elemento call-template è possibile richiamare il template con il nome indicato; al suo interno troviamo l'elemento for-each, l'unico comando d'iterazione messo a disposizione da XSLT (come vedremo esiste un modo più diretto per realizzare lo stesso effetto) simile al "ciclo for" di un comune linguaggio di programmazione: in questo (e altri) elementi è possibile definire quali elementi selezionare mediante l'attributo select, che può contenere una qualsiasi espressione XPath. Lo stesso attributo, all'interno dell'elemento value-of, consente invece di inserire nell'output gli elementi selezionati.
L'aspetto di questa espressione appare molto simile ai percorsi di un file system ma sottointende una parte importantissima di XPath, l'asse (axis) lungo il quale ci si muove ad ogni passo, scandito dalla barra a destra.
Un doppio simbolo di due punti divide l'asse (in questo caso child, indicante i nodi figlio) dal nome del nodo da individuare. Il valore reale dell'attributo select appena mostrato è il seguente:
/child::mailbox/child::message
L'asse che consente di muoversi in direzione opposta a child è, come potete immaginare, parent (genitore) e lo si può abbreviare con una coppia di punti, analogamente a quanto avviene nei file system con la directory precedente. All'interno del ciclo for-each, che ha come contesto gli elementi message del file sorgente, potremmo quindi raggiungere l'elemento mailbox che li racchiude con una delle seguenti espressioni equivalenti:
parent::mailbox ../mailbox
Avendo l'elemento mailbox la pura funzione di contenitore, sarebbe utile non doverlo nemmeno considerare e passare direttamente dalla radice i messaggi saltando una "generazione" di elementi: questo è possibile attraverso l'espressione descendant::message, che è in grado di selezionare figli, nipoti etc. a partire dall'elemento corrente. Questo asse è abbreviabile con una doppia barra a sinistra '//'.
Da notare come il semplice asse descendant (discendente) non sia in grado di escludere alcun elemento in base al nome, nel nostro caso mailbox, ma solo in base alla posizione relativa nella genealogia del documento XML in esame: l'espressione descendant::* produrrebbe quindi una iterazione anche per questo elemento. La direzione opposta è ovviamente indicata dall'asse ancestor (antenato).
Un altro asse fondamentale è attribute, che permette di selezionare i nodi di tipo attributo: nel foglio di stile appena visto è stato abbreviato con il carattere '@' al fine di estrarre la data di un messaggio. In tutto sono ben tredici gli assi previsti da XPath e costituiscono la principale forza di XSLT, dal momento che gran parte della flessibilità che quest'ultimo offre discende appunto dal modo innovativo di attraversare l'alberatura di un documento XML.
Passaggio di parametri
Tornando al nostro intento iniziale, vorremmo poter parametrizzare l'invocazione dei template come come faremmo per una chiamata di funzione in JavaScript; nell'esempio potremmo voler visualizzare solo i messaggi relativi ad un preciso utente, celando gli altri nella privacy più assoluta. Questo consisterà innanzitutto nel modificare l'attributo select del ciclo for-each estendendo l'espressione XPath con il terzo elemento che la compone, il predicato, racchiuso tra parentesi quadre:
<xsl:for-each select="/mailbox/message[ to/text() = $user ]">
Con la funzione standard text() andremo a prelevare il testo contenuto nell'elemento to per confrontarlo poi con il valore del parametro user, indicato con il carattere '$' in analogia con la shell di DOS e Unix. Sarà necessario dichiarare il parametro, fornendo eventualmente un valore di default, all'inizio del foglio di stile (in questo caso si tratta di un parametro globale) oppure all'interno del template che ne farà uso:
<xsl:param name="user">anonimo</xsl:param>
Come default utilizzo il valore "anonimo" che si suppone non appartenere ad alcun utente in particolare. I parametri locali andranno attualizzati prima della chiamata al template che li utilizza; questo può essere fatto all'interno dell'elemento call-template:
<xsl:call-template name="list"> <xsl:with-param name="user">Mickey</xsl:with-param> </xsl:call-template>
Se il parametro è globale, è possibile assegnargli un valore all'esterno dell'esecuzione del foglio di stile, tipicamente nella pagina ASP, JSP o PHP che invoca la trasformazione. Nel nostro caso direttamente dalla linea di comando:
xt esempio2.xml esempio2.xsl esempio2.html utente=Mickey
Istruzioni condizionali
Un'altra caratteristica essenziale dei linguaggi di programmazione è rappresentata dalla possibilità di eseguire scelte differenti in base ai dati che vengono elaborati. Potremmo ad esempio decidere di visualizzare in rosa i messaggie dell'utente "Minnie" e in rosso quelli dell'utente "Pippo". Il più semplice elemento messo a disposizione da XSLT è, guarda caso, if e consente di specificare nell'attributo test quale sia la condizione che si deve verificare affinchè il codice contenuto nell'elemento venga eseguito:
<tr> <xsl:if test="from/text() = 'Minnie' "> <xsl:attribute name="style">background-color: #FFCCFF</xsl:attribute> </xsl:if> <!-- ... --> </tr>
Incontriamo così anche l'elemento attribute (da non confondere con l'asse omonimo) che consente di inserire un attributo dinamico nell'elemento che lo precede; applicando la condizione "from/text() = 'Pluto'" è possibile evidenziare il secondo tipo di messaggi che ci interessa.
Le due condizioni rimangono tuttavia indipendenti e potrebbero in teoria verificarsi contemporaneamente. Per evitare questo non disponiamo di un costrutto equivalente al classico if–then–else; disponiamo invece di un elemento con la stessa semantica del comando switch:
<xsl:choose> <xsl:when test="from/text() = 'Minnie' "> <xsl:attribute name="style">background-color: #FFCCFF</xsl:attribute> </xsl:when> <xsl:when test="from/text() = 'Pippo' "> <xsl:attribute name="style">background-color: #FF0000</xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style">background-color: #333333</xsl:attribute> </xsl:otherwise> </xsl:choose>
Questo elemento consente inoltre di specificare un comportamento di default, nel caso nessuna delle clausole contenute negli attributi test risultino verificate. Sembra proprio che, al di la della sintassi differente, sia possibile rendere con un foglio di stile la stessa logica di un linguaggio di scripting tradizionale.
Se però osserviamo l'output generato dall'esecuzione di esempio2.xsl sul file di input, ci renderemo conto che qualcosa non è stato copiato correttamente: il messaggio di Minnie "Ricordati dell'appuntamento di stasera!" conteneva una formattazione in grassetto, mediante il tag <strong> di (X)HTML, che è invece scomparso nel corso dell'elaborazione.
Considerando che l'elemento content potrebbe contenere liste, immagini, form e tutto quanto possiamo trovare in una comune pagina Web, appare chiaro che la strategia di andare a pescare selettivamente gli elementi di un sorgente XML (come abbiamo appena fatto nella compilazione della tabella) rischia di limitare il risultato finale laddove non vi sia una struttura così cristallina e definita, come nel caso della caotica formattazione di HTML.