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 (HTML, PDF, etc.), pérennité des documents, ouverture des formats, interopérabilité, etc.
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 :
- les CSS ne permettent pas d'extraire les valeurs des attributs pour les faire apparaître ;
- il est possible avec les CSS de placer les blocs les uns par rapport aux autres, d'en faire disparaître certains, mais pas de tout réorganiser de fond en comble, encore moins de créer de nouvelles données ;
- le langage CSS n'est pas un langage XML.
Cela amène à la définition d'un nouveau format : XSL pour eXtensible Stylesheet Language. Cependant, les critiques des CSS ont 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 donc 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, etc.
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
Terminons avec OpenOffice qui est lui aussi capable d'appliquer des transformations XSLT pour charger des documents dans un format XML quelconque.
Extraire 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'en é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. Vaut no par défaut indiquant que les caractères comme < ou > doivent être remplacés par leurs entités : respectivement < et >. On le met donc à yes pour contraindre la sortie de ces caractères sans modification.
L'appel à linstruction 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" />
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 elle 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).
<xsl:copy-of select="*" /> <xsl:copy-of select="diplome" /> <xsl:copy> coucou </xsl:copy>
Des exemples plus précis seront fournis dans les prochaines sections.
Les règles de transformation
Les règles constituent les briques de base : on va y décrire une transformation portant sur un certains 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 aux 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 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 implicitement définies dans toute feuille de style :
<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 :
- "element" et "@att" ont chacun une priorité valant 0 ;
- "node()", "*", "@*", et "processing-instruction()" ont chacun une priorité valant -0,5 ;
- +0,5 dans les autres cas.
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.
<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>
L'instruction xsl:sort doit obligatoirement être la première instruction dans le xsl:for-each.
Paramètres externes
On a parfois envie de passer des paramètres à la feuille XSLT pour conditionner la sortie, par exemple pour produire des documents différents à partir du même document d'origine. Pour cela, on déclare les paramètres comme pour des templates paramétrés, sauf que ceux-ci sont des fils directs de l'élément xsl:stylesheet :
<?xml version="1.0" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:param name="cible" /> <xsl:param name="refpersonne" /> . ... ici des règles de transformation ... .
Il reste à préciser les valeurs de ces paramètres au moment de la transformation. Avec xsltproc, cela se fait comme suit :
xsltproc --stringparam cible index doc2html.xsl doc.xml > index.html xsltproc --stringparam cible unepersonne --stringparam refpersonne p12 doc2html.xsl doc.xml > doc.html
Conditionner la sortie
L'instruction xsl:output peut apparaître comme fils direct de l'élément xsl:stylesheet et permet de préciser la sortie voulue, en particulier à travers les attributs suivants :
- method : indication de ce que l'on veut produire (xml, html ou text) ;
- doctype-public : la DTD respectée par le document final (le processeur ajoutera de lui même la ligne DOCTYPE qui convient) ;
- omit-xml-declaration : comme son nom l'indique ;
- indent : indenter ou pas le document produit ;
- encoding : le codage à utiliser pour la sortie ;
- media-type : le type MIME du document produit.
Un exemple :
<?xml version="1.0" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" /> . . .
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" />