MokaByte 99 - 7mbre 2005
 
MokaByte 99 - 7mbre 2005 Prima pagina Cerca Home Page

 

 

 

MokaCMS - Open Source per il Web Content Management
VII parte: da XML ad PDF utilizzando XSLT e FO (II puntata)

Riprendiamo lo sviluppo del foglio di stile xsl-fo che sarà utilizzato per produrre automaticamente il PDF di un articolo di Mokabyte, riprendendo dallo sviluppo del piede di pagina e poi passando allo sviluppo del corpo dell'articolo.

Nel numero precedente abbiamo iniziato lo sviluppo del foglio di stile impostando la gabbia e definendo l'intestazione. Ora affrontiamo gli altri elementi del file.
Il piè di pagina dovrà riportare il numero di pagina allineato al centro e sovrapposto ad una linea. Al termine di questa linea dovranno essere collocati 2 quadrati sovrapposti tra loro e di colore diverso; verde scuro il quadrato posto in primo piano e verde chiaro il quadrato messo in secondo piano.
È più difficile a dirsi che a farsi, e non è molto diverso da ciò che è stato fatto per l'intestazione.
La figura 1 mostra come desideriamo che appaia il piè di pagina.


Figura 1
- Il piè di pagina

<xsl:template name='footerTemplate'>

<fo:block-container height="1.25cm" width="14.5cm" bottom="2.00cm" left="3.60cm" position="absolute">
<fo:block-container height="0.50cm" width="1.0cm" top="0.10cm" left="6.75cm" right="6.75cm" position="absolute">
<fo:block xsl:use-attribute-sets="footer"><fo:page-number/></fo:block>
</fo:block-container>
<fo:block xsl:use-attribute-sets="footer">
<fo:leader leader-pattern="rule" rule-thickness="0.01cm" leader-length="6.75cm" color="rgb(0,102,0)"/>
<fo:leader leader-pattern="space" rule-thickness="0.01cm" leader-length="0.5cm"/>
<fo:leader leader-pattern="space" rule-thickness="0.01cm" leader-length="0.5cm"/>
<fo:leader leader-pattern="rule" rule-thickness="0.01cm" leader-length="6.75cm" color="rgb(0,102,0)"/>
</fo:block>
<fo:block-container background-color="rgb(166,206,57)" height="0.5cm" width="0.5cm" left="14.2cm" right="0.2cm" position="absolute"/>
<fo:block-container background-color="rgb(0,144,102)" height="0.5cm" width="0.5cm" top="0.3cm" left="14.4cm" right="0.0cm" position="absolute"/>
</fo:block-container>

</xsl:template>

Tutto il piè di pagina dovrà essere contenuto in un fo:block-container. Si ricordi che questi elementi possono essere posizionati ovunque a differenza dei fo:block che vengono inseriti uno dietro l'altro.
Tramite gli attributi top, bottom, left e right, si è in grado di posizionare il contenitore. Non è necessario specificare tutti e quattro gli attributi, ne basta solo 1, e in questo caso sono stati specificati bottom e left.
Il contenitore sarà alto 1,25 cm e largo 14,5 cm, e disterà di 2 cm dal bordo basso del foglio e di 3,60 cm dal bordo sinistro.
All'interno di questo contenitore si dovranno inserire tre fo:block-container e un fo:block. Il primo sarà di tipo fo:block-container, dovrà contenere il numero di pagina. Il numero di pagina è dato dall'elemento fo:page-number il quale manterrà in automatico il conteggio delle pagine senza l'apporto di programmazione. Il secondo blocco sarà di tipo fo:block e conterrà le linee estetiche. Le linee sono due perché nel mezzo è posizionato il blocco che contiene il numero di pagina. Le linee sono create sempre con il fo:leader, proprio come è stato fatto per l'intestazione. Rimangono due fo:block-container da esaminare. il primo risulterà un quadrato verde chiaro, alto e largo 0,5 cm; dista dal bordo destro del contenitore principale di 0,2 cm e dal bordo sinistro sempre del contenitore principale di 14,2 cm. Il secondo è come il primo con la differenza che è di colore verde scuro e che è posizionato a 0,3 cm dal bordo alto, a 14,4 cm dal bordo sinistro e a 0 cm dal bordo destro. In questo modo si ottiene il risultato mostrato precedentemente in Figura 6.

 

