Rédaction : Fabien Torre,
révision : Fred Mesnard.
Recommandation du W3C :
XSLT 2.0
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.
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.
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
<
et >
. 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.
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" />
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 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.
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>
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>
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>
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 :
"element"
et "@att"
ont chacun une priorité valant 0 ;"node()"
, "*"
, "@*"
, et "processing-instruction()"
ont chacun une priorité valant -0,5 ;
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
.
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>
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>
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.
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>
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>
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>
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" />