Du XML au XML avec XSLT

Creative Commons License
Cette création est mise à disposition sous un contrat Creative Commons.

L'article original se trouve sur http://ymettier.free.fr/articles_lmag/.

Article publié dans le numéro 51 (juin 2003) de GNU/Linux France Magazine


Table des matières

1. Introduction
2. La page news.db.xml
2.1. Définition du format
2.2. Format amélioré pour le site web
2.3. Exemple de fichier de nouvelles
3. La transformation en pseudo-XHTML pour le site web
3.1. La feuille de style basique
3.2. Une feuille de style plus élaborée
3.3. La liste des langues
4. Le fichier changes.xml
5. Conclusion

1. Introduction

Quel est l'intérêt de partir du XML pour arriver au XML pense-t-on au prime abord. Si vous lisez la conclusions tout de suite, vous allez comprendre un des intérêts d'une telle transformation. En effet, le XHTML est un sous-ensemble du XML, et pour le site web qui sert de prétexte à ce dossier, nous allons générer du XHTML à partir de données stoquées dans un fichier XML. Il ne restera plus qu'à utiliser le résultat avec la feuille de style XSLT du site web pour générer le site web! La transformation est donc news.db.xml -> news.xml -> pages web.

Ceci est loin d'être le seul intérêt de la transformation XML vers XML. En effet, de nombreux formats de fichiers sont exprimés en XML, et vous pouvez à partir d'une source XML, la convertir en un format de fichier XML qu'un logiciel adapté va vous permettre de lire. Par exemple, prenez un fichier .sxw, que vous générez avec OpenOffice.org. Décompressez-le avec unzip: vous allez obtenir plusieurs fichiers XML. Cette source peut vous permettre d'extraire des informations pour les réutiliser dans un autre format, sous-ensemble XML aussi, lisible par un autre logiciel. Inversement, si vous respectez le format du XML des fichiers d'OpenOffice.org, vous pouvez générer de tels fichiers à partir de sources différentes. Après tous, peut-être que par la suite, les convertisseurs de formats de fichiers, quand source et destination seront toutes deux en XML, ce seront simplement des feuilles de style XSLT ?

Mais revenons à nos moutons, et générons des pages au format pseudo-xhtml que notre moulinette de l'autre article sait si bien lire...

2. La page news.db.xml

2.1. Définition du format

Tout d'abord, définissons le format d'un fichier XML de news. Ce dossier ne portant pas directement sur XML, je vous épargnerai l'étape de la DTD, et nous définirons ce format par l'exemple. Par ailleurs, vous noterez que le fichier de news, tout comme celui des changements plus loin, n'est rien d'autre qu'une base de données. En SQL très rudimentaire, nous aurions eu ceci:

1 CREATE TABLE item (
2   day   int,
3   month int,
4   year  int,
5   text  text
6 )

Evidemment, s'il s'agissait de SQL, les puristes auraient optimisé ceci avec par exemple le type adéquat pour la date. Mais il s'agit de comparer un peu les deux types de bases pour un cas simple. Avant d'abandonner définitivement le sujet du SQL, une remarque. Pour une grosse base de données, il est en général préférable, et de loin, d'utiliser un SGDB relationnel. Mais pour une petite base simple, qui est en plus quasi en lecture seule, un document XML est tout a fait approprié. Les deux sont donc complémentaires, et dans notre cas, c'est le document XML le plus approprié.

Une nouvelle consiste en une date et un contenu, éventuellement un auteur, voire un modérateur quand le site devient assez gros. Dans notre cas, nous nous contenterons que la date et du contenu, l'ajout des auteur et modérateur se faisant simplement ensuite.

Une date peut s'exprimer de différentes manières en XML, comme <date y='yyyy' m='mm' d='dd'/>, ou en allant fouiller du côté du format RDF, ou comme ce que j'ai choisi, <date><year>YYYY</year><month>MM</month><day>DD</day></date>. Le contenu d'une nouvelle s'exprime simplement: <text>nouvelle</text>. Et le tout peut s'encapsuler dans un <item>. Ainsi, il n'y a qu'à mettre autant d'<item> que de nouvelles.

2.2. Format amélioré pour le site web