Il corpo della pagina
Prima di costruire quest' ultima parte bisogna effettuare ulteriori modifiche all'xml.
Si è deciso di inserire un'immagine dell'autore, la quale potrebbe essere la fotografia dell'autore stesso, o magari un suo logo, o più semplicemente il logo di Mokabyte. La struttura e la logica applicata per questo scopo sono le stesse che sono state usate per reperire in maniera semplice le informazioni relative alla rivista. È stato dunque creato l'elemento author contenente gli elementi name e image. Nell'elemento image è contenuto l'url dell'immagine, mentre nell'elemento name è presente il nome dell'autore:

<author>
<name>Di Simona De Rosa e Massimiliano Bigatti</name>
<image>image&#x2F;author_big_3x3.gif</image>
</author>

Sempre per un'organizzazione migliore dei dati è stato aggiunto l'elemento introduction, che conterrà alcune informazioni sull'autore, come una breve biografia.
Inoltre è stata aggiunta una struttura di elementi per la creazione dinamica di tabelle all'interno del documento. In questo modo si è in grado di costruire tabelle di numero di righe e di colonne diverse, utilizzando sempre la stessa parte di codice per la trasformazione.

<table>
<row>
<column width="8.00cm">
<column width="6.50cm">
</row>

</table>

L'elemento table può contenere da uno a enne elementi row, di conseguenza quando questi sono ciclati da un'elemento xsl:for-each fanno in modo che la tabella sia dinamica in numero di righe.
Lo stesso concetto si applica per l'elemento column presente all'interno dell'elemento row. Cosi facendo si è in grado di costruire dinamicamente oltre che le righe anche le colonne di una tabella.
L' importante è che ogni elemento row contenga lo stesso numero di elementi column. Purtroppo è necessario imporre che numero di colonne sia uguale per tutte le righe, perché in xsl-fo a differenza dell' HTML è obbligatorio dichiarare a priori quante colonne saranno presenti nella tabella. Ci si può chiedere dunque come fa ad essere una tabella dinamica. La dinamicità di questa tabella è infatti solo a livello di codice. Si sa che in un' xml se un campo è presente più volte, può essere prelevato in ciclo con l'elemento xsl:for-each. In realtà non è la tabella ad essere dinamica, ma è la sua costruzione a livello di xsl-fo che è dinamica. Si vedrà poi come realizzare questo concetto.
Per prima cosa verrà posizionata l'immagine dell'autore.

<xsl:template name='bodyTemplate'>
<xsl:variable name="image" select="author/image"/>
<fo:block-container height="6.0cm" width="3.60cm" top="1cm" left="-4.0cm" position="absolute">
<fo:block xsl:use-attribute-sets="author">
<fo:external-graphic src="url('{$image}')"/>
</fo:block>
<fo:block xsl:use-attribute-sets="author">
<xsl:value-of select="author/name"/>
</fo:block>
</fo:block-container>


</xsl:template>

Si assegni all'elemento xsl:variable l'url dell'immagine. Si creerà nuovamente un elemento fo:block-container che conterrà l'immagine. In realtà conterrà l'elemento fo:external-graphic, che è l'equivalente dell'elemento img di HTML.
Si noterà dal codice che il contenitore è posizionato a -4cm dal bordo sinistro del foglio. Si ricordi che anche la region-body dista 4cm dal bordo sinistro del foglio. Se nell'attributo left ci fosse stato 0cm l'immagine sarebbe stata collocata all'interno della region-body causando una sovrapposizione al testo che in seguito verrà scritto. Quindi spostando il blocco che conterrà l'immagine a -4cm dal bordo, significa che la distanza tra il bordo e il contenitore è pari a 0cm di conseguenza l'immagine è situata in un area non utilizzata della pagina. Così facendo non si creano sovrapposizioni. Originariamente il blocco era situato nella region-start, e questo faceva in modo che l'immagine fosse ripetuta in ogni pagina dell'articolo. E questo non è per niente estetico, ma può risultare utile in altre occasioni.
Lo stesso blocco contiene anche l'elemento fo:block utilizzato per riportare il nome dell'autore. Cosi il nome viene posizionato successivamente all'immagine.

