XSLT
ed XML. Oramai conosciamo tutti il significato di questi
acronimi. Stanno rispettivamente per eXtendible Styles
Language e eXtendible Maukup Language. Il primo è
composto da un linguaggio di trasformazione (xslt) e
uno di formattazione (xsl-fo).
Il linguaggio xslt (http://www.w3.org/Style/XSL/) permette
la manipolazione vera e propria dei documenti, mentre
il linguaggio xsl-fo permette la formattazione e l'applicazione
degli stili ai documenti. L'obiettivo di questo articolo
è di spiegare cosa verrà creato durante
il parsing tra l'xsl e l'xml. Per spiegare cosa avviene
durante la trasformazione abbiamo bisogno di un' esempio
xsl e un'esempio xml.
Il
file xml di riferimento ha la struttura prevista per
gli articolo di Mokabyte:
<body>
<paragraph>La comunicazione Java-Javascript</paragraph>
<text>Java accede al DOM [1] del Browser,
</text>
<text>Come si vedrà nel paragrafo successivo
</text>
<text>Qui di seguito è riportato uno schema
di
</text>
<code>
<break>import java.awt.*;</break>
<break>class MyApplet extends Applet {</break>
<break>public void init() {</break>
<break>JSObject win = JSObject.getWindow(this);</break>
<break>JSObject doc = (JSObject) win.getMember("document");</break>
<break>JSObject loc = (JSObject) doc.getMember("location");</break>
<break></break>
<break>String s = (String) loc.getMember("href");
</break>
<break>win.call("f", null);
// Call f() in HTML page</break>
<break>}</break>
<break>}</break></code>
<text>
</text>
<text>
</text>
</body>
Ovviamente
questo xml è solo una parte dell'xml originale.
È stato riformattato per essere più leggibile
nell'articolo.
Iniziamo
a scrivere l'xsl
In primo luogo è fondametale individuare l'obbiettivo
finale. In questo caso si vuole che l'output prodotto
sia codice HTML che rappresenti l'articolo.
Successivamente bisogna stabilire i punti necessari
al raggiungimento dell'obbiettivo. Le regole possono
essere così riassumibili:
- Ogni
tag <paragraph> e <text> diventi un elemento
<P> di HTML.
- L'elemento
<code> deve diventare un'elemento HTML <code>;
- E
ogni elemento <break> deve essere trasformato
nel famoso elemento <br> di HTML.
E'
indispensabile sapere che un file xsl deve essere un
documento XML ben formato.
Per questo motivo il foglio di stile deve iniziare con
una dichiarazione xml corretta:
<?xml
version="1.0"?>
version
indica la versione della tecnologia XML utilizzata.
Un altro elemento importante in xsl è stylesheet:
<xsl:stylesheet
version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
La
sua importanza è data dal fatto che il codice
che ne conseguirà dovrà adattarsi a quelle
che sono le regole di trasformazione e/o formattazione
xslt.
Anche qui ritroviamo l'attributo Version, il quale indica
la versione xslt implementata dal processore. Si presenta
un altro attributo nel tag stylesheet: xmlns:xsl, che
in questo caso definisce il namespace "xsl".
Questo attributo dichiara al processore di che tipo
sono gli elementi usati all'interno del foglio di stile
e tramite l'url specificato identifica dove tali elementi
sono definiti.
Inoltre esplicita che ogni elemento avrà un prefisso;
in questo caso "xsl:" (vedi elemento stylesheet
sopra citato).
Ulteriori esempi di namespace possono essere
Il
prossimo elemento è template match. Con questo
elemento si è in grado di posizionarsi in qualunque
punto dell'xml.
Per esempio:
<xsl:template
match="/">
Assegnando
il valore "/" (slash) all'attributp match,
accade che il processore quando esegue la trasformazione
si posiziona sulla radice del documento xml avendo visibilità
di tutti gli elementi figli contenuti in essa.
<xsl:template
match="body">
Sostituiendo
"body" al volore assegnato a match, il processore
si posiziona direttamente sul tag body. Ovviamente nell'xml
usato come esempio il tag body coincide con il tag radice,
ma se al posto di body l'attributo match fosse stato
valorizzato con code, il processore si sarebbe posizionato
direttamente sul tag code avendo visibilità solo
dei nodi figli dell'elemento code.
Infatti valorizzare l'attributo match con il nome di
un tag è utile se è necessario usare solo
una parte dell'xml a disposizione oppure se si desidera
scrivere codice più compatto.
L'inizio
dell'HTML
Verrà creato ora il primo elemento dell'html
utilizzando il linguaggio xsl. Per fare ciò si
ha bisogno dell'elemento xsl element:
<xsl:element
name="html">
Il
risultato di questa istruzione sarà:
<html>
In
questo modo si costruiranno anche il tag body, e ogni
elemento necessario dell'html.
Di seguito è riportato l'xsl prodotto fino a
ora:
<?xml
version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="body">
<xsl:element name="html">
<xsl:element name="body">
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Tornando
all'xml, si è detto che ogni tag paragraph e
text devono diventare entrambi degli elementi <P>
di html. Questo si ottiene con il seguente tag:
<xsl:for-each
select="child::*">
Con
questa istruzione si esegue un ciclo. La descrizione
corretta in italiano è: "Per ogni nodo figlio"
oppure "per tutti i nodi figli".
Siccome i "figli" sono di tipo diverso, durante
l'esecuzione di un ciclo si deve capire su quale nodo
è posizionato il processore. Per farlo si utilizza
l'elemento choose. Questo elemento permette di fare
delle scelte multiple. In poche parole è l'equivalente
di un
if(
){
}
else if(
){}
else(
){
}
in
java. Si prenda in esame la seguente parte di codice:
<xsl:choose>
<xsl:when test="name()='paragraph'">
</xsl:when>
<xsl:when test="name()='text'">
</xsl:when>
</xsl:choose>
La
funzione name() ritorna il nome del nodo corrente. Il
test consiste nel verificare se il nome del nodo corrente
corrisponde al tag paragraph. Se questa condizione è
vera, verrà eseguita una specifica parte di codice.
Contrariamente, verrà processata un'altra parte
di codice se il nome del nodo corrente non è
uguale a paragraph ma a text.
Sia per l'elemento text, sia per l'elemento code quando
saranno processati, sarà creato per ognuno un
elemento p che racchiude il testo contenuto nell'elemento
corrente.
<xsl:choose>
<xsl:when test="name()='paragraph'">
<xsl:element name="p">
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
<xsl:when test="name()='text'">
<xsl:element name="p">
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
</xsl:choose>
Si
applica lo stesso ragionamento anche quando l'elemento
corrente è code ma creando anziché un
elemento p un elemento html code. Si tenga presente
che code contiene ulteriori nodi.
La seguente porzione di file XML è una rielaborazione
dello schema originale. In questo formato sarà
più facile sviluppare l'xsl di formattazione
del codice:
<code>
<break>import java.awt.*;</break>
<break>class MyApplet extends Applet {</break>
<break>public void init() {</break>
<break>JSObject win = JSObject.getWindow(this);</break>
<break>JSObject doc = (JSObject) win.getMember("document");</break>
<break>JSObject loc = (JSObject) doc.getMember("location");</break>
<break></break>
<break>String s = (String) loc.getMember("href");
// document.location.href</break>
<break>win.call("f", null);
// Call f() in HTML page</break>
<break>}</break>
<break>}</break>
</code>
Originariamente
questa porzione di codice era così:
<code>import
java.awt.*;<break/>class MyApplet extends Applet
{<break/>public void init() {<break/>JSObject
win = JSObject.getWindow(this); <break/>JSObject
doc = (JSObject) win.getMember("document");
<break/>JSObject loc = (JSObject) doc.getMember("location");
<break/><break/>String s = (String) loc.getMember("href");
// document.location.href<break/>win.call("f",
null); // Call f() in HTML page<break/>}<break/>
}<break/>
</code>
Utilizzando
questo xsl per trattare la parte relativa al tag <code>:
<xsl:when test="name()='code'">
<xsl:element name="CODE">
<xsl:for-each select="child::*">
<xsl:choose>
<xsl:when test="name()='break'">
<xsl:element name="BR"/>
<xsl:value-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:when>
Si
ottiene il risultato seguente:
<CODE>
<BR>
<BR>
<BR>
...
<BR>
</CODE>
Non
viene quindi visualizzato nulla. Analizzando il codice
sopra riportato si nota che si preleva solo il contenuto
del tag break e non di code.
Un tentativo di correzione potrebbe essere il seguente:
<xsl:when test="name()='code'">
<xsl:element name="CODE">
<xsl:for-each select="child::*">
<xsl:choose>
<xsl:when test="name()='break'">
<xsl:element name="BR"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
Ma
si ottiene ancora un risultato indesiderato:
<CODE>
<BR>
...
<BR>
<BR>
<BR>import java.awt.*;class MyApplet extends Applet
{public void init() {JSObject win = JSObject.getWindow(this);JSObject
doc = (JSObject) win.getMember("document");JSObject
loc = (JSObject) doc.getMember("location");String
s = (String) loc.getMember("href"); // document.location.hrefwin.call("f",
null); // Call f() in HTML page}}
</CODE>
E'
essenziale capire su quale elemento ci si trova quando
è eseguito un ciclo, e con l'xsl riportato sopra
si è in grado di farlo, ma non si è in
grado di ottenere il risultato voluto.
Questo perché il ciclo eseguito è per
tutti i figli del nodo code e quando trova il tag break
deve sostituirlo con l'elemento BR e poi prelevare il
contenuto di code. Di conseguenza crea prima un elemento
BR per ogni tag break processato e solo successivamente
ne scrive il contenuto, visualizzando un grande spazio
bianco prima del testo.
Una
soluzione
Modificando l'xml come già visto, diviene possibile
capire quale nodo si sta processando e quindi di prenderne
il suo contenuto. In questo modo il file XML diventa:
<CODE>
<BR>import java.awt.*;<BR>class MyApplet
extends Applet {<BR>public void init() {<BR>JSObject
win = JSObject.getWindow(this);<BR>JSObject doc
= (JSObject) win.getMember("document");<BR>JSObject
loc = (JSObject) doc.getMember("location");<BR>
<BR>String s = (String) loc.getMember("href");
// document.location.href<BR>win.call("f",
null); // Call f() in HTML page<BR>}<BR>}
</CODE>
Il
foglio di stile dunque produce un output con il seguente
aspetto:
import
java.awt.*;
class MyApplet extends Applet {
public void init() {
JSObject win = JSObject.getWindow(this);
JSObject doc = (JSObject) win.getMember("document");
JSObject loc = (JSObject) doc.getMember("location");
String
s = (String) loc.getMember("href"); // document.location.href
win.call("f", null); // Call f() in HTML page
}
}
Di
seguito è riportato l'xsl completo:
<?xml
version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="body">
<xsl:element name="html">
<xsl:element name="body">
<xsl:for-each select="child::*">
<xsl:choose>
<xsl:when test="name()='paragraph'">
<xsl:element name="p">
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
<xsl:when test="name()='text'">
<xsl:element name="p">
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
<xsl:when test="name()='code'">
<xsl:element name="CODE">
<xsl:for-each select="child::*">
<xsl:choose>
<xsl:when test="name()='break'">
<xsl:element name="BR"/>
<xsl:value-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Un
occhio al codice
Per eseguire una trasformazione XSLT in Java è
sufficiente utilizzare le API standard JAXP. La classe
XmlTransform invoca, nel costruttore, la chiamata al
metodo newInstance() sulla classe TransformerFactory.
In questo modo si ottiene un TransformerFactory da utilizzare
nel metodo di trasformazione.
Il metodo transform() ottiene un oggetto Transformer
dalla factory tramite il metodo newTrasformer, a cui
passa l'XSL da utilizzare. Questo viene passato sottoforma
di oggetto StreamSource. Questa classe dispone di molti
costruttori che permettono di ottenere il foglio di
stile da molte sorgenti (p.e. http, inputstream). In
questo caso i dati vengono letti dalla memoria utilizzando
un oggetto ByteArrayInputStream, che trasforma un array
di byte in un flusso.
La trasformazione avviene tramite il metodo transform(),
che si aspetta uno StreamSource per leggere il file
XML ed uno per scrivere la risposta. Questa viene memorizzata
in un ByteArrayOutputStream apposito, da cui poi vengono
ottenuti i dati per la risposta, tramite l'invocazione
del metodo toString().
public
class XmlTransform {
TransformerFactory transformerFactory;
public XmlTransform() {
transformerFactory =
TransformerFactory.newInstance();
}
public String transform(String xml, String xsl) throws
TransformerException {
System.out.println("xml=" + xml);
System.out.println("xsl=" + xsl);
ByteArrayOutputStream resultStream =
new ByteArrayOutputStream();
ByteArrayInputStream xmlStream =
new ByteArrayInputStream(xml.getBytes());
ByteArrayInputStream xslStream =
new ByteArrayInputStream(xsl.getBytes());
Transformer transformer = transformerFactory.newTransformer(
new StreamSource(xslStream));
transformer.transform(
new StreamSource(xmlStream),
new StreamResult(resultStream));
return resultStream.toString();
}
}
Le
chiamate di trasformazione possono sollevare una TransformerException
in caso di problemi. Può infatti capitare che
il contenuto dei fogli di stile non sia compilato correttamente
o contenga errori nel formalismo XML.
Per funzionare, la classe richiede una implementazione
delle API JAXP. In questo caso è stato utilizzato
Xalan 2.6.0, reperibile sul sito xml.apache.org.
Conclusioni
In questo articolo si è visto come realizzare
un foglio di stile per trasformare un documento XML
in una pagina HTML, utilizzando le API JAXP. Si è
visto come queste siano molto semplici da utilizzare,
e come una piccola classe possa svolgere una funzionalità
molto potente.
Si è partiti dal flusso XML che descrive un articolo
di Mokabyte, che è però stato leggermente
variato per renderlo meglio gestibile attraverso i fogli
di stile XSL.
|