Notre site web a cette particularité de parler plusieurs langues. Il y a donc deux améliorations majeures à apporter. L'une est de supporter différentes langues dans les <text>. Pour cela, rien de tel que que ce tag <span> et son attribut xml:lang que l'on a déjà utilisé pour générer notre site web à partir des pages pseudo-xhtml. Plus compliqué par contre, la génération des dates. En effet, même si cela n'est pas propre à la langue, certains pays ont une notation différente de la date que notre JJ/MM/AAAA. Aux Etats-Unis par exemple, on note MM/DD/YYYY. Mais hors de question de tenir compte de cela dans chaque item. C'est au processeur XSLT d'effectuer la bonne transformation. Cependant, il faut quand même fournir la liste des langues dont l'affichage se fait dans le sens MM/DD/YYYY, le sens par défaut étant DD/MM/YYYY (et pour info, le format ISO 8601 est YYYYMMDD). Pour cela, plusieurs solutions:

  • un fichier de configuration XML contenant cette liste (le mieux, mais le plus lourd)

  • une liste dans la feuille XSLT (pas si trivial que cela)

  • une liste dans le fichier news.db.xml (à éviter absolument: on ne mélange pas les données avec la configuration)

  • un test exhaustif sur toutes les langues

Pour rester simple, je préfère cette dernière solution, d'autant plus que non seulement c'est simple à faire, mais en plus la liste exhaustive est petite, et se limite à l'anglais dans notre cas.

2.3. Exemple de fichier de nouvelles

Voici ce qu'un fichier de nouvelles peut donner...

 1 <?xml version="1.0" encoding="ISO-8859-1" standalone="yes" ?>
 2 <news>
 3   <title>
 4     <span xml:lang="en">News</span>
 5     <span xml:lang="fr">Infos</span>
 6     <span xml:lang="es">Noticias</span>
 7   </title>
 8   <item>
 9     <date><year>2003</year><month>03</month><day>21</day></date>
10     <text>
11 	    <span xml:lang="en">0.2 is out! See <a href="changes.en.html">Changes</a>.</span>
12       <span xml:lang="fr">0.2 est sortie! Voyez les <a href="changes.fr.html">Changements</a>.</span>
13       <span xml:lang="es">0.2 está disponible! Ver <a href="changes.en.html">Cambios</a>.</span>
14     </text>
15   </item>
16   <item>
17     <date><year>2003</year><month>02</month><day>01</day></date>
18     <text>
19 	    <span xml:lang="en">0.1 is out! See <a href="changes.en.html">Changes</a>.</span>
20       <span xml:lang="fr">0.1 est sortie! Voyez les <a href="changes.fr.html">Changements</a>.</span>
21       <span xml:lang="es">0.1 está disponible! Ver <a href="changes.en.html">Cambios</a>.</span>
22     </text>
23   </item>
24 </news>

Vous excuserez le manque de traduction allemande, mais après tout, cela me permet de vous montrer comment s'en sortir quand on ne connait pas toutes les langues.

3. La transformation en pseudo-XHTML pour le site web

3.1. La feuille de style basique

Pour effectuer une transformation XML vers XML, le principe est le même que pour la transformation de XML vers HTML. La source est dans les deux cas du XML ce qui simplifie déjà les choses. Le format de sortie se fixe soit en paramètre de xsltproc ou du processeur XSLT que vous allez utiliser, ou comme dans l'exemple suivant en signalant XML dans le format de sortie, ligne 5.

 1 <?xml version="1.0" encoding="ISO-8859-1"?>
 2 
 3 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 4   <xsl:output method="xml"
 5 	    encoding="ISO-8859-1"
 6 	    doctype-public="-//W3C//DTD XHTML 1.1//EN"
 7 	    doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
 8 	    indent="yes"
 9 	    />
