CentraleSupélecDépartement informatique
Gâteau du Glouton
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
XML avancé: XPath, XSLT

XSLT est un langage de réécriture d'arbres XML, à base de templates, qui s'appliquent sur des sous-arbres. Afin de sélectionner ces sous-arbres, on utilise le langage (non-XML) XPath.

XPath

XPath est un langage non-XML d’expression utilisé pour identifier des ensembles de nœuds de documents XML. Pour plus d'expressivité, on peut aussi:

  • Tester des conditions booléennes
  • Manipuler les chaînes de caractères
  • Effectuer des calculs numériques

Ici vous trouverez une page pour tester.

Chemin de localisation

Un chemin de localisation désigne un ensemble de nœuds. C'est une juxtaposition de sélecteurs (qui sélectionnent) et de conditions (qui filtrent)

Sélecteurs

En partant du nœud courant, on sélectionne :

  • les éléments enfants de nom foo : foo
  • tous les éléments enfants : *
  • le nœud courant : .
  • le nœud parent : ..
  • le contenu textuel : text()
  • un attribut : @nomAttribut

Note:

  • Un sélecteur précédé de / recherche à partir du nœud courant,
  • // recherche à partir du nœud courant ou n'importe-lequel de ses descendants
  • / tout seul représente la racine.

Par exemple, supposons que dans cet arbre le noeud courant soit "Oiseau", que les noeuds jaunes sont des balises et les noeuds verts des attributs.

Alors:

