Introduction à XSLT

Rédaction : Fabien Torre, révision : Fred Mesnard.
Recommandation du W3C : XSLT 2.0

Motivations et généralités

XSLT est un langage central dans le monde XML et beaucoup de qualités reconnues de XML reposent en fait sur l'utilisation de XSLT : productions de versions diffusables (par exemple HTML, PDF), pérennité des documents, ouverture des formats et interopérabilité. La première motivation est d'associer un style à un document XML, tout comme on associe une feuille de style CSS à une page HTML. Les CSS sont utilisables avec les documents XML mais présentent plusieurs défauts :

Cela amène à la définition d'un nouveau format : XSL pour eXtensible Stylesheet Language. Cependant, l'examen critique des CSS fait apparaître deux besoins bien différents : mettre en page le document XML et, par ailleurs, lui faire subir des transformations. D'où la définition de deux langages XML : XSL-FO (XSL Formating Objects) et XSLT (XSL Transformations). Dans ce cours, on ne s'intéresse qu'à la partie XSLT.

Une transformation XSLT est d'abord un fichier XML, auquel on donne en général l'extension .xsl et qui au minimum contient :

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  ... ici des règles de transformation ...
</xsl:stylesheet>

De manière générale, XSLT permet de transformer un document XML en un autre document XML. Parmi les formats utilisés comme sortie de XSLT, citons : FO, XHTML, SVG, DocBook. Pour appliquer une feuille de transformation XSLT à un document XML, une première solution consiste à introduire un lien dans la transformation dans le document :

<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="doc2html.xsl" ?>
<document> ... </document>

Ainsi, les programmes traitant le document et disposant d'un processeur XSLT pourront choisir d'appliquer la transformation, c'est le cas par exemple des navigateurs. Une autre solution est d'indiquer explicitement la feuille XSLT à appliquer au moment de la transformation, par exemple avec xsltproc : xsltproc doc2html.xsl doc.xml > doc.html. Notons enfin qu'OpenOffice est lui aussi capable d'appliquer des transformations XSLT pour charger des documents XML.

Extraire des données du document d'origine

Le premier besoin est de pouvoir récupérer des valeurs dans le document XML d'origine, où qu'elles se trouvent. Pour cela, on met à contribution le langage XPath qui permet précisément d'obtenir une valeur ou un ensemble de noeuds particuliers.

L'instruction value-of

Dans sa forme la plus simple et la plus utilisée, l'élément value-of est associé à son attribut select qui lui contient une requête XPath désignant une valeur (le contenu d'un élément textuel, d'un attribut ou encore le résultat d'un calcul). En voici quelques exemples :

<xsl:value-of select="nom" />
<xsl:value-of select="@date" />
<xsl:value-of select="/personnes/personne[@id='p12']/nom" />
<xsl:value-of select="." />
<xsl:value-of select="count(/personnes/personne)" />
<xsl:value-of select="position()" />

L'instruction value-of possède un autre attribut disable-output-escaping qui peut prendre deux valeurs yes ou no. La valeur no (défaut) indique que les caractères comme < ou > doivent être remplacés par leurs entités : respectivement &lt; et &gt;. On met l'attribut à yes pour contraindre la sortie de ces caractères sans modification. L'appel à l'instruction value-of provoque la sortie de la valeur calculée dans le document final.

Les variables

Un autre besoin est de récupérer une valeur ou des noeuds et simplement de les stocker avant de les traiter. Pour cela, on définit une variable comme dans un langage de programmation quelconque, ici avec un élément noté xsl:variable, un attribut name pour nommer la variable et, à nouveau, un attribut select contenant une requête XPath.

<xsl:variable name="nomdefamille" select="nom" />
<xsl:variable name="ladate"       select="@date" />
<xsl:variable name="nbpersonne"   select="count(/personnes/personne)" />
<xsl:variable name="lapersonne"   select="/personnes/personne[@id='p12']" />
<xsl:variable name="lesgens"      select="/personnes/personne" />

Une fois la variable définie, on peut l'utiliser (typiquement dans une nouvelle requête XPath) en utilisant son nom avec le signe dollar devant :

<xsl:variable name="refpersonne" select="citation/@ref" />
<xsl:value-of select="/personnes/personne[@id=$refpersonne]/nom" />