10   <xsl:param name="nb_news">20</xsl:param>
11 
12   <xsl:template match="/news">
13     <body>
14       <xsl:apply-templates select="./title" mode="title"/>
15       <table class="news">
16         <xsl:apply-templates select="./item[$nb_news >= position()]"/>
17       </table>
18     </body>
19   </xsl:template>
20 
21     <xsl:template match="/news/item">
22     <tr>
23       <td class="news">
24         <xsl:apply-templates select="date" mode="date"/>
25       </td>
26       <td class="news">
27         <xsl:apply-templates select="text"/>
28       </td>
29     </tr>
30   </xsl:template>
31 
32   <xsl:template match="@*|node()" mode="date">
33     <xsl:value-of select="day/node()"/>
34     <xsl:text>/</xsl:text>
35     <xsl:value-of select="month/node()"/>
36     <xsl:text>/</xsl:text>
37     <xsl:value-of select="year/node()"/>
38   </xsl:template>
39 
40   <xsl:template match="/news/title" mode="title">
41     <h1>
42       <xsl:apply-templates select="@*|node()"/>
43     </h1>
44   </xsl:template>
45 
46   <xsl:template match="@*|node()">
47     <xsl:copy>
48       <xsl:apply-templates select="@*|node()"/>
49     </xsl:copy>
50   </xsl:template>
51 
52 </xsl:stylesheet>

Cette feuille de style devrait être très simple à comprendre si vous avez lu l'article sur la création du site web. Il y a cependant ligne 16 une requête XPath intéressante qui permet de ne récupérer que les $nb_news dernières nouvelles postées. Cela est expliqué plus en détail dans l'article sur XPath.

Il y a aussi de remarquable le modèle ligne 32 qui est somme toute très simple à comprendre, mais qui montre bien avec celui ligne 40 que XSLT ne se limite pas aux balises XHTML même si l'on s'est limité à ces balises dans l'article sur la création du site web. Quant à l'attribut mode, nous l'avions déjà rencontré dans l'autre article, nous le retrouvons ici aussi.

Le reste de la feuille de style est similaire aux feuilles de styles déjà utilisées dans l'autre article, et je n'y reviens donc pas.

3.2. Une feuille de style plus élaborée

La feuille de style précédente ne prend pas en compte le format de la date. Et elle élimine aussi les langues autres que l'anglais (à moins de spécifier en paramètre que lang=fr par exemple). Voici comment faire: l'idée est de conserver tous les tags <span<, et de les créer pour la date, pour toutes les langues du document. Vous devez ajouter la variables suivantes, par exemple au début, vers la ligne 11 de la feuille de style précédente:

1   <xsl:variable name="langs"
2       select="//span[not(@xml:lang=preceding::span/@xml:lang)]/@xml:lang"/>

Et ensuite, recopiez à la place du modèle mode='date' ceux qui suivent:

 1    <xsl:template match="@*|node()" mode="date">
 2      <xsl:variable name="year" select="year/node()"/>
 3      <xsl:variable name="month" select="month/node()"/>
 4      <xsl:variable name="day" select="day/node()"/>
 5      <xsl:for-each select="$langs">
 6        <xsl:variable name="l1" select="."/>
 7        <xsl:choose>
 8          <xsl:when test="$l1 = 'en'">
 9 	      <span xml:lang='{$l1}'>
10              <xsl:value-of select="$day"/>
11              <xsl:text>/</xsl:text>
12              <xsl:value-of select="$month"/>
13              <xsl:text>/</xsl:text>
14              <xsl:value-of select="$year"/>
15            </span>
16          </xsl:when>
17          <xsl:otherwise>
18 	   <span xml:lang='{$l1}'>
19              <xsl:value-of select="$month"/>
20              <xsl:text>/</xsl:text>
21              <xsl:value-of select="$day"/>
22              <xsl:text>/</xsl:text>
23              <xsl:value-of select="$year"/>
24            </span>
25          </xsl:otherwise>
26        </xsl:choose>
27      </xsl:for-each>
28    </xsl:template>

Supprimez enfin les deux modèles qui traitent des langues, lignes 46 à 51.

3.3. La liste des langues

Comment récupérer la liste des langues du fichier, et surtout comment avoir une liste avec aucun duplicata ? Ceci n'est pas une mince affaire et mérite une explication. Tout se joue sur les deux lignes qui définissent la variable $langs. Sans entrer dans la syntaxe XPath qui fait l'objet de l'article du même nom, décomposons-la. Hors des crochets, on recherche tous les attributs xml:lang des noeuds span. Mais parmi les noeuds span, nous n'en selectionnons qu'une partie. En effet, lorsque l'égalité @xml:lang=preceding::span/@xml:lang est vraie, cela veut dire que le noeud span a un attribut xml:lang que l'on a déjà rencontré. Donc inutile de selectionné un tel noeud, et c'est pour cela qu'on ne prend que les noeuds pour lesquels l'égalite n'est pas vraie. C'est aussi simple que cela, mais même si on s'y connaît déjà un peu avec XSLT, on n'y pense pas forcément.