Costruzione dell'articolo
Con l'elemento xsl:for-each si eseguirà un ciclo per tutti i nodi figli. Sarà creato un blocco semplice per ogni nodo figlio, e tramite l'utilizzo dell'elemento xsl:use-attribute-sets ad ogni blocco sarà applicato lo stile appropriato, come si vede dall'esempio.

<fo:block>
<xsl:for-each select="child::*">
<xsl:choose>
<xsl:when test="name()='introduction'">
<fo:block xsl:use-attribute-sets="introduction"><xsl:value-of select="."/></fo:block>
</xsl:when>
<xsl:when test="name()='title'">
<fo:block xsl:use-attribute-sets="title"><xsl:value-of select="."/></fo:block>
</xsl:when>
<xsl:when test="name()='paragraph'">
<fo:block xsl:use-attribute-sets="subTitle"><xsl:value-of select="."/></fo:block>
</xsl:when>

Ritornando al discorso sulle tabelle, analizzato in parte precedentemente, si veda la costruzione di una tabella in xsl-fo.

<fo:table xsl:use-attribute-sets="table">
<fo:table-column column-number='1' column-width='width'/>

<fo:table-column column-number='N' column-width=' width '/>
<fo:table-body>
<fo:table-row>
<fo:table-cell column-number='1' ><fo:block>…</fo:block></fo:table-cell>

<fo:table-cell column-number='N'><fo:block>…</fo:block></fo:table-cell>
</fo:table-row>

<fo:table-row>
<fo:table-cell column-number='1'><fo:block>…</fo:block></fo:table-cell>

<fo:table-cell column-number='N'><fo:block>…</fo:block></fo:table-cell>
</fo:table-row>
</fo:table-body>
<fo:table>

In xsl-fo Si è obbligati a dichiarare quante colonne si utilizzeranno e quale grandezza ha ognuna di queste. Cosi si spiega l'esistenza dell'attributo width nel tag column.
Inoltre ogni volta che si costruisce una cella in fo, bisogna riportarne il numero.
Si riporta di seguito il codice necessario alla costruzione di una tabella, con le specifiche del caso.

<xsl:when test="name()='table'">
<fo:table xsl:use-attribute-sets="table">
<xsl:for-each select="row[position()='1']">
<xsl:for-each select="column">
<xsl:variable name="nColonna" select="position()"/>
<xsl:variable name="width" select="@width"/>
<fo:table-column column-number='{$nColonna}' column-width='{$width}'/>
</xsl:for-each>
</xsl:for-each>
<fo:table-body>
<xsl:for-each select="row">
<fo:table-row>
<xsl:for-each select="column">
<xsl:variable name="nColonna" select="position()"/>
<fo:table-cell column-number='{$nColonna}' xsl:use-attribute-sets="border.cell">
<fo:block><xsl:value-of select="."/></fo:block>
</fo:table-cell>
</xsl:for-each>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</xsl:when>

Il primo xsl:for-each costruisce la dichiarazione delle colonne, infatti cicla l'elemento row, ma attenzione, cicla l'elemento solo dove la posizione dell'elemento ciclato è uno, per questo si impone che nell'xml i tag column siano di ugual numero per ogni elemento row. In questo ciclo sono prelevati il numero di colonna dato dalla funzione position() e la larghezza stessa della colonna, specificata nell'attributo width del tag column. La funzione position() ritorna la posizione dell'elemento ciclato (in questo caso column) all'interno dell'elemento padre, che sempre nel nostro caso è l'elemento row dove la sua posizione è uno, cioè il primo elemento row in assoluto.
Il for-each interno, invece cicla le colonne, assegnando alla variabile nColonna la posizione dell'elemento corrente e alla variabile width il valore dell'attributo width, cioè la larghezza della colonna.
Il secondo xsl:for-each esterno, invece, costruisce il corpo della tabella, applicando la stessa logica usata per la dichiarazione delle colonne, e prelevando il contenuto dell'elemento column.
Proseguendo, si incontra un elemento nuovo, il fo:list-block. Questo elemento è utilizzato per creare elenchi puntati e/o numerati. Al suo interno è contenuto l'elemento list-item il quale corrisponde ad una riga dell'elenco. All'interno di quest' ultimo troviamo altri due elementi: list-item-label e list-item-body.
Il primo deve contenere la numerazione dell'elenco, oppure il simbolo da utilizzare; mentre il secondo contiene il testo dell'elenco.