<xsl:variable name="nb_matches"   select="count(//RENCONTRE)" />
<xsl:variable name="nb_victoires" select="count(//RENCONTRE[@SCORED > @SCOREE])" />
<xsl:variable name="pc_victoires" select="100.*$nb_victoires div $nb_matches" />
<xsl:value-of select="format-number($pc_victoires,'##.##')" />

XSLT est un langage de programmation fonctionnel et par conséquent les variables ne sont pas modifiables. Il est donc déraisonnable d'écrire quelque chose comme :

<xsl:variable name="i" select="$i+1" />

Les instructions copy et copy-of

L'instruction copy-of reproduit un ensemble de noeuds récupéré par un select, avec la sous-arborescence de chacun. L'instruction copy ne recopie que le noeud courant lui-même, sans son contenu et sans ses fils (il va donc falloir définir le nouveau contenu et les nouveaux descendants ; des exemples plus précis seront fournis dans les prochaines sections).

  <xsl:copy-of select="*" />
  <xsl:copy-of select="diplome" />
  <xsl:copy> coucou  </xsl:copy>

Les règles de transformation

Les règles constituent les briques de base : on va y décrire une transformation portant sur un certain type de noeuds. Une telle règle est représentée par l'élément xsl:template et contient le code XML à produire pour créer le nouveau document. Ce code doit être bien formé, cela signifie en particulier que toute balise ouverte dans une règle, doit être refermée dans cette même règle. On y trouve donc des balises du nouveau format (dans la suite on produit du XHTML) mais aussi des instructions XSLT comme celles déjà vues (xsl:value-of, xsl:variable, etc.) et éventuellement des appels à d'autres règles de transformation. Dans un langage de programmation classique, ce bloc peut être identifié à une procédure ou un fonction.

Les principes de base

Dans l'utilisation la plus courante, l'élément xsl:template est muni d'un attribut match contenant une requête XPath : les noeuds concernés par la transformation sont ceux qui vérifient cette requête.

<xsl:template match="diplome">
  . . .
</xsl:template>

<xsl:template match="mesdiplomes">
  . . .
</xsl:template>

Lorsque l'on décrit une règle de transformation, il faut garder à l'esprit que les requêtes XPath sont évaluées par rapport au noeud courant. Dans l'exemple suivant, nom et @année sont respectivement un sous élément et un attribut du diplôme que l'on est en train de transformer.

<xsl:template match="diplome">
  <h2> <xsl:value-of select="nom" /> </h2>
  <p> Obtenu en <xsl:value-of select="@année" /> </p>
</xsl:template>

Enfin, les appels se font à l'aide de l'instruction apply-templates et d'un attribut select contenant une requête XPath : celle-ci sélectionne des noeuds pour lesquels on va chercher des règles de transformation à activer. Ci dessous, une feuille XSLT complète qui commence par traiter la racine.

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
  <body> <xsl:apply-templates select="mesdiplomes" /> </body>
  </html>
</xsl:template>

<xsl:template match="mesdiplomes">
  <h1>Mon cursus</h1>
  <xsl:apply-templates select="diplome" />
</xsl:template>

<xsl:template match="diplome">
  <h2> <xsl:value-of select="nom" /> </h2>
  <p> Obtenu en <xsl:value-of select="@année" /> </p>
</xsl:template>

</xsl:stylesheet>

Précisons que l'attribut select de apply-templates n'est pas obligatoire, il peut être omis. Dans ce cas, on va chercher à appliquer les règles de transformation à chacun des noeuds fils (ce qui revient à poser select="node()" par défaut). Cela est particulièrement intéressant dans le cas d'un contenu mixte :

<xsl:template match="paragraph">
  <p> <xsl:apply-templates /> </p>
</xsl:template>

<xsl:template match="important">
  <em><xsl:value-of select="." /></em>
</xsl:template>

Lors de la transformation d'un paragraphe, les noeuds texte seront recopiés à l'identique, tandis que les éléments important seront traités par la seconde règle. Terminons avec des utilisations des instructions copy-of et copy. L'exemple suivant remplace les éléments paragraph en éléments p tandis que le contenu, probablement mixte (texte et balises), est recopié à l'identique :

<xsl:template match="paragraph">
  <p> <xsl:copy-of select="*" /> </p>
</xsl:template>

La règle ci-dessous permet de récupérer l'ensemble d'un document XML en ajoutant un attribut à chacune des balises ouvertes :