Ensuite, pour chaque noeud dont le mode est date, donc typiquement chaque noeud de date, nous mettons l'année, le mois et le jour dans des variables lignes 2 à 4. Cela simplifie la compréhension de la suite. Puis pour toutes les langues du document, ligne 5, nous effectuons un test ligne 8 pour savoir si cette langue doit être affichée en MMDDYYYY (lignes 9 à 15) ou comme nous en avons l'habitude DDMMYYYY (lignes 19 à 24). Cela nous permet de faire autant de lignes <span lang="XX"> qu'il y a de langues représentées dans le document XML. En effet, si dans l'autre article, celui qui explique comment générer des pages web pour chaque langue, nous n'avons besoin que d'une langue par page web, ici, c'est le contraire: nous devons être exhaustif.

Lignes 9 et 18, nous avons quelque chose de très intéressant, dans la valeur de l'attribut xml:lang. Nous avons une variable à l'intérieur d'accolades. En fait, lorsque nous avons une valeur d'attribut, nous pouvons mettre entre accolades une expression à évaluer, si l'attribut doit effectivement faire partir du résultat. Cela nous convient tout à fait pour évaluer notre variable.

4. Le fichier changes.xml

Le fichier des changements est un fichier un peu différent. En effet, celui-ci est encore plus simple: il ne contient qu'un numéro de version et la liste des changements. De plus, la liste des changements n'étant jamais écrite en plusieurs langues, nous n'avons même pas ce problème à traiter. Dois-je vous donner la feuille de style correspondante ? Je vais tout simplement vous la laisser, et vous tâcherez de deviner son format d'après la feuille de style:

 1 <?xml version="1.0" encoding="ISO-8859-1"?>
 2 
 3 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 4   <xsl:output method="xml"
 5 	    encoding="ISO-8859-1"
 6 	    doctype-public="-//W3C//DTD XHTML 1.1//EN"
 7 	    doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
 8 	    indent="yes"
 9 	    />
10   <xsl:param name="nb_changes">20</xsl:param>
11 
12   <xsl:variable name="langs"
13       select="//span[not(@xml:lang=preceding::span/@xml:lang)]/@xml:lang"/>
14 
15   <xsl:template match="/changes">
16     <body>
17       <xsl:apply-templates select="./title" mode="title"/>
18       <table class="changes">
19         <xsl:apply-templates select="./item[$nb_changes >= position()]"/>
20       </table>
21     </body>
22   </xsl:template>
23 
24   <xsl:template match="/changes/item">
25     <tr>
26       <td class="changes">
27         <xsl:apply-templates select="version"/>
28       </td>
29       <td class="changes">
30         <xsl:apply-templates select="text"/>
31       </td>
32     </tr>
33   </xsl:template>
34 
35   <xsl:template match="/changes/title" mode="title">
36     <h1>
37       <xsl:apply-templates select="@*|node()"/>
38     </h1>
39   </xsl:template>
40 
41   <xsl:template match="@*|node()">
42     <xsl:copy>
43       <xsl:apply-templates select="@*|node()"/>
44     </xsl:copy>
45   </xsl:template>
46 
47 </xsl:stylesheet>

No comment!

5. Conclusion

Cet article vous a montré que l'on pouvait transformer du XML en du XML. Il était plus prétexte à vous donner le code de ce qui permet de générer les pages à passer dans la moulinette à site web de l'autre article qu'autre chose. Mais vous avez cependant vu que n'importe quelle source XML peut être transformée d'un format à un autre, ce qui est à mon avis une méthode des plus agréables pour convertir un document XML d'un format donné en un document XML d'un autre format, pour passer facilement d'une application à une autre. Pour en revenir à notre site web, avec l'article sur la création d'un site web avec XSLT et avec celui-ci, vous avez maintenant tout le code pour créer un site web statique sympathique, assez facile à maintenir car basé sur des modèles. A vous de jouer!

création est mise à disposition sous un contrat Creative Commons