.→ Oiseau
*→ Canari, Pigeon, Rossignol
../Mammifère/Primate→ Primate
/Animal/Reptile→ Reptile
/Animal/Reptile/Serpent/Boa→ ∅
/Animal/*/*→ Serpent, Lézard, Canari, Pigeon, ... Chauve-souris
//Canari→ Canari
/Animal/Mammifère//*→ Primate, Homme, Chimpanzé, Chauve-souris

Concept d'axe

En fait chaque élément d'un chemin de localisation est exprimé vis-à-vis d'un certain axe

  • axe par défaut : parmi les enfants
  • . : sur soi-même
  • .. : sur son parent
  • // : sur un descendant

Il existe d'autres axes : chaque axe porte un nom qui doit être suivi de ::

Par exemple, following-sibling::X cherche les X parmi les éléments suivants de même niveau.

Dans l'arbre au dessus:

/Animal/Mammifère/descendant-or-self::*→ Mammifère, Primate, Homme, Chimpanzé, Chauve-souris
/Animal/Mammifère/descendant::*→ Primate, Homme, Chimpanzé, Chauve-souris
/Animal/Mammifère/preceding-sibling::*→ Reptile, Oiseau

Attributs

les attributs sont aussi des nœuds de l'arbre: des nœuds de type attribut. Ils sont accessible par l'axe attribute::X, abrégé en @X

Par exemple, dans un arbre XHTML:

  • //a/@href : sélectionne tous les liens d'un document
  • //a/attribute::href : pareil
  • //section//img/@* : sélectionne tous les attributs de toutes les images incluses dans une section

Prédicats

À chaque étape, un prédicat permet de conserver, parmi les nœuds courants, uniquement ceux qui vérifient une condition

Utilisation : A/B[condition]/C/D[condition]

Que peut-on utiliser dans un prédicat ?

  • un chemin de localisation : vrai si au moins un nœud résultat
  • une comparaison : < <= = != >= >
  • une combinaison par opérateurs booléens : and, or, not()
  • une position au sein d'un ensemble résultat
  • le résultat d'une fonction parmi une petite bibliothèque :
    • last() : nombre de nœuds dans le contexte courant
    • count(ensemble) : nombre de nœuds dans l'ensemble
    • name(X) : nom du résultat X
    • ...

Dans l'exemple de l'arbre ci-dessus:

/Animal/*[Lézard]→ Reptile
//*[@pattes]→ Lézard, Oiseau, Homme, Chimpanzé, Chauve-souris
//*[@pattes]//*→ Canari, Pigeon, Rossignol
//*[@pattes=2]→ Oiseau, Homme, Chauve-souris
//*[.//Pigeon]→ Animal, Oiseau
//*[not(*)]→ Serpent, Lézard, Canari, P., R., Homme, Ch., Ch.-souris
//Oiseau/*[1]→ Canari
//Oiseau/*[last()]→ Rossignol
//*[ancestor-or-self::*[@vole]]→ Oiseau, Canari, Pigeon, Rossignol, Ch.-souris
//*[contains(name(.), 'ri')]→ Canari, Primate, Chauve-souris

XSLT

Sert à donner la description d'une transformation d'un document XML, de façon indépendante d'un langage de programmation. La transformation est basée sur le concept des modèles (template).

En bref:

Dans la suite de ce tutoriel, on va utiliser des feuilles de style XSLT sur le document XML suivant, que nous appellerons livres.xml.

<liste>
  <livre genre="web">
    <titre>XML</titre>
    <auteur><prenom>Gilles</prenom><nom>Chagnon</nom></auteur>
    <pub>07</pub>
  </livre>
  <livre>
    <titre>Quantum Computation</titre>
    <auteur><prenom>Michael A.</prenom><nom>Nielsen</nom></auteur>
    <auteur><prenom>Isaac L.</prenom><nom>Chuang</nom></auteur>
    <pub>01</pub>
  </livre>
</liste>

Ici vous trouverez une page pour tester une feuille de style XSLT contre un document. Sinon, le programme saxon est une solution robuste et en locale.

Enfin, il est à noter que de nombreuses bibliothèque javascript existent pour traiter les transformations XSLT, que ce soit sur le client, ou avec node.js.

Structure du document XSLT

On utilise bien sûr les espaces de nom !

<x:stylesheet version='2.0' xmlns:x='http://www.w3.org/1999/XSL/Transform'>
  <x:output method="xml"/>

  <x:template match='expression-xpath'>
     ...
  </x:template>
  <x:template match='expression-xpath'>
     ...
  </x:template>
  <x:template match='expression-xpath'>
     ...
  </x:template>
</x:stylesheet>

Le noeud output indique au driver qui va traiter utiliser la feuille de style le type de document à produire. Cela peut être:

  • text : va ignorer les balises XML qui ne font pas partie de la feuille de style XSLT. Chaque caractère compte (y compris les espaces)
  • xml : va traiter correctement les questions d'espaces de nom en sortie. Mais par contre peut modifier les espaces (qui en XML n'ont pas de sens à priori)
  • html : peut ajouter par défaut l'espace de nom qui va bien, avec éventuellement le meta qui donne le charset.

Chaque template décrit une règle, c'est-à-dire une action à effectuer pour le ou les noeuds décrit(s) par l'expression XPath contenue dans l'attribut match

Par défaut, le modèle avec le match le plus précis est celui qui s'applique.

Exemple

Sur notre fichier livres.xml, la feuille de style XSLT

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="/liste/livre">
    Livre trouvé
  </xsl:template>
</xsl:stylesheet>

produit le résultat:

Livre trouvé
Livre trouvé

(une ligne par résultat)

Instructions

Évidemment, on peut faire plus que générer du texte... Ci-après, une courte liste d'instructions XSLT, avec utilisation sur notre petit fichier livres.xml.

Il faut noter que les trois instructions essentielles sont

  • apply-templates
  • value-of
  • text

Toutes les autres peuvent essentiellement se réduire à ces cas-là (sauf peut-être pour la notion de variable, mais qui n'est pas très utile dans notre cadre)

apply-templates

Cette instruction va appliquer le fichier de style XSLT (par défaut celui courant, même si on pourrait de fait en invoquer un autre) sur le noeud courant.

Par exemple, on pourrait faire

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  <xsl:template match="/liste">
    <html>
       <head><title>Des livres</title></head>
       <body>
          <xsl:apply-templates/>
       </body>
    </html>
  </xsl:template>
  <xsl:template match="livre">
     <h1>Livre trouvé</h1>
  </xsl:template>
</xsl:stylesheet>

et cela produirait:

<html>
   <head><title>Des livres</title></head>
   <body>
     <h1>Livre trouvé</h1>
     <h1>Livre trouvé</h1>
   </body>
</html>

Vous pouvez essayer de changer le type d'output, pour voir.

Cet exemple minimaliste est un bon exemple de l'utilisation des templates: au début, le premier template est utilisé. Le noeud courant devient /liste. Comme on rappelle la feuille de style, on va cette fois-ci attraper chaque fils livre et employer le template en question dessus.

value-of

Évidemment, ce serait plus malin de pouvoir mettre par exemple le titre du livre plutôt qu'une string fixe. C'est l'intérêt de l'instruction value-of:

<x:value-of select="expression-xpath" />

Écrit une valeur simple résultat de l'expression XPath à l'endroit courant. Exemples :

  • select="." retourne la valeur textuelle de l'élément courant
  • select="@attrib" retourne la valeur d'un attribut de l'élément courant

Dans le cadre de notre exemple, on peut maintenant compléter la feuille de style XSLT comme suit:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  <xsl:template match="/liste">
    <html>
       <head><title>Des livres</title></head>
       <body>
          <xsl:apply-templates/>
       </body>
    </html>
  </xsl:template>
  <xsl:template match="livre">
     <h1>
       <xsl:value-of select="titre"/>
     </h1>
  </xsl:template>
</xsl:stylesheet>

et cela produirait:

<html><head><title>Des livres</title></head><body>
  <h1>XML</h1>
  <h1>Quantum Computation</h1>
</body></html>

text

Il est parfois utile de pouvoir placer du texte littéral (en particulier des espaces...). Dans ce cas, on peut le placer entre les balises

<xsl:text>   du texte    les
  espaces      sont préservés   </xsl:text> 

for-each

Applique un traitement successivement à tous les nœuds d'une requête XPath.

Avec notre exemple, on peut s'en servir pour ajouter les auteurs:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  <xsl:template match="/liste">
    <html>
       <head><title>Des livres</title></head>
       <body>
          <xsl:apply-templates/>
       </body>
    </html>
  </xsl:template>
  <xsl:template match="livre">
     <h1>
       <xsl:value-of select="titre"/>
     </h1>
     <section>
       <p>Auteurs: </p>
       <ul>
       <xsl:for-each select="auteur">
         <li>
           <xsl:value-of select=".//prenom"/>
           <xsl:value-of select=".//nom"/>
         </li>
       </xsl:for-each>
       </ul>
     </section>
  </xsl:template>
</xsl:stylesheet>

et cela produirait:

<html><head><title>Des livres</title></head><body>
  <h1>XML</h1><section><p>Auteurs: </p><ul><li>GillesChagnon</li></ul></section>
  <h1>Quantum Computation</h1>
  <section>Auteurs: <ul><li>Michael A.Nielsen</li><li>Isaac L.Chuang</li></ul></section>
</body></html>

Bien que techniquement on trouve les noms des auteurs, il y a un problème: les prénoms et noms sont collés: C'est le problème référencé plus haut et traité avec l'instruction <xsl:text> </xsl:text>

La deuxième tentative est la bonne:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  <xsl:template match="/liste">
    <html>
       <head><title>Des livres</title></head>
       <body>
          <xsl:apply-templates/>
       </body>
    </html>
  </xsl:template>
  <xsl:template match="livre">
     <h1>
       <xsl:value-of select="titre"/>
     </h1>
     <section>
       <p>Auteurs: </p>
       <ul>
       <xsl:for-each select="auteur">
         <li>
           <xsl:value-of select=".//prenom"/>
           <xsl:text> </xsl:text>
           <xsl:value-of select=".//nom"/>
         </li>
       </xsl:for-each>
       </ul>
     </section>
  </xsl:template>
</xsl:stylesheet>

ce qui donne:

<html><head><title>Des livres</title></head><body>
  <h1>XML</h1><section><p>Auteurs: </p><ul><li>Gilles Chagnon</li></ul></section>
  <h1>Quantum Computation</h1><section><p>Auteurs: </p>
  <ul><li>Michael A. Nielsen</li><li>Isaac L. Chuang</li></ul></section>
</body></html>

if

L'instruction xsl:if permet d'exécuter ou non certaines parties du code. Par exemple, dans notre exemple on peut ne mettre un "s" à "Auteurs" que lorsqu'il y en a au moins 2. On peut aussi ajouter le genre si il est présent.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  <xsl:template match="/liste">
    <html>
       <head><title>Des livres</title></head>
       <body>
          <xsl:apply-templates/>
       </body>
    </html>
  </xsl:template>
  <xsl:template match="livre">
     <h1>
       <xsl:value-of select="titre"/>
       <xsl:if test="@genre"> (<xsl:value-of select="@genre"/>)</xsl:if>
     </h1>
     <section>
       <p>Auteur<xsl:if test="count(auteur) > 1">s</xsl:if>: </p>
       <ul>
       <xsl:for-each select="auteur">
         <li>
           <xsl:value-of select=".//prenom"/>
           <xsl:text> </xsl:text>
           <xsl:value-of select=".//nom"/>
         </li>
       </xsl:for-each>
       </ul>
     </section>
  </xsl:template>
</xsl:stylesheet>

Qui donne bien

<html><head><title>Des livres</title></head><body>
  <h1>XML (web)</h1><section><p>Auteur: </p><ul><li>Gilles Chagnon</li></ul></section>
  <h1>Quantum Computation</h1><section>
  <p>Auteurs: </p><li>Michael A. Nielsen</li><li>Isaac L. Chuang</li></section>
</body></html>

choose / when / otherwise

On peut aussi faire des tests plus compliqués avec l'instruction choose. Format:

<xsl:choose>
  <xsl:when test="montest1">
     ...
  </xsl:when>
  <xsl:when test="montest2">
     ...
  </xsl:when>
  ...
  <xsl:otherwise>
     ...
  </xsl:otherwise>
</xsl:choose>

Création d'éléments

On peut créer des éléments avec attributs de façon générique. Par exemple, si on souhaite créer une image dans notre fichier XSLT, on ferait:

<xsl:element name="img">
   <xsl:attribute name="src"> ... un lien ...  </xsl:attribute>
   <xsl:attribute name="alt"> ... un texte ... </xsl:attribute>
</xsl:element>

L'avantage de cette méthode est que l'on peut mettre dans la valeur de l'attribut une valeur obtenue par exemple avec value-of.

Variables

Enfin, on a une notion de variable. On définit une variable avec l'instruction

<xsl:variable name="siecle" select="20" />

Pour utiliser la variable, on utiliser le symbole "$", comme cela:

<xls:value-of select="$siecle*100 + pub" />

HTML5 avec XSLT

Pour générer du HTML5 valide, il faut un DOCTYPE au début du fichier... On peut en mettre un avec

<xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;</xsl:text>

Exemple complet

avec variable, générant du HTML5

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>
  <xsl:variable name="siecle" select="20" />
  <xsl:template match="/liste">
    <xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;</xsl:text>
    <html>
       <head><title>Des livres</title></head>
       <body>
          <xsl:apply-templates/>
       </body>
    </html>
  </xsl:template>
  <xsl:template match="livre">
     <h1>
       <xsl:value-of select="titre"/>
       <xsl:if test="@genre"> (<xsl:value-of select="@genre"/>)</xsl:if>
     </h1>
     <section>
       <p>Publié en <xsl:value-of select="$siecle*100 + pub" /></p>
       <p>Auteur<xsl:if test="count(auteur) > 1">s</xsl:if>: </p>
       <ul>
       <xsl:for-each select="auteur">
         <li>
           <xsl:value-of select=".//prenom"/>
           <xsl:text> </xsl:text>
           <xsl:value-of select=".//nom"/>
         </li>
       </xsl:for-each>
       </ul>
     </section>
  </xsl:template>
</xsl:stylesheet>

Qui donne (attention, le script en ligne ne gère pas l'echappement... Utiliser saxon par exemple)

<!DOCTYPE html><html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Des livres</title>
   </head>
   <body>
      <h1>XML (web)</h1>
      <section>
         <p>Publié en 2007</p>
         <p>Auteur: </p>
         <ul>
            <li>Gilles Chagnon</li>
         </ul>
      </section>
      <h1>Quantum Computation</h1>
      <section>
         <p>Publié en 2001</p>
         <p>Auteurs: </p>
         <ul>
            <li>Michael A. Nielsen</li>
            <li>Isaac L. Chuang</li>
         </ul>
      </section>
   </body>
</html>