<xsl:template match="*|/">
  <xsl:copy>
    <xsl:attribute name="lang">fr</xsl:attribute>
    <xsl:apply-templates />
  </xsl:copy>
</xsl:template>

Les modes

Pour pouvoir traiter plusieurs fois mais de manière différente les mêmes éléments, il est possible de spécifier un mode, dans la règle de transformation et dans son appel.

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
  <body>
  <h1>Mon cursus</h1>
    <ul>
      <xsl:apply-templates select="diplome" mode="sommaire" />
    </ul>
    <xsl:apply-templates select="diplome" mode="tout" />
  </body>
  </html>
</xsl:template>

<xsl:template match="diplome" mode="sommaire" >
  <li> <xsl:value-of select="nom" /> </li>
</xsl:template>

<xsl:template match="diplome" mode="tout">
  <h2> <xsl:value-of select="nom" /> </h2>
  <p> Obtenu en <xsl:value-of select="@année" /> </p>
</xsl:template>

</xsl:stylesheet>

Notations et instructions alternatives

Une difficulté apparaît lorsque l'on veut faire apparaître une valeur récupérée par value-of comme valeur d'attribut dans le nouveau document. Cela conduirait à un code XML mal formé :

<a href="<xsl:value-of select="lien/@url" />"> <xsl:value-of select="lien/texte" /> </a>

Dans ce cas, on préférera utiliser une notation entre accolades :

<a href="{lien/@url}"> <xsl:value-of select="lien/texte" /> </a>

Une autre possibilité est de recourir aux instructions xsl:element et xsl:attribute :

    <xsl:element name="a">
      <xsl:attribute name="href">
        <xsl:value-of select="lien/@url" />
      </xsl:attribute>
      <xsl:value-of select="lien/texte" />
    </xsl:element>

Les règles implicites et les priorités

D'après la recommandation du W3C, les règles suivantes sont toujours implicites :

<xsl:template match="*|/">
  <xsl:apply-templates />
</xsl:template>

<xsl:template match="text()|@*">
  <xsl:value-of select="." />
</xsl:template>

<xsl:template match="processing-instruction()|comment()" />

Autrement dit, la première transformation que nous avons considérée et qui était vide, produit déjà un résultat : le contenu textuel de chaque élément. Il faut observer que le premier xsl:apply-template sélectionne tous les noeuds qui ne sont pas des attributs, par conséquent la deuxième règle produira le contenu textuel des éléments mais pas celui des attributs puisque ceux-ci ne lui sont pas fournis. Il apparaît maintenant que, pour un noeud sélectionné, plusieurs règles de transformation peuvent s'appliquer et il s'agit d'en choisir une. Pour cela, chaque règle de transformation se voit attribuer une priorité, cela permet de déterminer quelle règle doit être appliquée lorsqu'un noeud en active plusieurs. L'attribution des priorités est la suivante et traduit l'intuition que la règle la plus spécifique (selon son match) est celle à appliquer :

Ainsi, les règles implicites ont la priorité la plus basse (-0,5) et perdront systématiquement contre des règles plus spécifiques. En cas d'égalité, la recommandation du W3C laisse le choix aux processeurs XSLT de s'arrêter en signalant une erreur ou de choisir la règle activable avec la priorité la plus haute et qui apparaît en dernier dans le fichier de transformation. Enfin, indiquons qu'il est possible de définir soi-même la priorité d'une règle avec l'attribut priority de l'élément xsl:template.

Les règles nommées et leurs paramètres

Il est possible d'activer une règle simplement en lui donnant un nom et en l'appellant à l'aide de l'instruction call-template :

<xsl:template name="signature">
  <hr />
  Fabien Torre. Copyright 2006.
</xsl:template>

<xsl:template select="...">
    ... <xsl:call-template name="signature" /> ...
</xsl:template>

On voit ici clairement le parallèle avec les procédures et fonctions des autres langages de programmation et l'étape suivante est naturellement de munir les règles de transformation de paramètres. Cela se fait avec xsl:param pour la déclaration des paramètres dans la règle et xsl:with-param pour leur instanciation au moment de l'appel :

<xsl:template name="faireunlien">
    <xsl:param name="url" />
    <xsl:param name="texte" />
    <a href="{$url}">
        <xsl:value-of select="$texte" />
    </a>
</xsl:template>