<xsl:when test="name()='ordered-list'">
<xsl:variable name="positionNode"><xsl:number count="ordered-list" format="1"/></xsl:variable>
<xsl:if test="$positionNode='1'">
<fo:block xsl:use-attribute-sets="listBlockPadding"/>
</xsl:if>
<fo:block>
<fo:list-block xsl:use-attribute-sets="list.Block">
<fo:list-item>
<fo:list-item-label xsl:use-attribute-sets="list.item.label">
<fo:block xsl:use-attribute-sets="font.list.item.label.order"><xsl:number count="ordered-list" format="1."/></fo:block>
</fo:list-item-label>
<fo:list-item-body xsl:use-attribute-sets="list.item.body">
<fo:block><xsl:value-of select="."/></fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
</fo:block>
</xsl:when>

 

Eseguire la trasformazione Fop
Fino ad ora si è parlato di come costruire un foglio di stile per la formattazione di un'articolo, ma non è ancora stato detto come effettuare la trasformazione e quindi creare cosi il PDF.
Per eseguire la trasformazione è necessaria una implementazione di Fop. Quella qui utilizzata è la 0.20.5 sviluppata in Java da Apache Software Foundation, ed è reperibile al seguente link www.apache.org/dist/xml/fop/ .
Successivamente si apra una finestra DOS, e ci si posizioni nella directory di Fop appena scaricata.
Ad esempio se Fop è stato scaricato direttamente in C:\programmi, si digiti il seguente comando:

cd c:\programmi\fop-0.20.5

una volta giunti qui possiamo eseguire il comando necessario alla trasformazione.
La sintassi è la seguente.

fop -xsl percorsoFile/nomeFile.xsl -xml percorsoFile/nomeFile.xml -pdf percorsoFile/nomeFile.pdf

quindi il comando necessario per la trasformazione dell'articolo è il seguente:

fop -xsl c:/articolo/mokaByteArticle.xsl -xml c:/articolo/articolo.xml -pdf c:/articolo/mokaByteArticle.pdf

Premendo invio, si vedrà che il processore Fop esegue la trasformazione come mostrato in figura 2.


Figura 2
- trasformazione Fop

Finita la trasformazione in c:\articolo troveremo finalmente il file PDF creato.


Aggiungere i fonts in Fop
Un'ultima cosa, ma non per questo meno importante, è l'utilizzo dei font.
I font disponibili in Fop sono solo cinque: Helvetica, Times New Roman, Courier New, Symbol e ZapfDingbats. Di conseguenza, per utilizzare altri font al di fuori di questi è necessario installarli.
Innanzi tutto è necessario stabilire dove risiedono i font di sistema, e in genere sono nella cartella C:\WINDOWS\Fonts.
Tramite una classe di Fop, esattamente la TTFReader, si possono creare gli xml dei font di sistema da passare in input a Fop. Prima di creare gli xml bisogna creare la cartella in cui metterli. Quindi verrà creata la cartella xmlFonts nella directory C:\fop-0.20.5.
Una volta creati gli xml, bisogna modificare il file di configurazione userconfig.xml di Fop, situato nella directory C:\fop-0.20.5\conf.
Infine, bisogna aggiungere un parametro alla riga di chiamata del processore fop (-c; indica a fop che deve utilizzare un file di configurazione) e il percorso di userconfig.xml nel comando usato per la trasformazione.
Per esempio, si ipotizzi di voler utilizzare il font Tahoma in Fop. Per creare il file xml relativo a questo font bisogna eseguire le seguenti istruzioni:

cd C:\Programmi\fop-0.20.5

java -cp "build\fop.jar;lib\xercesImpl.jar;lib\xalan.jar" org.apache.fop.fonts.apps.TTFReader \WINDOWS\Fonts\TAHOMA.TTF C:\Programmi\fop-0.20.5\xmlFonts\tahoma.xml

java -cp "build\fop.jar;lib\xercesImpl.jar;lib\xalan.jar" org.apache.fop.fonts.apps.TTFReader \WINDOWS\Fonts\TAHOMABD.TTF C:\Programmi\fop-0.20.5\xmlFonts\tahomaBold.xml

La classe TTFReader risiede nel fop.jar. Sono inoltre richiesti i jar xercesImpl e xalan. Come si può notare, con questi comandi si otterranno due xml, uno per tahoma e uno per tahoma bold. Una volta eseguito questo comando, è necessario aggiungere due chiavi, al file userconfig.xml, come riportato di seguito.

<entry>
<key>baseDir</key>
<value>C:\articolo</value>
</entry>

<entry>
<key>fontBaseDir</key>
<value>C:\programmi\fop-0.20.5\xmlFonts</value>
</entry>

La prima indica dove risiedono i file necessari alla trasformazione, e la seconda specifica dove risiedono il file xml relativi ai font da utilizzare durante la trasformazione. Dopo di che, nell'elemento fonts presente in userconfig.xml, bisogna dichiarare i font creati, specificando l'xml, il TTF, il nome che servirà al riconoscimento durante la trasformazione e lo stile (grassetto, normale, corsivo ecc).

<fonts>
<!-- Tahoma -->
<font metrics-file="tahoma.xml" kerning="yes" embed-file="C:\WINDOWS\Fonts\TAHOMA.TTF">
<font-triplet name="Tahoma" style="normal" weight="normal"/>
</font>
<font metrics-file="tahomaBold.xml" kerning="yes" embed-file="C:\WINDOWS\Fonts\TAHOMABD.TTF">
<font-triplet name="TahomaBD" style="normal" weight="bold"/>
</font>
</fonts>

Infine, per utilizzare effettivamente questo font nel foglio di stile creato in precedenza è necessario sostituire il valore dell'attributo font-family - Helvetica con Tahoma per lo stile normale e TahomaBD per lo stile in grassetto. Questi nomi sono gli stessi indicati in userconfig.xml.

Ora il comando necessario alla trasformazione avrà, come già accennato, un parametro in più:

fop -c C:/Programmi/fop-0.20.5/conf/userconfig.xml -xsl c:/articolo/mokaByteArticle.xsl -xml c:/articolo/articolo.xml -pdf c:/articolo/mokaByteArticle.pdf

Se invece si desidera eseguire la trasformazione Fop da programma è possibile invocare direttamente le classi implementate da Apache Fop in modo simile al seguente:

String outFile = "mokaByteArticle.pdf";
String xmlFile = "articolo.xml";
String xslFile = "mokaByteArticle.xsl";
String userConfig ="userconfig.xml";

Driver driver = new Driver();
driver.setRenderer(Driver.RENDER_PDF);
driver.setOutputStream(new java.io.FileOutputStream(outFile));

//imposta le opzioni (non c'è relazione con l'oggetto Driver in quanto è una classe statica)
userConfigFile = new File(userConfig);
options = new Options(userConfigFile);

Result res = new SAXResult(driver.getContentHandler());
Source src = new StreamSource(xmlFile);

Source xsltSrc = new StreamSource(xslFile);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(xsltSrc);

transformer.transform(src, res);

 

Conclusioni
In questi due articoli si è visto come costruire un foglio di stile per generare un PDF a partire da un documento XML, producendo un risultato gradevole e d'aspetto professionale. Il materiale qui illustrato sarà integrato prossimamente nel sistema MokaCMS.