<xsl:template select="personne" mode="lien">
    <xsl:call-template name="faireunlien">
        <xsl:with-param name="texte" select="concat(prénom,' ',nommarital,' ',nom)" />
        <xsl:with-param name="url"   select="@adresse" />
    </xsl:call-template>
</xsl:template>

Les règles récursives

Terminons avec un cas particulier de l'appel de règle, celui où appelante et appelée ne sont qu'une seule et même règle. L'exemple suivant montre une telle règle qui produit le chemin conduisant au noeud courant :

<xsl:template match="domaine">
  <xsl:apply-templates select="parent::domaine" />
  <li><a href="{@id}.html"><xsl:value-of select="@intitulé" /></a></li>
</xsl:template>

Naturellement, la récursivité est encore plus explicite avec une règle nommée.

<xsl:template name="ancêtres">
  <xsl:param name="noeud" />
  <xsl:if test="name($noeud)='domaine'">
    <xsl:call-template name="ancêtres">
      <xsl:with-param name="noeud" select="$noeud/parent::*" />
    </xsl:call-template>
    <li><a href="{$noeud/@id}.html"><xsl:value-of select="$noeud/@intitulé" /></a></li>
  </xsl:if>
</xsl:template>

Les structures de contrôle

Le test unique avec xsl:if

Comme dans tout langage de programmation, on retrouve avec xsl:if la possibilité de faire un test et des actions en fonction du résultat. Ici, on regarde si un attribut est renseigné et on produit du code seulement si c'est le cas :

<xsl:if test="@mail != ''"> <a href="{@mail}">m'écrire</a> </xsl:if>

Dans la cas suivant, on récupère un ensemble de noeuds et on vérifie qu'il est non vide avant de le traiter :

    <xsl:variable name="tout" select="mesdiplomes/diplome" />
    <xsl:if test="count($tout)>0">
       <h2>Mon cursus</h2>
       <xsl:apply-templates select="$tout" />
    </xsl:if>

Notons que cette instruction if ne dispose pas de else.

Tests avec xsl:choose, xsl:when et xsl:otherwise

Par contre, XSLT fournit l'instruction xsl:choose dans laquelle on peut multiplier les xsl:when (qui ressemblent à des xsl:if) et peut se terminer par xsl:otherwise pour traiter les cas qui auraient échappé à tous les tests :

  <xsl:choose>
    <xsl:when test="starts-with(@href,'#')">
      <a href="@href">lien interne</a>
    </xsl:when>
    <xsl:when test="starts-with(@href,'mailto:')">
      <a href="@href">adresse mail</a>
    </xsl:when>
    <xsl:otherwise>
      <a href="{@href}">lien web</a>
    </xsl:otherwise>
  </xsl:choose>

La boucle for-each

On a vu que les itérations étaient assurées par le mécanisme d'activation des règles. Celui-ci est suffisant et apparente XSLT à la famille des langages fonctionnels. On trouve cependant en XSLT une boucle classique des langages impératifs :

<xsl:for-each select="mesdiplomes/diplome">
  <h2> <xsl:value-of select="nom" /> </h2>
  <p> Obtenu en <xsl:value-of select="@année" /> </p>
</xsl:for-each>

Autres possibilités de XSLT

Trier des noeuds sélectionnés

Que ce soit pour un apply-templates ou une boucle for-each, il est possible d'ordonner les noeuds sélectionnés avant de les traiter. L'instruction xsl:sort doit obligatoirement être la première instruction dans le xsl:for-each.

<xsl:template match="/">
  <html> <body> <h1>Mon cursus</h1>
  <xsl:apply-templates select="diplome">
    <xsl:sort select="@année" order="descending" />
  </xsl:apply-templates>
  </body> </html>
</xsl:template>
<xsl:template match="/">
  <html> <body> <h1>Mon cursus</h1>
  <xsl:for-each select="diplome">
    <xsl:sort select="@année" order="descending" />
    <h2><xsl:value-of select="nom" /></h2>
    <p>Obtenu en <xsl:value-of select="@année" /></p>
  </xsl:for-each>
  </body> </html>
</xsl:template>

Produire du texte

Par exemple pour contraindre deux valeurs extraites à être séparées par un espace :

<xsl:value-of select="@prénom" />
<xsl:text> </xsl:text>
<xsl:value-of select="@nom" />

Valid XHTML 1.0 Strict