Le langage XSL (pour XML Stylesheet Language) a été conçu pour transformer des documents XML en d'autres formats comme PDF ou des pages HTML. Au cours de son développement, le projet s'est avéré plus complexe que prévu et il a été scindé en deux unités distinctes XSLT et XSL-FO. Le langage XSLT (pour XML Stylesheet Language Transformation) est un langage de transformation de documents XML. Le langage XSL-FO (pour XML Stylesheet Language - Formatting Objets) est un langage de mise en page de document. Le processus de transformation d'un document XML en un document imprimable, au format PDF par exemple, est donc découpé en deux phases. Dans la première phase, le document XML est transformé en un document XSL-FO à l'aide de feuilles de style XSLT. Dans la seconde phase, le document FO obtenu à la première phase est converti par un processeur FO en un document imprimable.
Même si le langage XSLT puise son origine dans la transformation de documents XML en document XSL-FO, il est adapté à la transformation d'un document de n'importe quel dialecte XML dans un document de n'importe quel autre dialecte XML. Il est souvent utilisé pour produire des documents XSL-FO ou XHTML mais il peut aussi produire des documents SVG. Le langage XSLT est aussi souvent utilisé pour réaliser des transformations simples sur des documents. Il s'agit, par exemple, de supprimer certains éléments, de remplacer un attribut par un élément ou de déplacer un éléments.
Ce chapitre est consacré à la partie XSLT de XSL. Comme l'ensemble
de cet ouvrage, il est essentiellement basé sur des exemples disponibles à
l'URL
http://www.liafa.univ-paris-diderot.fr/~carton/Enseignement/XML/
Le principe de fonctionnent de XSLT est le suivant. Une feuille de style XSLT contient des règles qui décrivent des transformations. Ces règles sont appliquées à un document source XML pour obtenir un nouveau document XML résultat. Cette transformation est réalisée par un programme appelé processeur XSLT. La feuille de style est aussi appelée programme dans la mesure où il s'agit des instructions à exécuter par le processeur.
La version 2.0 de XSLT a introduit un certain nombre d'évolutions par rapport à la version 1.0. Une première évolution importante est l'utilisation de XPath 2.0 à la place de XPath 1.0. Une seconde évolution importante est la possibilité de traiter un document validé au préalable par un schéma XML.
L'intérêt de cette validation est d'associer un type à chaque
contenu d'élément et à chaque valeur d'attribut. Si le type d'un
attribut est, par exemple, xsd:integer
et que sa
valeur 123
, celle-ci est interprétée comme un entier
et non comme une chaîne de caractères à la manière de XSLT 1.0. La
validation par un schéma n'est cependant pas nécessaire pour utiliser
XSLT 2.0 et profiter des évolutions par rapport à XSLT 1.0. Il existe
donc deux façons de traiter un document avec XSLT 2.0, soit sans schéma
soit avec schéma. La première façon correspond au fonctionnement de XSLT
1.0. La seconde façon prend en compte les types associés aux nœuds par
la validation.
Pour la version 1.0 de XSLT, il existe plusieurs processeurs libres
dont le plus répandu est xsltproc
. Il est très
souvent déjà installé sur les machines car il fait partie de la librairie
standard libxslt
. En revanche, il n'implémente que la
version 1.0 de la norme avec quelques extensions. Le logiciel
saxon
implémente la XSLT 2.0 mais la version gratuite
n'implémente que le traitement sans schéma. Il n'existe pas actuellement
de processeur libre implémentant le traitement avec schéma. Pour cette
raison, ce chapitre se concentre sur le traitement sans schéma même si
l'essentiel reste encore valable dans le traitement avec schéma.
Le langage XSLT est un dialecte XML. Ceci signifie qu'une feuille de style XSLT est un document XML qui peut lui-même être manipulé ou produit par d'autres feuilles de style. Cette possibilité est d'ailleurs exploité par schematron pour réaliser une validation en plusieurs phases.
On commence par la feuille de style XSLT la plus simple. Cette-ci
se contente de produire un document XHTML affichant le message
Hello World!
dans un titre. Cette feuille de style a
donc la particularité de produire un document indépendant du
document source.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:template match="/">
<html>
<head> <title>Hello World!</title> </head> <body> <h1>Hello world!</h1> </body> </html> </xsl:template> </xsl:stylesheet>
Élément racine | |
Déclaration de l'espace de noms XSLT associé au préfixe
| |
Déclaration de l'espace de noms XHTML comme espace de noms par défaut. | |
Définition d'une règle s'appliquant à la racine
| |
Fragment de document XHTML retourné par la règle. |
La feuille de style est contenue dans l'élément racine
xsl:stylesheet
. Elle est constituée d'une seule règle
introduite par l'élement xsl:template
dont l'attribut
match
précise que cette règle s'applique à la racine
du document source. L'élément xsl:template
contient
le document XHTML produit. En appliquant ce programme à n'importe quel
document XML, on obtient le résultat suivant qui est un document XHTML
valide.
<?xml version="1.0" encoding="utf-8"?> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>Hello World!</title> </head> <body> <h1>Hello world!</h1> </body> </html>
L'entête XML du résultat à été automatiquement mise par le
processeur XSLT. Comme le codage du document
résultat n'est pas spécifié par la feuille de style, c'est le codage
UTF-8 par défaut de XML qui a été utilisé. Le processeur a inséré la
déclaration de l'espace de noms XHTML dans l'élément racine
html
du document. Le processeur a également ajouté
l'élément meta
propre à HTML pour préciser le codage
des caractères. Le fonctionnement précis de cette feuille de style
est explicité après l'explication générale.
Le document XML source est transformé en un document XML résultat obtenu en appliquant les règles de la feuille de style à des nœuds du document source. Chaque application de règle à un nœud produit un fragment de document XML. Tous ces fragments sont assemblés pour former le document résultat.
Chaque fragment produit par l'application d'une règle est une suite de nœuds représentant des éléments, des attributs, des instructions de traitement et des commentaires. Il s'agit le plus souvent d'une suite d'éléments ou d'attributs. Lors de l'assemblage des fragments, ces nœuds viennent s'insérer à l'intérieur d'un autre fragment.
Chaque règle est déclarée par un élément xsl:template
. Le contenu de cet élément est le
fragment de document qui est produit par l'application de cette règle.
Ce contenu contient des éléments de l'espace de noms XSLT et des éléments
d'autres espaces de noms. Ces derniers éléments sont recopiés à
l'identique pour former le fragment. Les éléments de XSLT sont des
instructions qui sont exécutées par le processeur XSLT. Ces éléments
sont remplacés dans le fragment par le résultat de leur exécution.
L'élément essentiel est l'élément xsl:apply-templates
qui permet d'invoquer
l'application d'autres règles. Les fragments de document produits par
ces applications de règles remplacent l'élément
xsl:apply-templates
. L'emplacement de l'élément
xsl:apply-templates
constitue donc le point d'ancrage
pour l'insertion du ou des fragments produits par son exécution.
La déclaration d'une règle XSLT prend la forme globale suivante.
<xsl:template match="..."> <!-- Fragment produit --> ... <!-- Application de règles --> <xsl:apply-templates .... /> ... <!-- Application de règles --> <xsl:apply-templates .... /> ... <xsl:template/>
Chacune des règles est déclarée avec un élément xsl:template
dont l'attribut match
précise sur quels nœuds elle est susceptible
d'être appliquée. Le processus de transformation consiste à appliquer
des règles sur des nœuds dits actifs du documents
source. Au départ, seule la racine est active et la première règle est
donc appliquée à cette racine. L'application de chaque règle produit un
fragment de document qui va constituer une partie du document résultat.
Elle active d'autres nœuds avec des éléments xsl:apply-templates
placés au sein du fragment de
document. Des règles sont alors appliquées à ces nouveaux nœuds actifs.
D'une part, elles produisent des fragments de documents qui remplacent
dans le document résultat les éléments
xsl:apply-templates
qui les ont provoquées. D'autre
part, elles activent éventuellement d'autres nœuds pour continuer le
processus. Ce dernier s'arrête lorsqu'il n'y a plus de nœuds
actifs.
Le processus de transformation s'apparente donc à un parcours de
l'arbre du document source. Il y a cependant une différence importante.
Dans un parcours classique d'un arbre comme les parcours en largeur ou en
profondeur, le traitement d'un nœud entraîne le traitement de ses
enfants. L'application d'une règle XSLT active d'autres nœuds mais
ceux-ci ne sont pas nécessairement les enfants du nœud sur lequel la
règle s'applique. Les nœuds activés sont déterminés par l'attribut
select
des éléments
xsl:apply-templates
. Chaque attribut
select
contient une expression XPath dont l'évaluation
donne la liste des nœuds activés.
La figure ci-dessous illustre la construction de l'arbre résultat
par l'application des règles XSLT. Chacun des triangles marqués
template
représente un fragment de document produit
par l'application d'une règle. Tous ces triangles sont identiques sur la
figure même si les fragments qu'ils représentent sont différents. Le
triangle tout en haut représente le fragment produit par la règle
appliquée au commencement à la racine du document. Les flèches marquées
apply-templates
symbolisent l'application de nouvelles
règles par l'activation de nœuds. L'arbre du document résultat est, en
quelque sorte, obtenu en contractant ces flèches marquées
apply-templates
et en insérant à leur point de départ
le triangle sur lequel elles pointent. Les flèches partant d'un même
triangle peuvent partir de points différents car un même fragment de
document peut contenir plusieurs éléments
xsl:apply-templates
.
Il est maintenant possible de revenir sur le premier programme
Hello, Word!
et d'en expliquer le fonctionnement. Ce
programme contient une seule règle qui s'applique à la racine du document
source. Comme le premier nœud actif au départ est justement la racine,
le processus commence par appliquer cette règle. Le document résultat
est construit en ajoutant à sa racine le contenu de la règle. Comme ce
contenu ne contient aucun élément xsl:apply-templates
,
aucun nouveau nœud n'est rendu actif et le processus de transformation
s'arrête après l'application de cette première règle.
Le programme ou feuille de style est
entièrement inclus dans un élément racine
xsl:stylesheet
ou de façon complètement équivalente un
élément xsl:transform
. L'attribut version
précise la version de XSLT utilisée. Les
valeurs possibles sont 1.0
ou 2.0
.
Un processeur XSLT 1.0 signale généralement une erreur lorsque la feuille
de style n'utilise pas cette version. Un processeur XSLT 2.0 passe dans
un mode de compatibilité avec la version 1.0 lorsqu'il rencontre une
feuille de style de XSLT 1.0. La feuille de style déclare plusieurs
espaces de noms. L'espace de noms
des éléments de XSLT doit d'abord être déclaré. Il est identifié par
l'URI http://www.w3.org/1999/XSL/Transform
. Le préfixe
xsl
est généralement associé à cet espace de noms.
Dans tout ce chapitre, ce préfixe est utilisé pour qualifier les éléments
XSLT. Il faut ensuite déclarer les éventuels espaces de noms du document
source et du document résultat. Ces déclarations d'espaces de noms sont
indispensables car le contenu de chaque règle contient un mélange
d'éléments XSLT et d'éléments du document résultat. Les espaces de noms
permettent au processeur XSLT de les distinguer.
Les processeurs XSLT ajoutent les déclarations nécessaires
d'espaces de noms dans le document résultat. Il arrive que certains
espaces de noms soient déclarés alors qu'ils ne sont pas nécessaires.
L'attribut exclude-result-prefixes
de
xsl:stylesheet
permet d'indiquer que certains espaces
de noms ne seront pas utilisés dans le document résultat et que leurs
déclarations doivent être omises. Cet attribut contient une liste de
préfixes séparés par des espaces. Dans l'exemple suivant, les espaces de
noms associés aux préfixes xsl
(XSLT) et
dbk
(DocBook) ne sont pas déclarés dans le document
résultat.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
exclude-result-prefixes="xsl dbk"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dbk="http://docbook.org/ns/docbook"
xmlns="http://www.w3.org/1999/xhtml">
...
L'élément xsl:output
doit être un enfant de
l'élément xsl:stylesheet
. Il contrôle le format du
document résultat. Son attribut method
qui peut
prendre les valeurs xml
, xhtml
,
html
et text
indique le type de
document résultat produit. Ses attributs encoding
, doctype-public
, doctype-system
précisent respectivement l'encodage
du document, le FPI et l'URL de la DTD. L'attribut
indent
dont la valeur est yes
ou
no
précise si le résultat doit être indenté.
L'indentation est seulement nécessaire si le document doit être lu par un
œil humain. Elle peut sensiblement augmenter la taille du document. Un
exemple typique d'utilisation de xsl:output
pour
produire un document XHTML est le suivant.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> <xsl:output method="xml" encoding="iso-8859-1" doctype-public="-//W3C//DTD XHTML 1.1//EN" doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" indent="yes"/>
Lorsque la feuille de style produit plusieurs documents résultat,
plusieurs autres éléments xsl:output
peuvent
apparaître dans la feuille de style. Ceux-ci doivent avoir un attribut
name
permettant de les identifier. Ces éléments
servent à contrôler le format des documents écrits par des éléments xsl:result-document
.
Avant d'appliquer la feuille de style au document source, un
traitement des caractères
d'espacement est effectué sur le document. Ce traitement
consiste à supprimer certains nœuds textuels ne contenant que des
caractères d'espacement. Seuls les nœuds textuels contenant uniquement
des caractères d'espacement sont concernés. Les nœuds contenant, au
moins, un autre caractère ne sont jamais supprimés par ce traitement.
L'attribut xml:space
ainsi que les éléments xsl:strip-space
et
xsl:preserve-space
contrôlent lesquels des nœuds
textuels sont effectivement supprimés.
La sélection des nœuds textuels à supprimer est basée sur une
liste de noms d'éléments du document source dont les caractères
d'espacement doivent être préservés. Par défaut, cette liste contient
tous les noms d'éléments et aucun nœud textuel contenant des caractères
d'espacement n'est supprimé. L'élément
xsl:strip-space
permet de retirer des noms d'éléments
de cette liste et l'élément xsl:preserve-space
permet
d'en ajouter. Ce dernier élément est rarement utilisé puisque la liste
contient, au départ, tous les noms d'éléments. L'attribut
elements
des éléments
xsl:strip-space
et
xsl:preserve-space
contient une liste de noms
d'éléments (à retirer ou à ajouter) séparés par des espaces. Cet
attribut peut également contenir la valeur '*'
ou des valeurs de la forme
tns:*
. Dans ces cas, sont retirés (pour
xsl:strip-space
) ou ajoutés (pour
xsl:preserve-space
) à la liste tous les noms
d'éléments ou tous les noms d'éléments de l'espace de noms associé au
préfixe tns
.
Un nœud textuel est retiré de l'arbre du document si les deux
conditions suivantes sont vérifiées. Il faut d'abord que le nom de
son parent n'appartienne pas à la liste des noms d'éléments dont les
espaces doivent être préservés. Ceci signifie que le nom de son
parent a été retiré de la liste grâce à un élément
xsl:strip-space
. Il faut ensuite que la valeur
de l'attribut xml:space
donné au plus proche parent
contenant cet attribut ne soit pas la valeur
preserve
. La valeur par défaut de cet attribut
est default
qui autorise la suppression.
La feuille de style suivante recopie le document source mais les
nœuds textuels contenant uniquement des caractères d'espacement sont
supprimés du contenu des éléments strip
. Afin
d'observer les effets de xsl:strip-space
, il est
nécessaire que la valeur de l'attribut indent
de
l'élément xsl:output
soit no
pour
qu'aucun caractère d'espacement ne soit ajouté pour
l'indentation.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="iso-8859-1" indent="no"/> <xsl:strip-space elements="strip"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Le document suivant contient des éléments preserve
et des éléments strip
.
<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?> <list> <sublist> <preserve> <p>a b c</p> <p> a b c </p> </preserve> <strip xml:space="preserve"> <p>a b c</p> <p> a b c </p> </strip> <strip> <p>a b c</p> <p> a b c </p> </strip> </sublist> <sublist xml:space="preserve"> <strip> <p>a b c</p> <p> a b c </p> </strip> <strip xml:space="default"> <p>a b c</p> <p> a b c </p> </strip> </sublist> </list>
Si la feuille de style précédente est appliquée au document
précédent, certains espaces sont supprimés. Aucun des espaces contenus
dans les éléments p
n'est supprimé car ces éléments
ont un seul nœud textuel comme enfant et ce nœud textuel contient des
caractères autres que des espaces. En revanche, des retours à la ligne
entre les balises <strip>
et
<p>
ainsi que les espaces entre les balises
</p>
et <p>
disparaissent car les nœuds textuels qui les contiennent ne contiennent
que des caractères d'espacement.
<?xml version="1.0" encoding="iso-8859-1"?> <list> <sublist> <preserve> <p>a b c</p> <p> a b c </p> </preserve> <!-- La valeur "preserve" de l'attribut xml:space inhibe la suppression des espaces --> <strip xml:space="preserve"> <p>a b c</p> <p> a b c </p> </strip> <strip><p>a b c</p><p> a b c </p></strip> </sublist> <sublist xml:space="preserve"> <!-- La valeur "preserve" de l'attribut xml:space du parent inhibe la suppression des espaces --> <strip> <p>a b c</p> <p> a b c </p> </strip> <!-- La valeur "default" de l'attribut xml:space masque la valeur "preserve" de l'attribut du parent --> <strip xml:space="default"><p>a b c</p><p> a b c </p></strip> </sublist> </list>
Les deux éléments xsl:template
et
xsl:apply-templates
constituent le cœur de XSLT. Ils
permettent respectivement de définir des règles et de les appliquer à
des nœuds. Chaque définition de règle par un élément
xsl:template
précise, par un motif XPath, les nœuds
sur lesquels elle s'applique. Chaque application de règles par un
élément xsl:apply-templates
précise les nœuds activés
auxquels doit être appliquée une règle mais ne spécifie pas du tout la
règle à appliquer. Le choix de la règle à appliquer à chaque nœud est
fait par le processeur XSLT en fonction de priorités entre les règles. Ces
priorités sont déterminées à partir des motifs XPath de chaque règle. Ce
principe de fonctionnement est le mode normal de fonctionnement de XSLT.
Il est, cependant, utile à l'occasion d'appliquer une règle déterminée à
un nœud. L'élément xsl:call-template
permet l'appel
explicite d'une règle par son nom.
Les définitions de règles sont globales. Tous les éléments
xsl:template
sont enfants de l'élément racine
xsl:stylesheet
et la portée des règles est
l'intégralité de la feuille de style. Au contraire, les éléments
xsl:apply-templates
et
xsl:call-template
ne peuvent apparaître que dans le
contenu d'un élément xsl:template
.
L'élément xsl:template
permet de définir une
règle. Cet élément est nécessairement enfant de l'élément racine
xsl:stylesheet
. L'attribut match
qui contient un motif XPath
définit le contexte de la règle, c'est-à-dire, les nœuds sur lesquels
elle s'applique. L'attribut name
donne le nom de la
règle. Un de ces deux attributs doit être présent mais les deux peuvent
être simultanément présents. L'attribut match
permet
à la règle d'être invoquée implicitement par un élément
xsl:apply-templates
alors que l'attribut
name
permet à la règle d'être appelée explicitement
par un élément xsl:call-template
. L'élément
xsl:template
peut aussi contenir un attribut
priority
pour fixer la priorité de la règle ainsi qu'un
attribut mode
pour préciser dans quels modes elle peut être appliquée. Le
contenu de l'élément xsl:template
est le fragment de
document produit par la règle. Il est inséré dans le document résultat
lors de l'application de la règle.
Le fragment de programme suivant définit une règle. La valeur de
l'attribut match
vaut '/'
et indique donc que la règle s'applique
uniquement à la racine de l'arbre. La racine de l'arbre résultat est
alors le fragment XHTML contenu dans xsl:template
.
Comme ce fragment ne contient pas d'autres directives XSLT, le
traitement de l'arbre source s'arrête et le document résultat est réduit
à ce fragment.
<xsl:template match="/"> <html> <head> <title>Hello, World!</title> </head> <body> <h1>Hello, world!</h1> </body> </html> </xsl:template>
La règle ci-dessous s'applique à tous les éléments de nom
author
, year
ou
publisher
.
<xsl:template match="author|year|publisher">
...
</xsl:template>
Lorsqu'une règle est appliquée à un nœud du document source, ce
nœud devient le nœud
courant du focus. Les expressions XPath sont alors évaluées à
partir de ce nœud qui est retourné par l'expression
'.'
. Certains éléments comme xsl:for-each
ou
les filtres peuvent localement
changer le focus et le nœud courant. Le nœud sur lequel est appliquée
la règle peut encore être recupéré par un appel à la fonction XPath
current()
.
L'élément xsl:apply-templates
provoque
l'application de règles sur les nœuds sélectionnés par son attribut
select
qui contient une expression XPath.
L'évaluation de cette expression par rapport au nœud courant donne une
liste de nœuds du document source. À chacun de ces nœuds devenus
actifs, une règle choisie par le processeur XSLT est appliquée. Le
résultat de ces application de règles remplace alors l'élément
xsl:apply-templates
dans le contenu de l'élément
xsl:template
pour former le résultat de la règle en
cours d'application.
L'expression XPath de l'attribut select
sélectionne, en général, plusieurs nœuds. Sur chacun de ces nœuds, est
appliquée une seule règle dépendant du nœud. La règle est choisie parmi
les règles où il y a concordance entre le motif XPath de l'attribut
match
et le nœud. Lors de l'application de la règle
choisie, chaque nœud sélectionné devient le nœud courant du focus. Pour appliquer
plusieurs règles à un même nœud, il faut plusieurs éléments
xsl:apply-templates
. Il est, en particulier,
possible d'avoir recours à des modes pour appliquer des règles
différentes.
La valeur par défaut de l'attribut select
est
node()
qui sélectionne tous les enfants du
nœud courant, y compris les nœuds textuels, les commentaires et les
instructions de traitement mais pas les attributs. Pour sélectionner
uniquement les enfants qui sont des éléments, il faut mettre la valeur
'*'
. Pour sélectionner tous les attributs du nœud
courant, il faut mettre la valeur @*
qui est
l'abréviation de attribute::*
. Il est, bien sûr,
possible de mettre toute expression XPath comme
ancestor-or-self::p[@xml:lang][1]/@xml:lang
. Une
partie importante de la programmation XSLT réside dans les choix
judicieux des valeurs des attributs select
des
éléments xsl:apply-templates
ainsi que des valeurs
des attributs match
des éléments
xsl:template
.
Dans la feuille de style suivante, la première règle qui
s'applique à la racine '/'
crée la structure du
document XHTML. Les enfants li
de l'élément
ul
sont créés par les applications de règles sur les
éléments book
provoquées par le premier élément
xsl:apply-templates
. Les contenus des éléments
li
sont construits par les applications de règles sur
les enfants des éléments book
provoquées par le
second élément xsl:apply-templates
.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> <xsl:template match="/"> <html> <head><title>Bibliographie</title></head> <body> <h1>Bibliographie</h1> <ul><xsl:apply-templates select="bibliography/book"/></ul> </body> </html> </xsl:template> <xsl:template match="book"> <!-- Une entrée (item) de la liste par livre --> <li><xsl:apply-templates select="*"/></li> </xsl:template> ... </xsl:stylesheet>
Les nœuds sur lesquels sont appliqués les règles sont très souvent
des descendants et même des enfants du nœud courant mais ils peuvent
également être très éloignés dans le document. Dans l'exemple suivant,
les éléments sont retournés par la fonction XPath id()
. Il faut
remarquer que l'élément xsl:for-each
change le focus
et que l'expression XPath '.'
ne désigne plus le nœud
courant sur lequel s'applique la règle.
<xsl:template match="dbk:callout">
<xsl:text>\item[</xsl:text>
<!-- Parcours des éléments référencés par l'attribut arearefs -->
<xsl:for-each select="id(@arearefs)">
<xsl:apply-templates select="."/>
</xsl:for-each>
<xsl:text>]</xsl:text>
<xsl:apply-templates/>
</xsl:template>
Les nœuds sélectionnés par l'expression XPath de l'attribut
select
sont normalement traités dans l'ordre du document. L'élément
xsl:apply-templates
peut, néanmoins, contenir un ou
plusieurs éléments xsl:sort
pour effectuer un tri des nœuds sélectionnés.
Lorsque plusieurs règles peuvent s'appliquer à un même nœud, le processeur XSLT choisit la plus appropriée parmi celles-ci. Le choix de la règle est d'abord dicté par les priorités d'import entre les feuilles de style puis par les priorités entre les règles.
Le choix de la règle à appliquer s'effectue en deux étapes. La première étape consiste à ne conserver que les règles de la feuille de style de priorité d'import maximale parmi les règles applicables. Lorsqu'une feuille de style est importée, elle a une priorité d'import inférieure à la feuille de style réalisant l'import. L'ordre des imports a également une incidence sur les priorités d'import. Lorsque plusieurs feuilles de style sont importées par une même feuille de style, les feuilles importées en premier ont une priorité inférieure à celles importées ensuite.
La seconde étape consiste à choisir, parmi les règles restantes,
celle qui a la priorité maximale. C'est une erreur s'il y en a
plusieurs de priorité maximale. Le processeur XSLT peut signaler
l'erreur ou continuer en choisissant une des règles. La priorité d'une
règle est un nombre décimal. Elle peut est fixée par un attribut
priority
de l'élément
xsl:template
. Sinon, elle prend une valeur par
défaut qui dépend de la forme du motif contenu dans l'attribut
match
. Les priorités par défaut prennent des valeurs
entre -0.5
et 0.5
. Lorsque le
motif est de la forme
, le processeur considère
chacun des motifs expr-1
| ... |
expr-N
expr-i
de façon
indépendante. Il fait comme si la règle était répétée pour chacun de
ces motifs. L'axe n'a aucune incidence sur la priorité par défaut.
Celle-ci est déterminée par les règles ci-dessous.
-0.5
pour les motifs très généraux de la forme *
,
@*
, node()
,
text()
, comment()
ainsi que le
motif /
,
-0.25
pour les motifs de la forme
*:
ou
name
comme
prefix
:**:p
ou dbk:*
,
0
pour les motifs de la forme name
ou @
comme
name
section
ou @type
,
0.5
pour tous les autres motifs comme
chapter/section
ou
section[@type]
.
C'est normalement la règle de priorité maximale qui est
appliquée à un nœud lorsque le processus est initié par un élément
xsl:apply-templates
. Il existe également les deux
éléments xsl:next-match
et
xsl:apply-imports
qui permettent d'appliquer une
règle de priorité inférieure. Ces deux éléments appliquent
implicitement une règle à l'élément courant et il n'ont donc pas
d'attribut select
.
L'élément xsl:next-match
applique à l'élément
courant la règle qui se trouvait juste avant la règle en cours
d'application dans l'ordre des priorités croissantes. Cet élément est
souvent utilisé pour définir une règle pour un cas particulier tout en
réutilisant la règle pour le cas général. Dans l'exemple suivant, la
règle spécifique pour les éléments para
avec un
attribut role
égal à right
a une
priorité supérieure à l'autre règle. Celle-ci ajoute un élément
div
avec un attribut style
. Elle
appelle la règle générale pour la règle pour les éléments DocBook
para
grâce à l'élément
xsl:next-match
.
<!-- Paragraphes -->
<xsl:template match="dbk:para">
<p xsl:use-attribute-sets="para"><xsl:apply-templates/></p>
</xsl:template>
<!-- Paragraphes alignés à droite -->
<xsl:template match="dbk:para[@role='right']">
<div style="text-align: right">
<xsl:next-match/>
</div>
</xsl:template>
L'élément xsl:apply-imports
permet d'appliquer
au nœud courant une règle provenant d'une feuille de style importée par la feuille de style
contenant la règle en cours. La règle appliquée est la règle ayant une
priorité maximale parmi les règles provenant des feuilles de style
importées. Cet élément est souvent utilisé pour redéfinir une règle
d'une feuille de style importée tout en utilisant la règle redéfinie.
Dans l'exemple suivant, la feuille de style
general.xsl
contient une règle pour les éléments
DocBook para
.
<!-- Feuille de style general.xsl -->
<!-- Paragraphes -->
<xsl:template match="dbk:para">
<p xsl:use-attribute-sets="para"><xsl:apply-templates/></p>
</xsl:template>
La feuille de style main.xsl
importe la feuille
de style general.xsl
. Elle
contient une nouvelle règle pour les éléments DocBook
para
. Cette règle a une priorité d'import supérieure
et elle est donc appliquée en priorité. Cette règle fait appel à la
règle contenue dans general.xsl
grâce à l'élément
xsl:apply-imports
.
<!-- Feuille de style main.xsl --> <!-- Import de la feuille de style general.xsl --> <xsl:import href="general.xsl"/> <!-- Paragraphes alignés à gauche --> <xsl:template match="dbk:para"> <div style="text-align: left"> <xsl:apply-imports/> </div> </xsl:template>
L'élément xsl:call-template
provoque
l'application de la règle dont le nom est donné par l'attribut
name
. Contrairement à l'élément
xsl:apply-templates
, l'élément
xsl:call-template
ne modifie pas contexte et, donc en
particulier, le nœud
courant. La règle est donc appliquée sur le nœud courant. Le
résultat de l'application de la règle remplace alors l'élément
xsl:call-template
dans le contenu de l'élément
xsl:template
pour former le résultat de la règle en
cours d'application.
L'utilisation de xsl:call-template
est courante
pour factoriser des parties communes à plusieurs règles. Lorsque des
règles partagent un même fragment, il est approprié de placer ce
fragment dans une nouvelle règle nommée puis de l'utiliser en invoquant,
à plusieurs reprises, cette règle avec l'élément
xsl:call-template
.
Dans la feuille de style suivante, les différentes règles pour
traiter les éléments title
, url
et
les autres enfants de l'élément book
font appel à la
règle comma
pour ajouter une virgule si l'élément
n'est pas le dernier enfant. Il est important que le focus soit
préservé à l'appel de cette règle pour que le test position()
!= last()
fonctionne correctement.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> ... <xsl:template match="book"> <!-- Une entrée (item) de la liste par livre --> <li><xsl:apply-templates select="*"/></li> </xsl:template> <xsl:template match="title"> <!-- Titre du livre en italique --> <i><xsl:apply-templates/></i> <xsl:call-template name="comma"/> </xsl:template> <xsl:template match="url"> <!-- URL du livre en police fixe --> <tt><xsl:apply-templates/></tt> <xsl:call-template name="comma"/> </xsl:template> <xsl:template match="*"> <xsl:apply-templates/> <xsl:call-template name="comma"/> </xsl:template> <xsl:template name="comma"> <!-- Virgule si ce n'est pas le dernier --> <xsl:if test="position() != last()"> <xsl:text>, </xsl:text> </xsl:if> </xsl:template> </xsl:stylesheet>
Les deux éléments xsl:apply-templates
et
xsl:call-template
peuvent contenir des éléments
xsl:with-param
pour spécifier les valeurs de paramètres que la règle appliquée peut
avoir déclarés avec l'élément xsl:param
.
Chaque application de règle de la feuille de style produit un
fragment du document résultat. Ce fragment est une liste de nœuds. Il
est construit à partir du contenu de l'élément
xsl:template
et d'autres éléments permettant d'insérer
d'autres nœuds calculés.
Lorsqu'une règle est appliquée, tout le contenu de l'élément
xsl:template
est recopié dans le résultat à
l'exception des éléments de l'espace de noms XSLT. Ces derniers sont,
en effet, évalués par le processeur puis remplacés par leur valeur.
Tout les autres éléments ainsi que leur contenu se retrouvent recopiés à
l'identique dans le document résultat.
Cette fonctionnalité est utilisée dans le premier exemple Hello, Word!
.
L'unique élément xsl:template
contient le document
XHTML produit par la feuille de style. Les espaces de noms jouent ici
un rôle essentiel puisqu'ils permettent de distinguer les directives de
traitement pour le processeur XSLT (c'est-à-dire les éléments XSLT) des
autres éléments.
<xsl:template match="/"> <html> <head> <title>Hello World!</title> </head> <body> <h1>Hello world!</h1> </body> </html> </xsl:template>
L'insertion d'un contenu fixe, comme dans l'exemple précédent, est
très limité. Le résultat attendu dépend généralement du document
source. Dans la règle ci-dessous, le contenu de l'élément
h1
provient du document source. Ce texte est extrait
du document source grâce à l'élément xsl:value-of
décrit ci-dessous. Il est le
contenu textuel de l'élément racine text
du document
source.
<xsl:template match="/">
<html>
<head>
<title>Localized Hello World !</title>
</head>
<body>
<h1><xsl:value-of select="text"/></h1>
</body>
</html>
</xsl:template>
Si la feuille de style ainsi modifiée est appliquée au document suivant,
on obtient un document XHTML où le contenu de l'élément h1
est
maintenant Bonjour !
.
<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?> <!-- Élément text contenant le texte inséré dans l'élément h1 --> <text>Bonjour !</text>
Du contenu peut aussi être construit par un élément
xsl:apply-templates
. Ce contenu est alors le
résultat de l'application de règles aux éléments sélectionnés par
l'attribut select
de
xsl:apply-templates
. On reprend le document
bibliography.xml
déjà utilisé au chapitre sur la
syntaxe.
La feuille de style suivante transforme le document
bibliography.xml
en un document XHTML qui présente
la bibliographie sous forme d'une liste avec une mise en forme
minimaliste. Cette feuille de style fonctionne de la manière suivante.
La première règle est appliquée à la racine du document source. Elle
produit le squelette du document XHTML avec en particulier un élément
ul
pour contenir la liste des livres. Le contenu de
cet élément ul
est construit en appliquant une règle
à chacun des éléments book
par l'intermédiaire d'un
élément xsl:apply-templates
avec un attribut
select
de valeur
bibliography/book
. La seconde règle de la feuille de
style est la règle appliquée aux éléments book
. Pour
chacun de ces éléments, elle produit un élément li
qui s'insère dans le contenu de l'élément ul
. Le
contenu de l'élément li
est, à nouveau, produit en
appliquant une règle aux enfants de l'élément book
par l'intermédiaire d'un élément xsl:apply-templates
dont l'attribut select
a la valeur par défaut
node()
. C'est la dernière règle qui s'applique à
chacun de ces éléments. Cette règle se contente d'appliquer
récursivement une règle à leurs enfants. La règle par défaut pour les nœuds
textuels recopie alors les contenus de ces nœuds.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> <!-- Règle pour la racine qui construit le squelette --> <xsl:template match="/"> <html> <head> <title>Bibliographie</title> </head> <body> <h1>Bibliographie</h1> <ul><xsl:apply-templates select="bibliography/book"/></ul> </body> </html> </xsl:template> <!-- Règle pour les éléments book --> <xsl:template match="book"> <!-- Une entrée li de la liste par livre --> <li><xsl:apply-templates/></li> </xsl:template> <!-- Règle pour les autres éléments --> <xsl:template match="*"> <!-- Récupération du texte par la règle par défaut --> <xsl:apply-templates/> </xsl:template> </xsl:stylesheet>
En appliquant la feuille de style précédente au document
bibliography.xml
, on obtient le document XHTML
suivant dont l'indentation a été remaniée pour une meilleure
présentation.
<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Bibliographie</title> </head> <body> <h1>Bibliographie</h1> <ul> <li>XML langage et appplications Alain Michard 2001 Eyrolles 2-212-09206-7 http://www.editions-eyrolles/livres/michard/ </li> <li>Designing with web standards Jeffrey Zeldman 2003 New Riders 0-7357-1201-8 </li> <!-- Document tronqué --> ... </ul> </body> </html>
Dans la feuille de style précédente, deux règles sont susceptibles
de s'appliquer aux éléments book
. La première est la
règle dont la valeur de l'attribut match
est
book
et la seconde est celle dont la valeur de
l'attribut match
est '*'
. Le
processeur XSLT choisit la première en raison des priorités attribuées aux règles en
fonction du motif contenu dans l'attribut
match
.
La présentation de la bibliographie en XHTML obtenue avec la
feuille de style précédente est très sommaire. Il est possible de
l'améliorer en mettant, par exemple, le titre en italique et l'URL en
fonte fixe. Il suffit, pour cela, d'ajouter deux règles spécifiques
pour les éléments title
et url
.
Les deux nouvelles règles suivantes ont une priorité supérieure à la
dernière règle de la feuille de style. Elles sont donc respectivement
appliquées aux éléments title
et
url
.
<!-- Règle pour les éléments title --> <xsl:template match="title"> <!-- Titre en italique --> <i><xsl:apply-templates/></i> </xsl:template> <!-- Règle pour les éléments url --> <xsl:template match="url"> <!-- URL en fonte fixe --> <tt><xsl:apply-templates/></tt> </xsl:template>
Beaucoup de la programmation XSLT s'effectue en jouant sur les
éléments sélectionnés par les attributs match
des éléments
xsl:apply-templates
. La feuille de style suivante
présente la bibliographie en XHTML sous la forme de deux listes, une pour
les livres en français et une autre pour les livres en anglais. Le
changement par rapport à la feuille de style précédente se situe dans la
règle appliquée à la racine. Celle-ci contient maintenant deux éléments
xsl:apply-templates
. Le premier active les livres en
français et le second les livres en anglais.
<?xml version="1.0" encoding="iso-8859-1" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> <xsl:template match="/"> <html> <head> <title>Bibliographie Français/Anglais</title> </head> <body> <h1>Bibliographie en français</h1> <!-- Traitement des livres avec l'attribut lang="fr" --> <ul><xsl:apply-templates select="bibliography/book[@lang='fr']"/></ul> <h1>Bibliographie en anglais</h1> <!-- Traitement des livres avec l'attribut lang="en" --> <ul><xsl:apply-templates select="bibliography/book[@lang='en']"/></ul> </body> </html> </xsl:template> <!-- Les autres règles sont inchangées --> ... </xsl:stylesheet>
Il est parfois souhaité qu'une feuille de style XSLT produise une
autre feuille de style XSLT comme document résultat. Ce mécanisme est,
en particulier, mis en œuvre par les schematrons. Le problème est que
l'espace de noms XSLT est justement utilisé par le processeur pour
distinguer les éléments XSLT qui doivent être exécutés des autres
éléments qui doivent être recopiés dans le résultat. Il est alors, a
priori, impossible de mettre des éléments XSLT dans le document
résultat. L'élément xsl:namespace-alias
permet de
contourner cette difficulté. Il provoque la conversion d'un espace de
noms de la feuille de style en un autre espace de noms dans le
résultat. Cet élément doit être enfant de l'élément racine
xsl:stylesheet
de la feuille de style. Ses attributs
stylesheet-prefix
et
result-prefix
donnent respectivement les préfixes
associés à l'espace de noms à convertir et de l'espace de noms cible.
Ces deux attributs peuvent prendre la valeur particulière
#default
pour spécifier l'espace de noms par
défaut.
Pour produire des éléments XSLT dans le document résultat, la
feuille de style déclare, d'une part, l'espace de noms XSLT associé,
comme d'habitude, au préfixe xsl
et elle déclare,
d'autre part, un autre espace de noms associé à un préfixe arbitraire
tns
. Les éléments XSLT devant être recopiés dans le
résultat sont placés dans l'espace de noms associé au préfixe
tns
. L'élément
xsl:namespace-alias
assure la conversion de cet
autre espace de noms en l'espace de noms XSLT dans le résultat.
L'exemple suivant est une feuille de style produisant une autre feuille
de style produisant, à son tour, un document XHTML.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tns="http://www.omega-one.org/~carton"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <!-- Le préfix tns est transformé en xsl dans la sortie --> <xsl:namespace-alias stylesheet-prefix="tns" result-prefix="xsl"/> <xsl:template match="/"> <tns:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <tns:output method="xhtml" encoding="iso-8859-1" indent="yes"/> <tns:template match="/"> <html> <head> <title>Exemple d'expressions XPath</title> </head> <body> <h1>Exemple d'expressions XPath</h1> <xsl:apply-templates select="operators/operator"/> </body> </html> </tns:template> </tns:stylesheet> </xsl:template> ...
Il existe des règles par défaut pour traiter les différents nœuds d'un document. Celles-ci simplifient l'écriture de feuilles de style très simples en fournissant un traitement adapté à la plupart des nœuds. Ces règles ont la priorité la plus faible. Elles ne s'appliquent à un nœud que si la feuille de style ne contient aucune règle pouvant s'appliquer à ce nœud. Pour chacun des types de nœuds possibles, il existe une règle par défaut. Ces règles ont le comportement suivant. Elles suppriment les instructions de traitement et les commentaires. Elles recopient dans le résultat le contenu des nœuds textuels et les valeurs des attributs. Le traitement d'un élément par ces règles est d'appliquer récursivement une règle à ses enfants, ce qui exclut les attributs.
La feuille de style suivante est la feuille de style minimale sans aucune définition de règles. Elle est néanmoins utile grâce à la présence des règles par défaut.
<?xml version="1.0" encoding="us-ascii"?> <!-- Feuille de style minimale sans règle --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
En appliquant cette feuille de style minimale au document
bibliography.xml
, on obtient le résultat
suivant.
<?xml version="1.0" encoding="UTF-8"?> XML langage et applications Alain Michard 2001 Eyrolles 2-212-09206-7 http://www.editions-eyrolles/livres/michard/ XML by Example Benoît Marchal 2000 Macmillan Computer Publishing 0-7897-2242-9 XSLT fondamental Philippe Drix 2002 Eyrolles 2-212-11082-0 Designing with web standards Jeffrey Zeldman 2003 New Riders 0-7357-1201-8
Ce résultat s'explique par la présence de règles par défaut qui sont les suivantes.
<!-- Règle pour la racine et les éléments --> <xsl:template match="/|*"> <!-- Traitement récursif des enfants --> <!-- La valeur par défaut de l'attribut select est node() --> <xsl:apply-templates/> </xsl:template> <!-- Règle pour nœuds textuels et les attributs --> <xsl:template match="text()|@*"> <!-- La valeur textuelle est retournée --> <xsl:value-of select="."/> </xsl:template> <!-- Règle pour les intructions de traitement et les commentaires --> <!-- Suppression de ceux-ci car cette règle ne retourne rien --> <xsl:template match="processing-instruction()|comment()"/>
La règle par défaut pour les attributs est de retourner leur
valeur. Les valeurs des attributs n'apparaissent pas dans le résultat
ci-dessus car cette règle par défaut pour les attributs n'est pas
invoquée. En effet, la valeur par défaut de l'attribut
select
de l'élément
apply-templates
est node()
, qui est la forme abrégée de
child::node()
et qui ne sélectionne pas les
attributs.
Il est possible d'insérer directement dans un attribut la valeur
d'une expression XPath. L'expression doit être délimitée dans
l'attribut par une paire d'accolades '{'
et
'}'
. À l'exécution, l'expression est évaluée et le
résultat remplace dans l'attribut l'expression et les accolades qui
l'entourent. L'expression XPath peut simplement être une variable XSLT comme dans le premier
example <body ... >
ci-dessous. Un même
attribut peut contenir un mélange de texte et de plusieurs expressions
XPath comme dans le second exemple <div ... >
ci-dessous.
<body background-color="{$color}"> ... <div style="height: {$height+10}; width: {$width+10}">
Cette syntaxe est déjà plus concise que l'utilisation de l'élément
xsl:attribute
pour
ajouter un attribut et lui donner une valeur. Le premier exemple
ci-dessus est équivalent à l'exemple ci-dessous.
<body> <xsl:attribute name="background-color" select="$color"/>
Les expressions XPath en attribut avec des structures de contrôle XPath sont souvent beaucoup plus concises que les constructions équivalentes en XSLT. L'expression XPath ci-dessous est, par exemple, plus courte que l'équivalent avec les structures de contrôle XSLT.
<a id="{if (@xml:id) then @xml:id else generate-id()}"/>
Pour insérer des accolades ouvrantes ou fermantes dans la valeur
d'un attribut, il faut simplement doubler les accolades et écrire
'{{'
ou '}}'
. Si la règle XSLT
suivante
<xsl:template match="*"> <braces id="{@id}" esc="{{@id}}" escid="{{{@id}}}"/> </xsl:template>
est appliquée à un élément <braces
id="JB007"/>
, on obtient l'élément suivant. La chaîne
{@id}
est remplacée par la valeur de l'attribut
id
. En revanche, la chaîne
{{@id}}
donne la valeur {@id}
où
chaque accolade redoublée donne une seule accolade. Finalement, la
chaîne {{{@id}}}
combine les deux
comportements.
<braces id="JB007" esc="{@id}" escid="{JB007}"/>
Les expressions XPath en attribut apparaissent normalement dans
les attributs des éléments hors de l'espace de noms XSLT. Elles peuvent
égalemement apparaître comme valeur de quelques attributs d'éléments
XSLT. C'est, par exemple, le cas de l'attribut name
des éléments xsl:element
et
xsl:attribute
et de l'attribut
href
de xsl:result-document
.
Ce mécanisme permet d'insérer dans le document résultat des éléments
et/ou des attributs dont les noms sont calculés dynamiquement.
L'élément xsl:text
utilise son contenu pour
créer un nœud textuel dans le document résultat. Les caractères spéciaux
'<'
, '>'
et '&'
sont automatiquement remplacés par les entités prédéfinies
correspondantes si la valeur de l'attribut method
de
l'élément xsl:output
n'est
pas text
. Si la valeur de cet attribut est
text
, les caractères spéciaux sont recopiés à
l'identique.
<xsl:text>Texte et entités '<', '>' et '&'</xsl:text>
La présentation de la bibliographie en XHTML peut être améliorée
par l'ajout de virgules ','
entre le titre, l'auteur,
… de chacun des livres. L'élément xsl:text
est alors
nécessaire pour insérer les virgules. Il faut, en outre, gérer
l'absence de virgule à la fin grâce à l'élément xsl:if
.
<!-- Règle pour les autres éléments -->
<xsl:template match="*">
<xsl:apply-templates/>
<!-- Virgule après ces éléments -->
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
Pour que le test position() != last()
fonctionne correctement, il faut que la règle appliquée aux éléments
book
n'active pas les nœuds textuels contenant des
caractères d'espacements. Il faut, pour cela, remplacer la valeur par
défaut de l'attribut select
par la valeur
'*'
qui ne sélectionne que les enfants qui sont des
éléments et pas ceux de type text()
.
<!-- Règle pour les éléments book -->
<xsl:template match="book">
<!-- Une entrée li de la liste par livre -->
<li><xsl:apply-templates select="*"/></li>
</xsl:template>
L'élément xsl:value-of
crée un nœud textuel
dont le contenu est calculé. L'attribut select
doit
contenir une expression XPath qui est évaluée pour donner une liste de
valeurs. Chacune de ces valeurs est convertie en une chaîne de
caractères. Lorsqu'une valeur est un nœud, la conversion retourne la
valeur textuelle de
celui-ci. Le texte est alors obtenu en concaténant ces différentes
chaînes. Un espace ' '
est inséré, par
défaut, entre ces chaînes. Le caractère inséré peut être changé en
donnant une valeur à l'attribut separator
de
l'élément xsl:value-of
.
La feuille se style suivante collecte des valeurs des enfants
de l'élément racine list
pour former le contenu
d'un élément values
.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<values><xsl:value-of select="list/*" separator=","/></values>
</xsl:template>
</xsl:stylesheet>
Le document suivant comporte un élément racine avec des enfants
item
contenant des valeurs 1
,
2
et 3
.
<?xml version="1.0" encoding="iso-8859-1"?> <list><item>1</item><item>2</item><item>3</item></list>
En appliquant la feuille de style précédente au document
précédent, on obtient le document suivant où apparaissent les trois
valeurs 1
, 2
et
3
séparées par des virgules
','
.
<?xml version="1.0" encoding="iso-8859-1"?>
<values>1,2,3</values>
Le fonctionnement de xsl:value-of
de XSLT 1.0
est différent et constitue une incompatibilité avec XSLT 2.0. Avec XSLT
1.0, seule la première valeur de la liste donnée par l'évaluation de
l'expression de l'attribut select
est prise en compte.
Les autres valeurs sont ignorées. En appliquant la feuille de style
précédente au document précédent avec un processeur XSLT 1.0, on obtient
le document suivant où seule la valeur 1
apparaît.
<?xml version="1.0" encoding="iso-8859-1"?>
<values>1</values>
Quelques exemples d'utilisation de
xsl:value-of
.
<xsl:value-of select="."/> <xsl:value-of select="generate-id()"/> <xsl:value-of select="key('idchapter', @idref)/title"/> <xsl:value-of select="ancestor-or-self::p[@xml:lang][1]/@xml:lang"/>
Tout élément contenu dans un élément
xsl:template
et n'appartenant pas à l'espace de noms
XSLT est recopié à l'identique lors de l'application de la règle. Ceci
permet d'ajouter facilement des éléments et des attributs dont les noms
sont fixes. Il est parfois nécessaire d'ajouter des éléments et/ou des
attributs dont les noms sont calculés dynamiquement. Les éléments
xsl:element
et xsl:attribute
construisent respectivement un nœud pour un élément ou un
attribut. Le nom de l'élément ou de l'attribut est déterminé
dynamiquement par l'attribut name
de
xsl:element
et xsl:attribute
. Cet
attribut contient une expression
XPath en attribut. L'évaluation des expressions XPath délimitées
par une paire d'accolades '{'
et
'}'
fournit le nom de l'élément ou de l'attribut. Le
contenu de l'élément ou la valeur de l'attribut sont donnés par
l'attribut select
ou par le contenu des éléments
xsl:element
et xsl:attribute
. Ces
deux possibilités s'excluent mutuellement. L'attribut
select
doit contenir une expression XPath.
Dans l'exemple suivant, le nom de l'élément est obtenu en
concaténant la chaîne fixe new-
avec la valeur de la
variable XSLT var
.
<xsl:element name="{concat('new-', $var)}">...</element>
Les éléments xsl:element
et
xsl:attribute
sont particulièrement utiles lorsque
l'insertion de l'élément ou de l'attribut est conditionnelle. Le
fragment de feuille de style suivant construit un élément
tr
. Un attribut bgcolor
de valeur #ffffcc
est ajouté lorsque la position de l'élément
courant est paire.
<tr>
<xsl:if test="position() mod 2 = 0">
<xsl:attribute name="bgcolor">#ffffcc</xsl:attribute>
</xsl:if>
...
</tr>
L'élément xsl:element
peut avoir un attribut
use-attribute-set
pour ajouter les attributs déclarés
par des groupes d'attributs.
Il est fréquent qu'une feuille de style ajoute systématiquement les mêmes attributs à différents éléments. Les groupes d'attributs définissent des ensembles d'attributs qu'il est, ensuite, facile d'utiliser pour ajouter des attributs aux éléments construits. Cette technique accroît, en outre, la flexibilité des feuilles de style. Lors de l'import d'une feuille de style, un groupe d'attributs peut être modifié, permettant ainsi, d'adapter la feuille de style sans modifier les règles.
Un groupe d'attributs est introduit par l'élément
xsl:attribute-set
dont l'attribut name
précise le nom. Cet élément doit être un
enfant de l'élément racine xsl:stylesheet
de la
feuille de style. L'élément xsl:attribute-set
contient des déclarations d'attributs introduites par des éléments
xsl:attribute
. Le groupe d'attribut est ensuite
utilisé par l'intermédiaire de l'attribut
use-attribute-sets
. Cet attribut peut apparaître
dans les éléments XSLT xsl:element
et
xsl:copy
mais
il peut aussi apparaître dans d'autres éléments hors de l'espace de noms XSLT. Dans ce dernier
cas, son nom doit être qualifié pour qu'il fasse partie de l'espace de
noms XSLT. L'attribut use-attribute-sets
contient
une liste de noms de groupes d'attributs séparés par des
espaces.
Un groupe d'attributs peut réutiliser d'autres groupes
d'attributs. Il contient alors tous les attributs de ces groupes en
plus de ceux qu'il déclare explicitement. Les noms des groupes
utilisés sont donnés par un attribut
use-attribute-sets
de l'élément
xsl:attribute-set
.
La feuille de style suivante permet la transformation d'un
sous-ensemble, délibérément très réduit, de DocBook en XHTML. Le
sous-ensemble est constitué des éléments book
,
title
, chapter
,
sect1
et para
. La feuille de
style définit trois groupes d'attributs de noms
text
, title
et
para
. Le premier est prévu pour contenir des
attributs généraux pour les éléments XHTML contenant du texte. Il est
utilisé dans les définitions des deux autres groupes
title
et para
. Le groupe
title
est utilisé pour ajouter des attributs aux
éléments h1
et h2
construits par
la feuille de style. Le groupe para
est utilisé pour
ajouter des attributs aux éléments p
.
Il faut remarquer que de nom l'attribut
use-attribute-sets
n'est pas qualifié lorsque
celui-ci apparaît dans un élément XSLT comme
xsl:attribute-set
alors qu'il est qualifié lorsque
celui-ci apparaît dans des éléments hors de l'espace de noms XSLT comme
h1
et p
.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dbk="http://docbook.org/ns/docbook" xmlns="http://www.w3.org/1999/xhtml"> <xsl:output ... /> <!-- Définition d'un groupe d'attribut text --> <xsl:attribute-set name="text"> <xsl:attribute name="align">left</xsl:attribute> </xsl:attribute-set> <!-- Définition d'un groupe d'attribut para pour les paragraphes --> <xsl:attribute-set name="para" use-attribute-sets="text"/> <!-- Définition d'un groupe d'attribut title pour les titres --> <xsl:attribute-set name="title" use-attribute-sets="text"> <xsl:attribute name="id" select="generate-id()"/> </xsl:attribute-set> <xsl:template match="/"> <xsl:comment>Generated by dbk2html.xsl</xsl:comment> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title><xsl:value-of select="dbk:book/dbk:title"/></title></head> <body><xsl:apply-templates/></body> </html> </xsl:template> <!-- Éléments non traités --> <xsl:template match="dbk:title|dbk:subtitle"/> <!-- Livre --> <xsl:template match="dbk:book"> <xsl:apply-templates/> </xsl:template> <!-- Chapitres --> <xsl:template match="dbk:chapter"> <h1 xsl:use-attribute-sets="title"><xsl:value-of select="dbk:title"/></h1> <xsl:apply-templates/> </xsl:template> <!-- Sections de niveau 1 --> <xsl:template match="dbk:sect1"> <h2 xsl:use-attribute-sets="title"><xsl:value-of select="dbk:title"/></h2> <xsl:apply-templates/> </xsl:template> <!-- Paragraphes --> <xsl:template match="dbk:para"> <p xsl:use-attribute-sets="para"><xsl:apply-templates/></p> </xsl:template> </xsl:stylesheet>
Un document produit par cette feuille de style contient de multiples occurrences des mêmes attributs avec les mêmes valeurs. Il est préférable d'utiliser CSS qui permet de séparer le contenu de la présentation et de réduire, du même coup, la taille du fichier XHTML.
Une feuille de style peut importer la feuille de style précédente tout en redéfinissant certains groupes d'attributs pour en modifier le comportement.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- Import de la feuille de style précédente --> <xsl:import href="dbk2html.xsl"/> <!-- Modification du groupe d'attributs text --> <xsl:attribute-set name="title"> <!-- Nouvelle valeur de l'attribut align --> <xsl:attribute name="align">center</xsl:attribute> </xsl:attribute-set> <!-- Modification du groupe d'attributa text --> <xsl:attribute-set name="para"> <!-- Ajout d'un attribut style --> <xsl:attribute name="style">font-family: serif</xsl:attribute> </xsl:attribute-set> </xsl:stylesheet>
Les éléments xsl:comment
et
xsl:processing-instruction
construisent
respectivement un nœud pour un commentaire ou une instruction de traitement. Le nom de
l'instruction de traitement est déterminé dynamiquement par l'attribut
name
de
xsl:processing-instruction
. Cet attribut contient
une expression XPath en
attribut. Le texte du commentaire ou de l'instruction de
traitement est donné par le contenu des éléments
xsl:comment
et
xsl:processing-instruction
.
<!-- Ajout d'un commentaire dans le document résultat --> <xsl:comment>Un commentaire dans le document final</xsl:comment> <!-- Ajout d'une instruction de traitement dans le document résultat --> <xsl:processing-instruction name="dbhtml"> filename="index.html" </xsl:processing-instruction>
On obtient, dans le document résultat, le commentaire et l'instruction de traitement comme ci-dessous.
<!-- Un commentaire dans le document final --> <?dbhtml filename="index.html"?>
La présence de commentaires dans un document généré par une feuille de style est très souvent superflue. Elle peut cependant être nécessaire pour inclure des règles CSS dans l'entête d'un document XHTML comme dans l'exemple donné pour les paramètres globaux.
L'élément xsl:sequence
construit une liste de
valeurs déterminée par l'évaluation de l'expression XPath contenue dans
l'attribut select
ou par le contenu de
l'élément xsl:sequence
lui-même. Cet élément est
souvent utilisé dans la déclaration d'une variable ou dans la définition
d'une fonction d'extension.
L'élément xsl:sequence
permet à une règle de
retourner une liste de nœuds du document source. Dans la feuille de
style suivante, la règle s'appliquant à l'élément
list
retourne la liste de ses enfants
item
où apparaît la chaîne
'1'
.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<!-- Déclararion de variable var -->
<xsl:variable name="var" as="node()*">
<xsl:apply-templates select="list"/>
</xsl:variable>
<!-- Utilisation de la variable var -->
<texts>
<xsl:for-each select="$var">
<xsl:copy-of select="."/>
<xsl:copy-of select="following-sibling::*[1]"/>
</xsl:for-each>
</texts>
</xsl:template>
<xsl:template match="list">
<xsl:sequence select="item[contains(.,'1')]"/>
</xsl:template>
</xsl:stylesheet>
Si la feuille de style précédente est appliquée au document suivant, on obtient le document résultat donné ci-dessous.
<?xml version="1.0" encoding="utf-8"?> <!-- Document source --> <texts><item>12</item><item>23</item><item>34</item><item>41</item></texts>
Les deux éléments item
contenant
12
et 41
ont été copiés par le
premier xsl:copy-of
dont l'attribut
select
vaut '.'
. L'élément
item
contenant 23
a été copié par
le second xsl:copy-of
dont l'attribut
select
vaut
following-sibling::*[1]
car il est le premier frère
droit de celui contenant 12
. Le document résultat
ci-dessous n'aurait pas été le même si l'élément
xsl:sequence
avait été remplacé par un élément
xsl:copy-of
. L'axe
following-sibling
fonctionne car les nœuds
proviennent du document source et ne sont pas des copies.
<?xml version="1.0" encoding="utf-8"?> <!-- Document résultat --> <texts><item>12</item><item>23</item><item>41</item></texts>
Une liste créée par xsl:sequence
peut être
triée par l'élément xsl:perform-sort
.
L'élément xsl:copy
permet de copier le nœud
courant dans le document résultat. Cette copie est dite
superficielle car elle copie seulement le nœud
ainsi que les déclarations d'espaces de
noms nécessaires. L'élément xsl:copy
n'a pas
d'attribut select
car c'est toujours le nœud courant
qui est copié.
Les attributs, dans le cas où le nœud courant est un élément, et
le contenu ne sont pas copiés par xsl:copy
. Ils
doivent être ajoutés explicitement. Le contenu de l'élément
xsl:copy
définit le contenu de l'élément copié.
C'est ici qu'il faut, par exemple, mettre des éléments
xsl:copy-of
et/ou
xsl:apply-templates
pour ajouter des attributs et/ou
du contenu.
La feuille de style suivante transforme le document
bibliography.xml
en remplaçant chaque attribut
lang
d'un élément book
par un
élément lang
contenant la valeur de l'attribut
lang
. La feuille de style est constituée de deux
règles. Une première règle s'applique aux éléments
book
et une seconde à tous les autres éléments. Dans
les deux règles, la copie de l'élément est réalisée par un élément
xsl:copy
. La copie des attributs est réalisée par un
élément xsl:copy-of
qui sélectionne explicitement les
attributs. La copie des enfants est réalisée par un élément
xsl:apply-templates
pour un appel récursif des
règles.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <!-- Règle pour la racine et les éléments autres que book --> <xsl:template match="/|*"> <xsl:copy> <!-- Copie explicite des attributs --> <xsl:copy-of select="@*"/> <!-- Copie explicite des enfants --> <xsl:apply-templates/> </xsl:copy> </xsl:template> <!-- Règle pour les éléments book --> <xsl:template match="book"> <xsl:copy> <!-- Copie explicite des attributs autres que 'lang' --> <xsl:copy-of select="@*[name() != 'lang']"/> <!-- Ajout de l'élément lang --> <lang><xsl:value-of select="@lang"/></lang> <!-- Copie explicite des enfants --> <xsl:apply-templates/> </xsl:copy> </xsl:template> </xsl:stylesheet>
En appliquant la feuille de style précédente au document
bibliography.xml
, on obtient le document
suivant.
<?xml version="1.0" encoding="iso-8859-1"?> <bibliography> <book key="Michard01"> <lang>fr</lang> <title>XML langage et appplications</title> <author>Alain Michard</author> <year>2001</year> <publisher>Eyrolles</publisher> <isbn>2-212-09206-7</isbn> <url>http://www.editions-eyrolles/livres/michard/</url> </book> <book key="Zeldman03"> <lang>en</lang> <title>Designing with web standards</title> <author>Jeffrey Zeldman</author> <year>2003</year> <publisher>New Riders</publisher> <isbn>0-7357-1201-8</isbn> </book> <!-- Fichier tronqué --> ... </bibliography>
L'élément xsl:copy
peut avoir un attribut
use-attribute-set
pour ajouter les attributs déclarés
par des groupes
d'attributs.
L'élément xsl:copy-of
permet de copier des
nœuds sélectionnés ainsi que tous les descendants de ces nœuds dans le
document résultat. Cette copie est dite profonde
car tout le sous-arbre enraciné en un nœud sélectionné est copié.
L'expression XPath contenue dans l'attribut select
de xsl:copy-of
détermine les nœuds à copier.
La feuille de style suivante transforme le document
bibliography.xml
en répartissant la bibliographie
en deux parties dans des éléments bibliodiv
contenant
respectivement les livres en français et en anglais. L'unique règle de
la feuille de style crée le squelette avec les deux éléments
bibliodiv
et copie les livres dans ces deux éléments
grâce à deux éléments xsl:copy-of
.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <xsl:template match="/"> <bibliography> <bibliodiv> <!-- Copie des éléments book dont l'attribut lang vaut fr --> <xsl:copy-of select="bibliography/book[@lang='fr']"/> </bibliodiv> <bibliodiv> <!-- Copie des éléments book dont l'attribut lang vaut en --> <xsl:copy-of select="bibliography/book[@lang='en']"/> </bibliodiv> </bibliography> </xsl:template> </xsl:stylesheet>
Le rôle de l'élément xsl:number
est double. Sa
première fonction est de créer un entier ou une liste d'entiers pour
numéroter un élément. La seconde fonction est de formater cet entier ou
cette liste. La seconde fonction est plutôt adaptée à la numérotation.
Pour formater un nombre de façon précise, il est préférable d'utiliser
la fonction XPath format-number()
.
La fonction de formatage est relativement simple. L'attribut
format
de xsl:number
contient une
chaîne formée d'un préfixe, d'un indicateur de
format et d'un suffixe. Le préfixe et le suffixe
doivent être formés de caractères non alphanumériques. Ils sont
recopiés sans changement. L'indicateur de format est remplacé par
l'entier. Le tableau suivant récapitule les différents formats
possibles. Le format par défaut est 1
.
Format | Résultat |
---|---|
1 |
1, 2, 3, …, 9, 10, 11, …
|
01 |
01, 02, 03, …, 09, 10, 11, …
|
a
|
a, b, c, …, z, aa, ab, …
|
A |
A, B, C, …, Z, AA, AB, …
|
i |
i, ii, iii, iv, v, vi, vii, viii, ix, x, xi, …
|
I |
I, II, III, IV, V, VI, VII, VIII, IX, X, XI, …
|
Tableau 8.1. Formats de xsl:number
Il existe aussi des formats w
,
W
et Ww
permettant d'écrire les
nombres en toutes lettres en minuscule, en majuscule ou avec juste une
majuscule pour la première lettre. L'attribut lang
qui prend les mêmes valeurs que l'attribut xml:lang
spécifie la langue dans laquelle sont écrits les nombres. Cette
fonctionnalité n'est pas implémentée dans tous les processeurs XSLT et
l'attribut lang
n'est pas toujours pris en
compte.
Format | Résultat |
---|---|
w |
one, two, three, …, nine, ten, eleven, …
|
W |
ONE, TWO, THREE, …, NINE, TEN, ELEVEN, …
|
Ww |
One, Two, Three, …, Nine, Ten, Eleven, …
|
Tableau 8.2. Formats w
, W
et
Ww
de xsl:number
Le numéro calculé par xsl:number
peut être
donné de façon explicite par l'attribut value
qui
contient une expression XPath. L'évaluation de cette expression
fournit le nombre résultat. Cette méthode permet un contrôle total sur
le calcul du numéro.
<xsl:number value="1 + count(preceding::*[not(@hide)])" format="i"/>
Si l'attribut value
est absent, le numéro est
calculé grâce aux valeurs des attributs level
,
count
et from
. L'attribut
level
détermine le mode de calcul alors que les
attributs count
et from
déterminent les éléments pris en compte. Ces deux derniers attributs
contiennent un motif XPath
permettant de sélectionner des nœuds.
L'attribut level
peut prendre les valeurs
single
, multiple
et
any
. Les modes de calcul single
et any
fournissent un seul entier alors que le mode
multiple
fournit une liste d'entiers. Dans ce
dernier cas, le format peut contenir plusieurs indicateurs de formats
séparés par des caractères non alphanumériques comme
1.1.1
ou [A-1-i]
.
Dans le mode single
, le numéro est égal au
nombre (augmenté d'une unité pour commencer avec 1) de frères gauches
du nœud courant qui satisfont le motif donné par
count
. L'attribut from
n'est pas
pris en compte dans ce mode. Rappelons qu'un
frère est un enfant du même nœud père et qu'il est
dit gauche s'il précède le nœud courant dans le
document.
La feuille de style suivante ajoute un numéro aux éléments
section
. Ce numéro est calculé dans le mode
single
.
<?xml version="1.0" encoding="us-ascii"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="*">
<xsl:copy>
<xsl:if test="name()='section'">
<!-- format="1" est la valeur par défaut -->
<xsl:number level="single" count="section" format="1"/>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
On considère le document XML suivant qui représente le squelette
d'un livre avec deux chapitres, deux sections dans chaque chapitre et
deux sous-sections dans chaque section. Les sections ainsi que les
sous-sections sont contenues dans des éléments
section
.
<?xml version="1.0" encoding="iso-8859-1"?> <book> <chapter> <section><section></section><section></section></section> <section><section></section><section></section></section> </chapter> <chapter> <section><section></section><section></section></section> <section><section></section><section></section></section> </chapter> </book>
En appliquant la feuille de style au document précédent, on
obtient le document XML suivant. Chaque élément
section
contient en plus un numéro calculé par
xsl:number
en mode single
. Les
sections sont numérotées à partir de 1 dans chaque chapitre et les
sous-sections sont aussi numérotées à partir de 1 dans chaque
section.
<?xml version="1.0" encoding="iso-8859-1"?> <book> <chapter> <section>1<section>1</section><section>2</section></section> <section>2<section>1</section><section>2</section></section> </chapter> <chapter> <section>1<section>1</section><section>2</section></section> <section>2<section>1</section><section>2</section></section> </chapter> </book>
Dans le mode multiple
, l'élément
xsl:number
fournit une liste d'entiers qui est
calculée de la façon suivante. Le nœud de départ
est déterminé par l'attribut from
qui contient un
motif XPath. C'est l'ancêtre le plus proche du nœud courant qui
satisfait le motif de l'attribut from
. Par défaut,
le nœud de départ est la racine du document. Ensuite, on considère
chacun des ancêtres entre le nœud de départ et le nœud courant qui
satisfait l'attribut count
, avec le nœud de départ
exclus et le nœud courant inclus. Pour chacun de ces ancêtres, le
nombre (plus une unité) de frères gauches qui satisfont le motif de
count
fournit un des entiers de la suite.
Si l'élément xsl:number
de la feuille de style
précédente est remplacé par l'élément suivant, on obtient le document
ci-dessous. Comme le format est A.1.i
, chaque
section contient un numéro global formé d'un numéro de chapitre
(A
, B
, …), d'un numéro de
section (1
, 2
, …) et d'un
numéro de sous-section (i
, ii
,
…). Ces différents numéros sont séparés par les points
'.'
qui sont repris du format.
<xsl:number level="multiple" count="chapter|section" format="A.1.i"/>
<?xml version="1.0" encoding="iso-8859-1"?> <book> <chapter> <section>A.1<section>A.1.i</section><section>A.1.ii</section></section> <section>A.2<section>A.2.i</section><section>A.2.ii</section></section> </chapter> <chapter> <section>B.1<section>B.1.i</section><section>B.1.ii</section></section> <section>B.2<section>B.2.i</section><section>B.2.ii</section></section> </chapter> </book>
Si un attribut from
avec la valeur
chapter
est ajouté à l'élément
xsl:number
de la feuille de style précédente, on
obtient le document ci-dessous. Les chapitres ne sont plus pris en
compte dans la numérotation des sections et des sous-sections.
<xsl:number level="multiple" count="chapter|section"
from="chapter" format="A.1"/>
<book> <chapter> <section>A<section>A.1</section><section>A.2</section></section> <section>B<section>B.1</section><section>B.2</section></section> </chapter> <chapter> <section>A<section>A.1</section><section>A.2</section></section> <section>B<section>B.1</section><section>B.2</section></section> </chapter> </book>
Dans le mode any
, le nœud de
départ est égal au dernier nœud avant le nœud courant qui
vérifie le motif donné par l'attribut from
. Par
défaut, le nœud de départ est la racine du document. Le numéro est égal
au nombre (augmenté d'une unité pour commencer avec 1) de nœuds entre
le nœud de départ et le nœud courant qui satisfont le motif donné par
l'attribut count
.
Si l'élément xsl:number
de la feuille de style
précédente est remplacé par l'élément suivant, on obtient le document
ci-dessous. Chaque section contient son numéro d'ordre dans tout le
document car la valeur par défaut de from
est la
racine du document.
<xsl:number level="any" count="section" format="1"/>
<?xml version="1.0" encoding="iso-8859-1"?> <book> <chapter> <section>1<section>2</section><section>3</section></section> <section>4<section>5</section><section>6</section></section> </chapter> <chapter> <section>7<section>8</section><section>9</section></section> <section>10<section>11</section><section>12</section></section> </chapter> </book>
L'élément xsl:number
suivant utilise
l'attribut from
pour limiter la numérotation des
éléments section
aux contenus des éléments
chapter
. En appliquant la feuille de style
précédente avec cet élément xsl:number
, on obtient
le document ci-dessous. Chaque section contient son numéro d'ordre
dans le chapitre.
<xsl:number level="any" count="section" from="chapter" format="1"/>
<?xml version="1.0" encoding="iso-8859-1"?> <book> <chapter> <section>1<section>2</section><section>3</section></section> <section>4<section>5</section><section>6</section></section> </chapter> <chapter> <section>1<section>2</section><section>3</section></section> <section>4<section>5</section><section>6</section></section> </chapter> </book>
L'élément xsl:number
a des possibilités
limitées pour formater un nombre de manière générale. Ils possède deux
attributs grouping-separator
et
grouping-size
dont les valeurs par défaut sont
respectivement ','
et
3
. Dans l'exemple suivant, l'entier 1234567 est
formaté en 1.234.567
.
<xsl:number grouping-separator="." value="12345678"/>
La fonction XPath format-number()
permet de formater un nombre entier ou décimal de façon plus précise.
Le premier paramètre est le nombre à formater et le second est une
chaîne de caractères qui décrit le formatage à effectuer. Cette
fonction est inspirée de la classe DecimalFormat
de
Java et elle s'apparente, par les formats utilisés, à la fonction
printf
du langage C. Un troisième paramètre
optionnel référence éventuellement un élément
xsl:decimal-format
pour changer la signification de
certains caractères dans le format.
Lorsqu'aucun élément xsl:decimal-format
n'est
référencé, le format est une chaîne formée de caractères
'#'
, '0'
, '.'
,
','
, '%'
U+25
, '‰'
U+2030
et ';'
. La
signification de ces différents caractères est donnée ci-dessous. La
chaîne passée en second paramètre peut aussi contenir d'autres
caractères qui sont recopiés inchangés dans le résultat.
'#'
position pour un chiffre
'0'
position pour un chiffre remplacé éventuellement par
0
'.'
position du point décimal
','
position du séparateur de groupe (milliers, millions, …)
';'
séparateur entre un format pour les nombres positifs et un format pour les nombres négatifs.
La chaîne de caractères passée en second paramètre à
format-number()
peut contenir deux formats séparés
par un caractère ';'
comme
#000;-#00
par exemple. Le premier format
#000
est alors utilisé pour les nombres positifs et
le second format -#00
pour les nombres négatifs.
Dans ce cas, le second format doit explicitement insérer le caractère
'-'
car c'est la valeur absolue du
nombre qui est formatée. En formatant les nombres 12 et -12 avec ce
format #000;-#00
, on obtient respectivement
012
et -12
. La table ci-dessous
donne quelques exemples de résultats de la fonction
format-number()
avec des formats différents.
Nombre/Format | |##| | |####| | |#,#00.##| | |####.00| | |0000.00| |
---|---|---|---|---|---|
1 | |1| | |1| | |01| | |1.00| | |0001.00| |
123 | |123| | |123| | |123| | |123.00| | |0123.00| |
1234 | |1234| | |1234| | |1,234| | |1234.00| | |1234.00| |
12.34 | |12| | |12| | |12.34| | |12.34| | |0012.34| |
1.234 | |1| | |1| | |01.23| | |1.23| | |0001.23| |
Tableau 8.3. Résultats de format-number()
Les caractères '%'
et
'‰'
permettent de formater une fraction entre
0 et 1 comme un pourcentage ou un millième. Le formatage du nombre
0.1234 avec les formats #%
, #.##%
et #‰
donne respectivement
12%
, 12.34%
et
123‰
.
L'élément xsl:decimal-format
permet de changer
les caractères utilisés dans le format. Cet élément déclare un objet
qui définit l'interprétation des caractères dans le format utilisé par
format-number()
. L'attribut name
donne le nom de l'objet qui est utilisé comme troisième paramètre de la
fonction format-number()
. Outre cet attribut,
l'élément xsl:decimal-format
possède plusieurs
attributs permettant de spécifier les caractères utilisés pour marquer
les différentes parties du nombre (point décimal, séparateur de groupe,
etc …).
decimal-separator
caractère pour marquer le point décimal ('.'
par défaut)
grouping-separator
caractère pour séparer les groupes (','
par
défaut)
digit
caractère pour la position d'un chiffre ('#'
par défaut)
zero-digit
caractère pour la position d'un chiffre remplacé par
'0'
('0'
par défaut)
pattern-separator
caractère pour séparer deux formats pour les nombres positifs et les
nombres négatifs (';'
par défaut)
percent
caractère pour formater les pourcentages
('%'
par défaut)
per-mille
caractère pour formater les millièmes
('‰'
par défaut)
Les éléments xsl:decimal-format
doivent être
enfants de l'élément racine xsl:stylesheet
de la
feuille de style et leur portée est globale. Ils peuvent être
référencés par la fonction format-number()
dans
n'importe quelle expression XPath de la feuille de style.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- Format des nombres en englais --> <xsl:decimal-format name="en" decimal-separator="." grouping-separator=","/> <!-- Format des nombres en français --> <xsl:decimal-format name="fr" decimal-separator="," grouping-separator="."/> ... <price xml:lang="en-GB" currency="pound"> <xsl:value-of select="format-number($price, '###,###,###.##', 'en')"/> </price> ... <price xml:lang="fr" currency="euro"> <xsl:value-of select="format-number($price, '###.###.###,##', 'fr')"/> </price> ...
Le langage XSLT possède, comme tout langage de programmation, des
structures de contrôle permettant d'effectuer des tests et des boucles.
Il contient les deux éléments xsl:if
et
xsl:choose
pour les tests et les deux
éléments xsl:for-each
et xsl:for-each-group
pour les boucles. L'élément
xsl:if
autorise un test sans alternative (pas
d'élément xsl:else
) alors que l'élément
xsl:choice
permet, au contraire un choix entre
plusieurs alternatives. L'élément xsl:for-each
permet
des boucles simples pour parcourir une liste de valeurs comme,
par exemple, une liste de nœuds sélectionnés. L'élément
xsl:for-each-group
permet de former des groupes à
partir de nœuds sélectionnés puis de traiter successivement les
différents groupes. Certaines constructions XSLT sont parfois réalisées
de manière plus concise par des structures de contrôle XPath placées
dans des attributs.
L'élément xsl:if
permet de réaliser un test.
De façon surprenante, cet élément ne propose pas d'alternative car il
n'existe pas d'élément xsl:else
. Lorsqu'une
alternative est nécessaire, il faut utiliser l'élément
xsl:choose
.
La condition du test est une expression XPath contenue dans
l'attribut test
de xsl:if
. Cette
expression est évaluée puis convertie en valeur booléenne. Si le résultat
est true
, le contenu de l'élément
xsl:if
est pris en compte et les nœuds créés sont
insérés dans le résultat de la règle. Sinon, le contenu de l'élément
xsl:if
est ignoré.
<xsl:if test="not(position()=last())"> <xsl:text>, </xsl:text> </xsl:if>
L'élément xsl:choose
permet de réaliser
plusieurs tests consécutivement. Il contient des éléments
xsl:when
et éventuellement un élément
xsl:otherwise
. Chacun des éléments
xsl:when
possède un attribut test
contenant une expression XPath servant de condition.
Les conditions contenues dans les attributs
test
des éléments xsl:when
sont
évaluées puis converties en valeur
booléenne. Le contenu du premier élément
xsl:when
dont la condition donne la valeur
true
est pris en compte et les nœuds créés sont
insérés dans le résultat de la règle. Les autres éléments
xsl:when
et un éventuel élément
xsl:otherwise
sont ignorés. Si aucune condition ne
donne la valeur true
, le contenu de l'élément
xsl:otherwise
, s'il existe, est pris en compte. Tous
les éléments xsl:when
sont ignorés. Si aucune
condition ne donne la valeur true
et s'il n'y a pas
d'élément xsl:otherwise
, l'élément
xsl:choose
tout entier est ignoré.
Le fragment de feuille de style retourne le contenu de l'enfant
title
de nœud courant si cet enfant existe ou
construit un titre avec un numéro avec xsl:number
sinon.
<xsl:choose> <xsl:when test="title"> <xsl:value-of select="title"/> </xsl:when> <xsl:otherwise> <xsl:text>Section </xsl:text> <xsl:number level="single" count="section"/> </xsl:otherwise> </xsl:choose>
L'élément xsl:for-each
permet de réaliser des
boucles en XSLT. L'itération est déjà présente implicitement avec
l'élément xsl:apply-templates
puisqu'une règle est
successivement appliquée à chacun des nœuds sélectionnés. L'élément
xsl:for-each
réalise une boucle de manière
explicite.
L'attribut select
détermine les objets traités.
L'expression XPath qu'il contient est évaluée pour donner une liste
l
d'objets qui est, ensuite, parcourue. Le contenu
de l'élément xsl:for-each
est exécuté pour chacun des
objets de la liste l
. Le focus est modifié pendant
l'exécution de xsl:for-each
. À chaque itération,
l'objet courant est fixé à un des objets de la liste
l
. La taille du contexte est également fixée à la
longueur de l
et la position dans le contexte est
finalement fixée à la position de l'objet courant dans la liste
l
.
La feuille de style suivante présente la bibliographie
bibliography.xml
sous forme d'un tableau XHTML. Le
tableau est construit par l'unique règle de la feuille de style. La
première ligne du tableau avec des éléments th
est
ajoutée explicitement et les autres lignes avec des éléments
td
sont ajoutées par un élément
xsl:for-each
qui parcourt tous les éléments
book
de bibliography.xml
. Le
résultat de la fonction position()
est formatée par
l'élément xsl:number
pour
numéroter les lignes du tableau.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <head> <title>Bibliographie en tableau</title> </head> <body> <h1>Bibliographie en tableau</h1> <table align="center" border="1" cellpadding="2" cellspacing="0"> <tr> <th>Numéro</th> <th>Titre</th> <th>Auteur</th> <th>Éditeur</th> <th>Année</th> </tr> <xsl:for-each select="bibliography/book"> <xsl:sort select="author" order="ascending"/> <tr> <td><xsl:number value="position()" format="1"/></td> <td><xsl:value-of select="title"/></td> <td><xsl:value-of select="author"/></td> <td><xsl:value-of select="publisher"/></td> <td><xsl:value-of select="year"/></td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet>
En appliquant la feuille de style précédente au document
bibliography.xml
, on obtient le document
suivant.
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Bibliographie en tableau</title> </head> <body> <h1>Bibliographie en tableau</h1> <table align="center" border="1" cellpadding="2" cellspacing="0"> <tr> <th>Numéro</th> <th>Titre</th> <th>Auteur</th> <th>Éditeur</th> <th>Année</th> </tr> <tr> <td>1</td> <td>XML langage et appplications</td> <td>Alain Michard</td> <td>Eyrolles</td> <td>2001</td> </tr> <!-- Fichier tronqué --> ... </table> </body> </html>
La feuille de style précédente utilise l'élément
xsl:for-each
pour itérer sur les différents livres
contenus dans le fichier bibliography.xml
. Le même
résultat peut être obtenu sans utiliser xsl:for-each
comme dans la feuille de style ci-dessous. L'idée est d'employer
judicieusement l'élément xsl:apply-templates
qui
itère implicitement sur les nœuds sélectionnés. La plupart des
utilisations de xsl:for-each
peuvent être évitées.
Elles sont souvent le signe d'une mauvaise programmation XSLT.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <head><title>Bibliographie en tableau</title></head> <body> <h1>Bibliographie en tableau sans <tt>xsl:for-each</tt></h1> <table align="center" border="1" cellpadding="2" cellspacing="0"> <tr> <th>Numéro</th> <th>Titre</th> <th>Auteur</th> <th>Éditeur</th> <th>Année</th> </tr> <xsl:apply-templates select="bibliography/book"> <xsl:sort select="author" order="descending"/> </xsl:apply-templates> </table> </body> </html> </xsl:template> <xsl:template match="book"> <tr> <xsl:if test="position() mod 2 = 0"> <xsl:attribute name="bgcolor">#ffffcc</xsl:attribute> </xsl:if> <td><xsl:number value="position()" format="1"/></td> <xsl:apply-templates select="title, author, publisher, year"/> </tr> </xsl:template> <xsl:template match="title | author | publisher | year"> <td><xsl:value-of select="."/></td> </xsl:template> </xsl:stylesheet>
L'élément xsl:for-each-group
est un ajout de
XSLT 2.0. Il permet de grouper des nœuds du document source suivant
différents critères puis de parcourir les groupes ainsi formés.
La feuille de style suivante donne un premier exemple simple
d'utilisation de l'élément xsl:for-each-group
. Elle
présente la bibliographie en XHTML en regroupant les livres par années.
Le regroupement est réalisé par l'élément
xsl:for-each-group
avec l'attribut
group-by
égal à l'expression XPath
year
. L'attribut select
détermine
que les éléments à regrouper sont les élément book
.
Le traitement de chacun des groupes est réalisé par le contenu de
l'élément xsl:for-each-group
. La clé du groupe est
d'abord récupérée par la fonction XPath current-grouping-key()
pour construire le titre contenu dans l'élément XHTML
h2
. Les éléments book
de chaque
groupe sont ensuite traités, l'un après l'autre, grâce à un élément
xsl:apply-templates
. L'attribut
select
de cet élément utilise la fonction current-group()
qui retourne la liste des objets du groupe.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> <xsl:output method="xhtml" encoding="iso-8859-1" indent="yes"/> <xsl:template match="/"> <html> <head> <title>Bibliographie par année</title> </head> <body> <h1>Bibliographie par année</h1> <!-- Regroupement des livres par années --> <xsl:for-each-group select="bibliography/book" group-by="year"> <!-- Tri des groupes par années --> <xsl:sort select="current-grouping-key()"/> <!-- Titre avec l'année --> <h2> <xsl:text>Année </xsl:text> <xsl:value-of select="current-grouping-key()"/> </h2> <!-- Liste des livres de l'année --> <ul> <!-- Traitement de chacun des éléments du groupe --> <xsl:apply-templates select="current-group()"> <!-- Tri des éléments du groupe par auteur puis publisher --> <xsl:sort select="author"/> <xsl:sort select="publisher"/> </xsl:apply-templates> </ul> </xsl:for-each-group> </body> </html> </xsl:template> <!-- Règle pour les éléments book --> <xsl:template match="book"> <li><xsl:apply-templates select="*"/></li> </xsl:template> <!-- Règle pour les éléments title --> <xsl:template match="title"> <i><xsl:apply-templates/></i> <xsl:call-template name="separator"/> </xsl:template> <!-- Règle pour les autres éléments --> <xsl:template match="*"> <xsl:apply-templates/> <xsl:call-template name="separator"/> </xsl:template> <!-- Virgule après les éléments --> <xsl:template name="separator"> <xsl:if test="position() != last()"> <xsl:text>, </xsl:text> </xsl:if> </xsl:template> </xsl:stylesheet>
En appliquant la feuille de style précédente au document
bibliography.xml
, on obtient le document
suivant.
<?xml version="1.0" encoding="iso-8859-1"?> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>Bibliographie par année</title> </head> <body> <h1>Bibliographie par année</h1> <h2>Année 2000</h2> <ul> <li><i>XML by Example</i>, Benoît Marchal, 2000, Macmillan Computer Publishing, 0-7897-2242-9</li> ... </ul> <h2>Année 2001</h2> ... </body> </html>
La feuille de style suivante donne un exemple plus avancé
d'utilisation de l'élément xsl:for-each-group
.
Celle-ci effectue une transformation d'un document XHTML en un document
DocBook. Pour simplifier, on se contente de sous-ensembles très
restreints de ces deux dialectes XML. On considère uniquement les
éléments html
, body
,
h1
, h2
et p
de
XHTML et des éléments book
,
chapter
, sect1
,
title
et para
de DocBook. Ces
deux dialectes organisent un document de manières différentes. Dans un
document XHTML, les chapitres et les sections sont uniquement délimités
par les titres h1
et h2
. Au
contraire, dans un document DocBook, les éléments
chapter
et sect1
encapsulent les
chapitres et les sections. Ces différences rendent plus difficile la
transformation de XHTML vers DocBook. Pour trouver le contenu d'un
chapitre, il est nécessaire de regrouper tous les éléments placés entre
deux éléments h1
consécutifs. L'élément
xsl:for-each-group
permet justement de réaliser
facilement cette opération.
L'espace de noms par défaut de la feuille de style est celui
de DocBook. Les noms des éléments XHTML doivent ainsi être qualifiés
par le préfixe html
associé à l'espace de noms
de XHTML.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://docbook.org/ns/docbook" exclude-result-prefixes="xsl html"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <xsl:template match="/"> <book> <xsl:apply-templates select="html:html/html:body"/> </book> </xsl:template> <xsl:template match="html:body"> <!-- Regroupement des éléments avec l'élément h1 qui précède --> <xsl:for-each-group select="*" group-starting-with="html:h1"> <chapter> <!-- Titre avec le contenu de l'élément h1 --> <title><xsl:value-of select="current-group()[1]"/></title> <!-- Traitement du groupe --> <!-- Regroupement des éléments avec l'élément h2 qui précède --> <xsl:for-each-group select="current-group()" group-starting-with="html:h2"> <xsl:choose> <xsl:when test="local-name(current-group()[1]) = 'h2'"> <sect1> <!-- Titre avec le contenu de l'élément h2 --> <title><xsl:value-of select="current-group()[1]"/></title> <xsl:apply-templates select="current-group()"/> </sect1> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="current-group()"/> </xsl:otherwise> </xsl:choose> </xsl:for-each-group> </chapter> </xsl:for-each-group> </xsl:template> <!-- Supression des éléments h1 et h2 --> <xsl:template match="html:h1|html:h2"/> <!-- Transformation des éléments p en éléments para --> <xsl:template match="html:p"> <para><xsl:apply-templates/></para> </xsl:template> </xsl:stylesheet>
Le document suivant est le document XHTML sur lequel est appliqué la tranformation. Celui-ci représente le squelette typique d'un document XHTML avec des titres de niveaux 1 et 2 et des paragraphes.
<?xml version="1.0" encoding="iso-8859-1" ?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr"> <head> <title>Fichier HTML exemple</title> </head> <body> <h1>Titre I</h1> <p>Paragraphe I.0.1</p> <h2>Titre I.1</h2> <p>Paragraphe I.1.1</p> <p>Paragraphe I.1.2</p> <h2>Titre I.2</h2> <p>Paragraphe I.2.1</p> <p>Paragraphe I.2.2</p> <h1>titre II</h1> <p>Paragraphe II.0.1</p> <h2>Titre II.1</h2> <p>Paragraphe II.1.1</p> <p>Paragraphe II.1.2</p> <h2>Titre II.2</h2> <p>Paragraphe II.2.1</p> <p>Paragraphe II.2.2</p> </body> </html>
Le document suivant est le document DocBook obtenu par
transformation par la feuille de style prédédente du document XHTML
précédent. Les deux éléments h1
du document XHTML
donnent deux éléments chapter
dans le document
DocBook.
<?xml version="1.0" encoding="iso-8859-1"?> <book xmlns="http://docbook.org/ns/docbook"> <chapter> <title>Titre I</title> <para>Para I.0.1</para> <sect1> <title>Titre I.1</title> <para>Para I.1.1</para> <para>Para I.1.2</para> </sect1> <sect1> <title>Titre I.2</title> <para>Para I.2.1</para> <para>Para I.2.2</para> </sect1> </chapter> <chapter> <title>titre II</title> <para>Para II.0.1</para> <sect1> <title>Titre II.1</title> <para>Para II.1.1</para> <para>Para II.1.2</para> </sect1> <sect1> <title>Titre II.2</title> <para>Para II.2.1</para> <para>Para II.2.2</para> </sect1> </chapter> </book>
L'élément xsl:sort
permet de trier des nœuds
avant de les traiter. L'élément xsl:sort
doit être le
premier enfant des éléments xsl:apply-templates
, xsl:for-each
ou xsl:for-each-group
. Le tri s'applique à tous les
nœuds sélectionnés par l'attribut select
de ces différents éléments. Le tri est réalisé juste après la sélection
des nœuds et avant leur traitement qui s'effectue dans l'ordre obtenu. Le
fragment de feuille de style suivant permet, par exemple, de trier les
éléments book
par ordre lexicographique croissant
d'auteur.
<xsl:apply-templates select="bibliography/book"> <xsl:sort select="author" order="ascending"/> </xsl:apply-templates>
L'attribut select
de xsl:sort
détermine la clé du tri. L'attribut data-type
qui
peut prendre les valeurs number
ou
text
spécifie comment les clés doivent être
interprétées. Il est possible d'avoir plusieurs clés de tri en mettant
plusieurs éléments xsl:sort
comme dans l'exemple
suivant. Les éléments book
sont d'abord triés par
auteur puis par année.
<xsl:apply-templates select="bibliography/book"> <xsl:sort select="author" order="ascending"/> <xsl:sort select="year" order="descending"/> </xsl:apply-templates>
Le tri réalisé par xsl:sort
est basé sur les
valeurs retournées par l'expression XPath contenue dans l'attribut
select
. Cette expression est souvent le nom d'un
enfant ou d'un attribut mais elle peut aussi être plus complexe. La
feuille de style suivante réordonne les enfants des éléments
book
. L'expression contenue dans l'attribut
select
de xsl:sort
retourne un
numéro d'ordre en fonction du nom de l'élément. Ce numéro est calculé
avec la fonction index-of()
et une
liste de noms dans l'ordre souhaité. Cette solution donne une expression
concise. Elle a aussi l'avantage que l'ordre est donné par une liste qui
peut être fixe ou calculée. Cette liste peut, par exemple, être la liste
des noms des enfants du premier élément book
.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <!-- Liste des noms des enfants du premier élément book --> <xsl:variable name="orderlist" as="xsd:string*" select="/bibliography/book[1]/*/name()"/> ... <xsl:template match="book"> <xsl:copy> <!-- Copie des attributs --> <xsl:copy-of select="@*"/> <xsl:apply-templates> <!-- Tri des enfants dans l'ordre donné par la liste fixe --> <!-- Les noms absents de la liste sont placés à la fin --> <xsl:sort select="(index-of(('title', 'author', 'publisher', 'year', 'isbn'), name()),10)[1]"/> <!-- Tri dans l'ordre des enfants du premier élément book --> <!-- <xsl:sort select="index-of($orderlist, name())"/> --> </xsl:apply-templates> </xsl:copy> </xsl:template> </xsl:stylesheet>
L'élément xsl:perform-sort
permet d'appliquer
un tri à une liste quelconque d'objets, en particulier avant de
l'affecter à une variable.
Ses enfants doivent être un ou des éléments xsl:sort
puis des éléments qui construisent la liste comme xsl:sequence
dans
l'exemple suivant.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsl:output method="text" encoding="iso-8859-1"/> <xsl:template match="/"> <xsl:variable name="list" as="xsd:integer*"> <xsl:perform-sort> <xsl:sort data-type="number" order="ascending"/> <xsl:sequence select="(3, 1, 5, 0)"/> </xsl:perform-sort> </xsl:variable> <!-- Produit 0,1,3,5 --> <xsl:value-of select="$list" separator=","/> </xsl:template> </xsl:stylesheet>
Le langage XSLT permet l'utilisation de variables pouvant contenir des valeurs. Les valeurs possibles comprennent les valeurs atomiques, les nœuds ou les listes de ces valeurs, c'est-à-dire toutes les valeurs des expressions XPath. Les variables peuvent être utilisées dans les expressions XPath.
Le langage XSLT distingue les variables des paramètres. Les
variables servent à mémoriser des valeurs intermédiaires alors que les
paramètres servent à transmettre des valeurs aux règles. Les variables
sont introduites par l'élément xsl:variable
et les
paramètres par l'élément xsl:param
. L'élément
xsl:with-param
permet de donner une valeur à un
paramètre lors de l'appel à une règle.
La valeur affectée à la variable est fixée au moment de sa
déclaration par l'élément xsl:variable
et elle ne
peut plus changer ensuite. Les variables ne sont donc pas vraiment
variables. Il s'agit d'objets non
mutables dans la terminologie des langages de programmation.
La portée de la variable est l'élément XSLT qui la contient. Les
variables dont la déclaration est enfant de l'élément xsl:stylesheet
sont donc globales.
L'attribut name
détermine le nom de la
variable. La valeur est donnée soit par une expression XPath dans
l'attribut select
soit directement dans le contenu de
l'élément xsl:variable
. Un attribut optionnel
as
peut spécifier le type de la variable. Les types
possibles sont les types
XPath. Dans l'exemple suivant, les deux variables
squares
et cubes
sont déclarées de
type xsd:integer*
. Chacune d'elles contient donc une
liste éventuellement vide d'entiers. La valeur de la variable
square
est donnée par l'élément xsl:sequence
contenu dans l'élément xsl:variable
. La valeur de la
variable cubes
est donnée par l'expression XPath de
l'attribut select
.
<xsl:variable name="squares" as="xsd:integer*"> <xsl:for-each select="1 to 5"> <xsl:sequence select=". * ."/> </xsl:for-each> </xsl:variable> <xsl:variable name="cubes" as="xsd:integer*" select="for $i in 1 to 5 return $i * $i * $i"/>
Une variable déclarée peut apparaître dans une expression XPath en
étant précédée du caractère '$'
comme dans l'exemple
suivant.
<xsl:value-of select="$squares"/>
Une variable permet aussi de mémoriser un ou plusieurs nœuds. Il
est parfois nécessaire de mémoriser le nœud courant dans une variable
afin de pouvoir y accéder dans une expression XPath qui modifie le contexte dynamique. Le
fragment de feuille de style suivant mémorise le nœud courant dans la
variable current
. Il l'utilise ensuite pour
sélectionner les éléments publisher
dont l'attribut
id
est égal à l'attribut by
du
nœud courant.
<xsl:variable name="current" select="." as="node()"/> <xsl:xsl:copy-of select="//publisher[@id = $current/@by]"/>
XSLT 2.0 a introduit une fonction XPath current()
qui retourne le nœud en cours de traitement par la règle. Il n'est plus
nécessaire de le mémoriser dans une variable. L'exemple précédent
pourrait être réécrit de la façon suivante.
<xsl:xsl:copy-of select="//publisher[@id = current()/@by]"/>
L'expression XPath //publisher[@id =
$current/@by]
n'est pas très efficace car elle nécessite un
parcours complet du document pour retrouver le bon élément
publisher
. Elle peut avantageusement être remplacée
par un appel à la fonction key()
. Il faut au
préalable créer avec xsl:key
un index des
éléments publisher
basé sur la valeur de leur
attribut id
. Cette approche est illustrée dans
l'exemple suivant.
Une variable peut aussi être utilisée, dans un souci d'efficacité,
pour mémoriser un résultat intermédiaire. Dans l'exemple suivant, le
nœud retourné par la fonction key()
est mémorisé dans
la variable result
puis utilisé à plusieurs
reprises.
<!-- Indexation des éléments publisher par leur attribut id --> <xsl:key name="idpublisher" match="publisher" use="@id"/> ... <!-- Sauvegarde du noeud recherché --> <xsl:variable name="result" select="key('idpublisher', @by)" as="node()"/> <publisher> <!-- Utilisation multiple du noeud --> <xsl:copy-of select="$result/@*[name() != 'id']"/> <xsl:copy-of select="$result/* | $result/text()"/> </publisher>
L'élément xsl:variable
permet également de
déclarer des variables locales lors de la définition de fonctions d'extension XPath.
Il existe des paramètres XSLT qui s'apparentent aux paramètres des
fonctions des langages classiques comme C ou Java. Ils servent à
transmettent des valeurs à la feuille de style et aux règles. Les
paramètres sont déclarés par l'élément xsl:param
qui
permet également de donner une valeur par défaut comme en C++. Cet
élément peut être enfant de l'élément racine
xsl:stylesheet
ou des éléments
xsl:template
. Dans le premier cas, le paramètre est
global et dans le second cas, il est
local à la règle déclarée par
xsl:template
.
Comme avec l'élément xsl:variable
, l'attribut
name
de l'élément xsl:param
détermine le nom du paramètre. La valeur par défaut est optionnelle.
Elle est donnée soit par une expression XPath dans l'attribut
select
soit directement dans le contenu de l'élément
xsl:param
. Un attribut optionnel
as
peut spécifier le type du paramètre. Le fragment
suivant déclare un paramètre bg-color
avec une valeur
par défaut égale à la chaîne de caractères
white
.
<xsl:param name="bg-color" select="'white'"/>
Les apostrophes '''
sont
nécessaires autour de la chaîne white
car la valeur
de l'attribut select
est une expression XPath. La
même déclaration peut également prendre la forme suivante sans les
apostrophes.
<xsl:param name="bg-color">white<xsl:param/>
Les paramètres globaux sont déclarés par un élément
xsl:param
enfant de l'élément xsl:stylesheet
. Leur valeur est fixée au moment
de l'appel au processeur XSLT. Leur valeur reste constante pendant
toute la durée du traitemet et ils peuvent être utilisés dans toute la
feuille de style. La syntaxe pour donner une valeur à un paramètre
global dépend du processeur XSLT. Les processeurs qui peuvent être
utilisés en ligne de commande ont généralement une option pour donner
une valeur à un paramètre. Le processeur xstlproc
a, par exemple, des options --param
et
--stringparam
dont les valeurs sont des expressions
XPath. La seconde option ajoute implicitement les apostrophes '''
nécessaires autour des chaînes de
caractères.
La feuille de style suivante utilise un paramètre global
bg-color
pour la couleur de fond du document XHTML
résultat. Sa valeur est utilisée pour inclure une régle CSS dans un commentaire dans l'entête du
document.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dbk="http://docbook.org/ns/docbook" xmlns="http://www.w3.org/1999/xhtml"> <xsl:output ... /> <!-- Paramètre global pour la couleur du fond --> <xsl:param name="bg-color" select="'white'"/> <xsl:template match="/"> <xsl:comment>Generated by dbk2html.xsl</xsl:comment> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title><xsl:value-of select="dbk:book/dbk:title"/></title> <style> <xsl:comment> body { background-color: <xsl:value-of select="$bg-color"/>; } </xsl:comment> </style> </head> <body><xsl:apply-templates/></body> </html> </xsl:template> ... </xsl:stylesheet>
Pour changer la couleur de fond du document résultat, il faut
donner une autre valeur au paramètre bg-color
comme
dans l'exemple suivant.
xsltproc --stringparam bg-color blue dbk2html.xsl document.xml
La déclaration d'un paramètre d'une règle est réalisée par un
élément xsl:param
enfant de
xsl:template
. Ces déclarations de paramètres
doivent être les premiers enfants de l'élément
xsl:template
. Le passage d'une valeur en paramètre
est réalisé par un élément xsl:with-param
enfant de
xsl:apply-templates
ou de xsl:call-template
. L'attribut
name
des éléments xsl:param
et
xsl:with-param
détermine le nom de la variable. La
valeur est donnée soit par une expression XPath dans l'attribut
select
soit directement dans le contenu des éléments
xsl:param
et xsl:with-param
. Un
attribut optionnel as
peut spécifier le type de la valeur. La valeur
est optionnelle pour xsl:param
et c'est la valeur
par défaut du paramètre. La valeur est bien sûr obligatoire pour
xsl:with-param
.
Dans l'exemple suivant, la première règle (pour la racine
'/'
) applique la règle à l'élément
text
avec le paramètre color
égal
à blue
. La valeur par défaut de ce paramètre est
black
.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <xsl:template match="/"> <xsl:apply-templates select="text"> <!-- Valeur du paramètre pour l'appel --> <xsl:with-param name="color" select="'blue'"/> </xsl:apply-templates> </xsl:template> <xsl:template match="text"> <!-- Déclaration du paramètre avec 'black' comme valeur par défaut --> <xsl:param name="color" select="'black'" as="xsd:string"/> <p style="color:{$color};"><xsl:value-of select="."/></p> </xsl:template> </xsl:stylesheet>
Dans l'exemple précédent, la valeur passée en paramètre est une
chaîne de caractères mais elle peut aussi être un nœud ou une liste de
nœuds. Dans l'exemple suivant, la règle get-id
retourne un attribut id
. La valeur de celui-ci est
produite à partir des attributs id
et
xml:id
du nœud passé en paramètre. Par défaut, ce
nœud est le nœud courant.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <xsl:template match="/"> <xsl:apply-templates select="*"/> </xsl:template> <xsl:template match="*"> <xsl:copy> <!-- Appel de la règle get-id avec la valeur par défaut du paramètre --> <xsl:call-template name="get-id"/> <xsl:apply-templates select="*"/> </xsl:copy> </xsl:template> <!-- Retourne un attribut id --> <xsl:template name="get-id"> <!-- Paramètre avec le noeud courant comme valeur par défaut --> <xsl:param name="node" as="node()" select="."/> <xsl:attribute name="id" select="($node/@id, $node/@xml:id, generate-id($node))[1]"/> </xsl:template> </xsl:stylesheet>
Un paramètre d'une règle peut avoir ou ne pas avoir de valeur par
défaut fournie lors de la déclaration de celui-ci par
xsl:param
. Lorsque la règle est appelée
implicitement ou explicitement sans donner de valeur au paramètre avec
xsl:with-param
, c'est la valeur par défaut qui est
utilisée si celle-ci existe et c'est une erreur sinon. Les paramètres
sans valeur par défaut doivent nécessairement recevoir une valeur avec
xsl:with-param
. Le processeur XSLT peut signaler
l'erreur ou continuer le traitement.
L'élément xsl:param
est également utilisé pour
déclarer les paramètres des fonctions d'extension XPath. La
règle get-id
de la feuille de style précédente
aurait aussi pu être remplacée par une fonction d'extension. Dans la
feuille de style suivante, la fonction XPath
get-id()
calcule la valeur de l'attribut
id
à partir des attributs id
et
xml:id
du nœud passé en paramètre.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:fun="http://www.omega-one.org/~carton"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <!-- Définition de la fonction d'extension get-id --> <xsl:function name="fun:get-id" as="xsd:string"> <xsl:param name="node" as="node()"/> <xsl:sequence select="($node/@id, $node/@xml:id, generate-id($node))[1]"/> </xsl:function> <xsl:template match="/"> <xsl:apply-templates select="*"/> </xsl:template> <xsl:template match="*"> <xsl:copy> <!-- Ajout de l'attribut id --> <xsl:attribute name="id" select="fun:get-id(.)"/> <xsl:apply-templates select="*"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
XPath 2.0 a ajouté des fonctions qui facilitent le traitement des
chaînes de caractères. Avec XSLT 1.0 et XPath 1.0, il est souvent
nécessaire d'avoir recours à des règles récursives pour certains
traitements. Les deux règles quote.string
et
quote.string.char
données ci-dessous insèrent un
caractère '\'
avant chaque caractère
'\'
ou '"'
d'une
chaîne.
<!-- Insertion de '\' avant chaque caractère '"' et '\' du contenu --> <xsl:template match="text()"> <xsl:call-template name="quote.string"> <xsl:with-param name="string" select="."/> </xsl:call-template> </xsl:template> <!-- Insertion de '\' avant chaque caractère '"' et '\' du paramètre string --> <xsl:template name="quote.string"> <!-- Chaîne reçue en paramètre --> <xsl:param name="string"/> <xsl:choose> <xsl:when test="contains($string, '"')"> <xsl:call-template name="quote.string.char"> <xsl:with-param name="string" select="$string"/> <xsl:with-param name="char" select="'"'"/> </xsl:call-template> </xsl:when> <xsl:when test="contains($string, '\')"> <xsl:call-template name="quote.string.char"> <xsl:with-param name="string" select="$string"/> <xsl:with-param name="char" select="'\'"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$string"/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- Fonction auxiliare pour quote.string --> <xsl:template name="quote.string.char"> <!-- Chaîne reçue en paramètre --> <xsl:param name="string"/> <!-- Caractère reçu en paramètre --> <xsl:param name="char"/> <xsl:variable name="prefix"> <xsl:call-template name="quote.string"> <xsl:with-param name="string" select="substring-before($string, $char)"/> </xsl:call-template> </xsl:variable> <xsl:variable name="suffix"> <xsl:call-template name="quote.string"> <xsl:with-param name="string" select="substring-after($string, $char)"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="concat($prefix, '\', $char, $suffix)"/> </xsl:template>
Ce traitement pourrait être réalisé de façon plus concise avec
la fonction XPath 2.0 replace()
.
<!-- Insertion de '\' avant chaque caractère '"' et '\' du contenu --> <xsl:template match="text()"> <xsl:value-of select="replace(., '(["\\])', '\\$1')"> </xsl:template>
Comme avec les fonctions ou procédures des langages de
programmation classiques, il arrive qu'une règle r1
transmette un ou plusieurs paramètres à une règle r2
pour que celle-ci les transmette finalement à une règle
r3
bien que la règle r2
n'utilise
pas les valeurs de ces paramètres. Il existe en XSLT un mécanisme
permettant à la règle r1
de transmettre des
paramètres directement à la règle r3
sans que ceux-ci
soient visibles dans la règle r2
. Ce mécanisme est
basé sur le fait qu'une règle reçoive tous les paramètres qui lui sont
transmis bien qu'elle puisse uniquement utiliser ceux qu'elle déclare
explicitement. Lorsqu'un paramètre est déclaré
tunnel en donnant la valeur yes
à l'attribut tunnel
, celui-ci est retransmis
automatique par toute règle qui le réçoit. Dans l'example ci-dessus,
la règle r1
déclare le paramètre t
comme tunnel puis le transmet à la règle r2
. Même
si la règle r2
ne le déclare pas, elle le réçoit puis
le transmet à nouveau à la règle r3
qui peut
l'utiliset en le déclarant. Si la règle r3
invoque,
à sont tour, une règle r4
, le paramètre
t
est encore transmis.
Dans la feuille de tyle suivante, la règle r1
pour les éléments list
transmet les quatres
paramètres normal1
, normal2
,
tunnel1
et tunnel2
. Les deux
premiers sont des paramètres normaux alors que les deux derniers sont
des paramètres tunnel. La règle r2
pour les éléments
sublist
déclare uniquement les deux paramètres
normal1
et tunnel1
. Bien qu'elle
reçoivent les quatre paramètres, elle peut uniquement utiliser les deux
paramètres normal1
et tunnel1
qu'elle déclare. Elle retransmet ensuite ces deux paramètres. La règle
r3
pour les éléments item
déclare
les quatre paramètres normal1
,
normal2
, tunnel1
et
tunnel2
avec des valeurs par défaut. Les deux
paramètres normal1
et tunnel1
n'utilisent pas cette valeur par défaut car ils ont été transmis par la
règle r2
. Le paramètre tunnel2
ne
l'utilise pas non plus car il réçoit directement la valeur transmise par
la règle r1
car il est déclaré comme paramètre
tunnel. Finalement, le paramètre normal2
prend la
valeur par défaut car il n'est pas tunnel et parce qu'il n'a pas été
transmis.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="utf-8" indent="yes"/> <xsl:template match="/"> <xsl:apply-templates select="*"/> </xsl:template> <!-- Règle r1 --> <xsl:template match="list"> <xsl:copy> <xsl:apply-templates select="*"> <xsl:with-param name="normal1" select="1"/> <xsl:with-param name="tunnel1" select="1" tunnel="yes"/> <xsl:with-param name="normal2" select="2"/> <xsl:with-param name="tunnel2" select="2" tunnel="yes"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <!-- Règle r2 --> <xsl:template match="sublist"> <xsl:param name="normal1" select="-1"/> <!-- L'attribut tunnel="yes" est nécessaire --> <!-- Sinon, il s'agit d'une variable différente --> <xsl:param name="tunnel1" select="-1" tunnel="yes"/> <xsl:copy> <xsl:value-of select="$normal1, $tunnel1"/> <xsl:apply-templates select="*"> <xsl:with-param name="normal1" select="$normal1"/> <xsl:with-param name="tunnel1" select="$tunnel1" tunnel="yes"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <!-- Règle r3 --> <xsl:template match="item"> <xsl:param name="normal1" select="'d'"/> <xsl:param name="tunnel1" select="'d'" tunnel="yes"/> <xsl:param name="normal2" select="'d'"/> <xsl:param name="tunnel2" select="'d'" tunnel="yes"/> <xsl:copy> <xsl:value-of select="$normal1, $tunnel1, $normal2, $tunnel2"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
En appliquant la feuille de style précédent au document suivant, obtient le document résultat donné ensuite.
<?xml version="1.0" encoding="utf-8"?> <list><sublist><item/></sublist></list>
Dans le document résultat, les deux valeurs 1
et 1
insérées dans l'élément
sublist
par xsl:value-of
sont les
valeurs transmises par les paramètres normal1
et
tunnel1
. Les valeurs 1
,
1
et 2
insérées dans l'élément
item
sont les valeurs transmises par les paramètres
normal1
, tunnel1
et
tunnel2
. La valeur d
est la
valeur par défaut du paramètre normal2
.
<?xml version="1.0" encoding="utf-8"?> <list><sublist>1 1<item>1 1 d 2</item></sublist></list>
Avec XSLT 2.0, il est possible de définir de nouvelles fonctions qui peuvent être utilisées dans les expressions XPath. Ces nouvelles fonctions viennent complèter la librairie des fonctions XPath. Elles remplacent avantageusement des constructions lourdes de XSLT 1.0 avec des règles récursives. Le traitement des chaînes de caractères est un champ d'utilisation classique de ces fonctions.
La définition d'une fonction XPath est introduite par l'élément
xsl:function
. Cet élément a des attributs name
et as
qui
donnent respectivement le nom et le type de retour de la fonction. Le
nom de la fonction est un nom qualifié avec un espace de noms. Les paramètres de la
fonction sont donnés par des éléments xsl:param
enfants de l'élément
xsl:function
. Chacun de ces éléments a aussi des
attributs name
et as
qui donnent
respectivement le nom et le type du paramètre. En revanche, l'élément
xsl:param
ne peut pas donner une valeur par défaut au
paramètre avec un attribut select
ou un contenu.
Cette restriction est justifiée par le fait que les fonctions XPath sont
toujours appelées avec un nombre de valeurs correspondant à leur arité
(nombre de paramètres).
Les types possibles pour le type de retour et les paramètres sont les
types XPath. La définition
d'une fonction prend donc la forme générique suivante.
<xsl:function name="name
" as="return-type
"> <!-- Paramètres de la fonction --> <xsl:param name="param1
" as="type1
"/> <xsl:param name="param2
" as="type2
"/> ... <!-- Corps de la fonction --> ... </xsl:function>
L'élément xsl:function
est nécessairement un
enfant de l'élément racine xsl:stylesheet
de la feuille de style. Ceci signifie que la portée de la définition
d'une fonction est la feuille de style dans son intégralité.
La fonction url:protocol
de l'exemple suivant
extrait la partie protocole
d'une URL. Elle a un paramètre url
qui reçoit une
chaîne de caractères. Elle retourne la chaîne de caractères située avant
le caractère ':'
si l'URL commence par un protocole
ou la chaîne vide sinon.
<xsl:function name="url:protocol" as="xsd:string"> <xsl:param name="url" as="xsd:string"/> <xsl:sequence select=" if (contains($url, ':')) then substring-before($url, ':') else ''"/> </xsl:function>
Une fois définie, la fonction url:protocol
peut
être utilisée comme n'importe quelle autre fonction XPath. Si la valeur
de l'attribut xml:base
est la
chaîne http://www.omega-one.org/index.html
, l'exemple
suivant crée un nœud textuel contenant la chaîne
http
.
<xsl:value-of select="url:protocol(@xml:base)"/>
La fonction url:protocol
peut être, à son tour,
utilisée pour définir une nouvelle fonction
url:address
qui extrait l'adresse internet d'une URL.
Cette fonction utilise une variable locale protocol
pour mémoriser le résultat de la fonction
url:protocol
.
<xsl:function name="url:address" as="xsd:string"> <xsl:param name="url" as="xsd:string"/> <xsl:variable name="protocol" as="xsd:string" select="url:protocol($url)"/> <xsl:sequence select=" if (($protocol eq 'file') or ($protocol eq '')) then '' else substring-before(substring-after($url, '://'), '/')"/> </xsl:function>
Si la variable uri
a la valeur
http://www.omega-one.org/~carton/index.html
, l'évaluation
de l'expression XPath url:address($uri)
donne la
chaîne de caractères www.omega-one.org
.
Les fonctions définies par xsl:function
peuvent
bien sûr être récursives. La fonction récursive suivante
url:file
extrait le nom du fichier d'un chemin
d'accès. C'est la chaîne de caractères située après la dernière
occurrence du caractère '/'
.
<xsl:function name="url:file" as="xsd:string"> <xsl:param name="path" as="xsd:string"/> <xsl:sequence select=" if (contains($path, '/')) then url:file(substring-after($path, '/')) else $path"/> </xsl:function>
Si la variable uri
a la valeur
http://www.omega-one.org/~carton/index.html
, l'évaluation
de l'expression url:file($uri)
donne la chaîne
de caractères index.html
.
Une fonction XPath est identifiée par son nom qualifié et son arité
(nombre de paramètres). Il est ainsi possible d'avoir plusieurs
fonctions de même nom à condition qu'elles soient d'arités différentes.
Ceci permet de simuler des paramètres avec des valeurs par défaut en
donnant plusieurs définitions d'une même fonction avec des nombres de
paramètres différents. Dans l'exemple suivant, la fonction
fun:join-path
est définie une première fois avec trois
paramètres. La seconde définition avec seulement deux paramètres permet
d'omettre le troisième paramètre qui devient ainsi optionnel.
<!-- Définition d'une fonction join-path avec 3 paramètres --> <xsl:function name="fun:join-path" as="xsd:string"> <xsl:param name="path1" as="xsd:string"/> <xsl:param name="path2" as="xsd:string"/> <xsl:param name="sep" as="xsd:string"/> <xsl:sequence select="concat($path1, $sep, $path2)"/> </xsl:function> <!-- Définition d'une fonction join-path avec 2 paramètres --> <xsl:function name="fun:join-path" as="xsd:string"> <xsl:param name="path1" as="xsd:string"/> <xsl:param name="path2" as="xsd:string"/> <xsl:sequence select="concat($path1, '/' , $path2)"/> </xsl:function> ... <!-- Appel de la fonction à 3 paramètres --> <xsl:value-of select="fun:join-path('Directory', 'index.html', '/')"/> <!-- Appel de la fonction à 2 paramètres --> <xsl:value-of select="fun:join-path('Directory', 'index.html')"/>
Les modes sont un mécanisme important des feuilles de style XSLT. Ils permettent de faire subir plusieurs traitements différents à un même nœud du document source. Sans les modes, c'est toujours la même règle qui est appliquée à un même nœud pour donner le même résultat. Il est possible d'utiliser des paramètres pour changer le traitement à effectuer mais cette méthode n'est pas très pratique car elle oblige à regrouper dans une même règle plusieurs traitements différents. L'idée des modes est d'écrire une règle pour chaque traitement et ensuite de préciser le traitement à effectuer lorsqu'une règle est appliquée.
L'exemple classique est la transformation d'un document fait de chapitres et de sections (comme un document DocBook) en un document d'un autre format tel PDF ou HTML. La table des matière n'est pas écrite dans le document source. Elle est calculée à partir de la liste des chapitres et des sections. Chaque chapitre doit ainsi être traité deux fois : une première fois pour produire les pages qui le contiennent et une seconde fois pour en extraire une partie de la table des matières. Les deux traitements sont très différents. Le premiet traite tout le contenu du chapitre alors que le second se contente de récupérer le titre du chapitre et les titres de ses sections.
Ces différents traitements peuvent être distingués par des
modes. Chaque règle de la feuille de style déclare
pour quels modes elle s'applique avec l'attribut mode
de l'élément xsl:template
. En parallèle, chaque
application de règles avec xsl:apply-templates
spécifie un mode avec un attribut mode
.
Chaque mode est identifié par un nom XML. Il existe en outre les
valeurs particulières #default
,
#all
et #current
qui peuvent
apparaître dans les valeurs des attributs mode
.
La valeur de l'attribut mode
de l'élément
xsl:template
est soit la valeur
#all
soit une liste de modes, y compris
#default
, séparés par des espaces. La valeur
#current
n'a pas de sens dans ce contexte et ne peut
pas apparaître. La valeur par défaut est bien sûr
#default
.
<!-- Règle applicable avec le mode #default --> <xsl:template match="..."> ... <!-- Règle applicable avec le mode test --> <xsl:template match="..." mode="test"> ... <!-- Règle applicable avec les modes #default foo et bar --> <xsl:template match="..." mode="#default foo bar"> ... <!-- Règle applicable avec tous les modes --> <xsl:template match="..." mode="#all"> ...
La valeur de l'attribut mode
de l'élément
xsl:apply-templates
est soit
#default
soit #current
soit le nom
d'un seul mode. La valeur #all
n'a pas de sens dans
ce contexte et ne peut pas apparaître. La valeur par défaut est, bien
sûr, #default
. La valeur #current
permet appliquer des règles avec le même mode que celui de la règle en
cours. Elle est utile lorsqu'une règle peut s'appliquer dans plusieurs
modes.
<!-- Application avec le mode #default --> <xsl:apply-templates select="..."/> ... <!-- Application avec le mode test --> <xsl:apply-templates select="..." mode="test"/> ... <!-- Application avec le mode déjà en cours --> <xsl:apply-templates select="..." mode="#current"/> ...
Dans la feuille de style suivante, la règle sur la racine
'/'
applique, à deux reprises, des règles sur les
éléments sublist
: une première fois dans le mode par
défaut et une seconde fois dans le mode mode1
. La même
règle est appliquée les deux fois puisque la règle pour les éléments
sublist
s'applique justement dans le mode par défaut
et dans le mode mode1
. À son tour, cette règle
applique des règles, à deux reprises, sur les éléments
item
: une première fois dans le mode en cours et une
seconde fois dans le mode mode2
. Cela donne au total
quatre applications de règles sur les éléments item
:
une dans le mode par défaut, une dans le mode mode1
et
deux dans le mode mode2
. Comme il existe uniquement
des règles pour les modes mode1
et
mode2
pour les éléments item
, il
n'y a en définitive que trois applications de règles sur ces éléments :
une dans le mode mode1
et deux dans le mode
mode2
.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="utf-8"/> <!-- Règle pour la racine --> <xsl:template match="/"> <list> <xsl:apply-templates select="list/*"/> <xsl:apply-templates select="list/*" mode="mode1"/> </list> </xsl:template> <!-- Règle pour les éléments sublist --> <xsl:template match="sublist" mode="#default mode1"> <xsl:copy> <xsl:apply-templates select="*" mode="#current"/> <xsl:apply-templates select="*" mode="mode2"/> </xsl:copy> </xsl:template> <!-- Règles pour le éléments item --> <xsl:template match="item" mode="mode1"> <item1/> </xsl:template> <xsl:template match="item" mode="mode2"> <item2/> </xsl:template> </xsl:stylesheet>
En appliquant la feuille de style précédente au document suivant, on obtient le document donné ensuite.
<?xml version="1.0" encoding="utf-8"?> <list><sublist><item/></sublist></list>
Le document résultat contient deux éléments
sublist
créés par l'élément
xsl:copy
lors des deux applications de la règle dans
le mode par défaut et dans le mode mode1
. Le premier
élément sublist
contient un élément
item2
créé par l'application de la règle dans le mode
mode2
. Le second élément sublist
contient des éléments item1
et
item2
créés par l'application de règles dans les modes
mode1
et mode2
.
<?xml version="1.0" encoding="utf-8"?> <list> <sublist><item2/></sublist> <sublist><item1/><item2/></sublist> </list>
L'exemple suivant illustre une utilisation classique des modes. Le
document est traité une première fois en mode toc
pour
en extraire une table des matières et une seconde fois pour créer le
corps du document proprement dit.
<!-- Règle pour la racine --> <xsl:template match="/"> <html> <head> <title><xsl:value-of select="book/title"/></title> </head> <body> <!-- Fabrication de la table des matières --> <xsl:apply-templates mode="toc"/> <!-- Fabrication du corps du document --> <xsl:apply-templates/> </body> </html> </xsl:template> ... <!-- Règles pour la table des matières --> <xsl:template match="book" mode="toc"> <h1>Table des matières</h1> <ul><xsl:apply-templates mode="toc"/></ul> </xsl:template>
La fonction id()
permet de
retrouver des nœuds dans un document à partir de leur attribut de type
ID
. Elle
prend en paramètre une liste de noms séparés par des espaces. Elle
retourne une liste contenant les éléments dont la valeur de l'attribut de
type ID
est un des noms passés en paramètre. Cette
fonction est typiquement utilisée pour traiter des attributs de type
IDREF
ou IDREFS
. Ces attributs
servent à référencer des éléments du document en donnant une (pour
IDREF
) ou plusieurs (pour IDREFS
)
valeurs d'attibuts de type ID
. La fonction
id()
permet justement de retrouver ces éléments
référencés.
L'exemple suivant illustre l'utilisation classique de la fonction
id()
. On suppose avoir un document contenant une
bibliographie où les éditeurs ont été placés dans une section séparée du
document. Chaque élément
book
contient un élément published
ayant un attribut by
de type IDREF
pour identifier l'élément publisher
correspondant dans
la liste des édideurs. La feuille de style suivante permet de revenir à
une bibliographie où chaque élément book
contient
directement l'élément publisher
correspondant.
L'élément retourné par la fonction id()
est mémorisé
dans une variable pour
l'utiliser à plusieurs reprises.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="iso-8859-1" indent="yes"/>
<!-- Règle pour la racine -->
<xsl:template match="/">
<bibliography>
<xsl:apply-templates select="bibliography/books/book"/>
</bibliography>
</xsl:template>
<!-- Règles pour les livres -->
<xsl:template match="book">
<book>
<!-- Copie des attributs -->
<xsl:copy-of select="@*"/>
<!-- Copie des éléments autres que published -->
<xsl:copy-of select="*[name() != 'published']"/>
<!-- Remplacement des éléments published -->
<xsl:apply-templates select="published"/>
</book>
</xsl:template>
<!-- Règle pour remplacer published par le publisher référencé -->
<xsl:template match="published">
<!-- Elément publisher référencé par l'élément published -->
<xsl:variable name="pubnode" select="id(@by)"/>
<publisher>
<!-- Recopie des attributs autres que id -->
<!-- L'attribut id ne doit pas être recopié car sinon,
on peut obtenir plusieurs éléments publisher avec
la même valeur de cet attribut -->
<xsl:copy-of select="$pubnode/@*[name() != 'id']"/>
<xsl:copy-of select="$pubnode/* | $pubnode/text()"/>
</publisher>
</xsl:template>
</xsl:stylesheet>
Lorsque la fonction id()
retourne plusieurs
nœuds, il est possible de les traiter un par un en utilisant un élément
xsl:for-each
comme dans l'exemple suivant.
<xsl:for-each select="id(@arearefs)">
<xsl:apply-templates select="."/>
</xsl:for-each>
Afin de pouvoir accéder efficacement à des nœuds d'un document XML,
il est possible de créer des index. L'élément xsl:key
crée un index. L'élément xsl:key
doit être un enfant
de l'élément racine xsl:stylesheet
.
L'attribut name
de xsl:key
fixe le
nom de l'index pour son utilisation. L'attribut match
contient un motif qui détermine
les nœuds indexés. L'attribut use
contient une
expression XPath qui spécifie la clé d'indexation, c'est-à-dire la valeur
qui identifie les nœuds et permet de les retrouver. Pour chaque nœud
sélectionné par le motif, cette expression est évaluée en prenant le nœud
sélectionné comme nœud courant. Pour indexer des éléments en fonction de
la valeur de leur attribut type
, on utilise
l'expression @type
comme valeur de l'attribut
use
. La valeur de l'attribut use
peut être une expression plus complexe qui sélectionne des enfants et des
attributs. Dans l'exemple suivant, tous les éléments
chapter
du document sont indéxés en fonction de leur
attribut id
.
<xsl:key name="idchapter" match="chapter" use="@id"/>
La fonction key()
de XPath permet de retrouver
un nœud en utilisant un index créé par xsl:key
. Le
premier paramètre est le nom de l'index et le second est la valeur de la
clé. Dans l'exemple suivant, on utilise l'index créé à l'exemple
précédent. La valeur de l'attribut @idref
d'un
élément contenu dans la variable $node
sert pour
retrouver l'élément dont c'est la valeur de l'attribut
id
. Le nom idchapter
de l'index
est un nom XML et il doit être placé entre apostrophes ou
guillemets.
<xsl:value-of select="key('idchapter', $node/@idref)/title"/>
L'utilisation d'un index peut être contournée par des expressions
XPath appropriées. L'expression ci-dessus avec la fonction
key
est équivalente à l'expression XPath ci-dessous
qui utilise l'opérateur '//'
.
<xsl:value-of select="//chapter[@id = $node/@idref]/title"/>
L'inconvénient de cette dernière expression est de provoquer un parcours complet du document à chaque évaluation. Si l'expression est évaluée à de nombreuses reprises, ceci peut conduire à des problèmes d'efficacité.
Lors de la présentation des attributs de type ID
et
IDREF
, il a été observé que la bibliographie
bibliography.xml
doit être organisée de façon
différente si les éléments publisher
contiennent des
informations autres que le nom de l'éditeur. Afin d'éviter de dupliquer
ces informations dans chaque livre, il est préférable de regrouper les
éléments publisher
dans une section à part. Chaque
élément publisher
contenu dans un élément
book
est alors remplacé par un élément
published
avec un attribut by
de
type IDREF
pour référencer l'élément
publisher
déplacé. La feuille de style suivante
remplace les éléments publisher
par des éléments
published
et les regroupe dans une liste en fin de
document. Elle suppose que chaque élément publisher
contient, au moins, un enfant name
avec le nom de
l'éditeur. Elle supprime également les doublons en évitant que deux
éléments publisher
avec le même nom, c'est-à-dire le
même contenu de l'élément name
, apparaissent dans la
liste. Cette suppression des doublons est réalisée par une indexation
des éléments publisher
sur le contenu de
name
. Pour chaque élément
publisher
, l'indexation permet de retrouver
efficacement tous les éléments publisher
avec un
contenu identique de name
. Seul le premier de ces
éléments publisher
est ajouté à la liste des éditeurs
dans le document résultat.
Pour savoir si un élément publisher
est le
premier de ces éléments avec le même nom, celui-ci est comparé avec le
premier de la liste retournée par la fonction key()
avec l'opérateur is
. La
copie de cet élément publisher
est réalisée par un
élément xsl:copy
ainsi que par élement xsl:copy-of
pour
ses attributs et ses enfants.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <!-- Indexation des éléments publisher par leur nom --> <xsl:key name="pubname" match="publisher" use="name"/> <xsl:template match="/"> <bibliography> <books> <!-- Livres --> <xsl:apply-templates select="bibliography/book"/> </books> <publishers> <!-- Éditeurs --> <xsl:apply-templates select="bibliography/book/publisher"/> </publishers> </bibliography> </xsl:template> <!-- Règles pour les livres --> <xsl:template match="book"> <book> <!-- Copie des attributs et des enfants autres que publisher --> <xsl:copy-of select="@* | *[name() != 'publisher']"/> <!-- Transformation de l'élément publisher en élément published --> <xsl:if test="publisher"> <published id="{generate-id(key('pubname', publisher/name)[1])}"/> </xsl:if> </book> </xsl:template> <!-- Copie d'un élément publisher en ajoutant un attribut id --> <xsl:template match="publisher"> <!-- Test si l'élément publisher est le premier avec le même nom --> <xsl:if test=". is key('pubname', name)[1]"> <xsl:copy> <!-- Ajout de l'attribut id --> <xsl:attribute name="id"> <xsl:value-of select="generate-id()"/> </xsl:attribute> <!-- Copie des attributs et des enfants --> <xsl:copy-of select="@* | *"/> </xsl:copy> </xsl:if> </xsl:template> </xsl:stylesheet>
Le langage XSLT 1.0 était conçu au départ pour prendre un seul document XML en entrée et pour produire un seul document résultat. Il est néanmoins pratique de pouvoir éventuellement prendre plusieurs documents en entrée et de produire plusieurs documents en sortie.
La fonction document()
est fournie à XPath
par le langage XSLT. Elle permet d'accéder à des documents autres
que le document source traité par la feuille de style. Il est ainsi
possible de manipuler plusieurs documents avec une seule feuille de
style.
La façon la plus simple d'utiliser la fonction
document()
est de lui passer en paramètre l'URI d'un
document XML. La fonction retourne alors le nœud racine du document.
Ceci signifie que le document a été lu et transformé en arbre. Le
résultat peut être mémorisé dans une variable ou être utilisé
directement. Si l'URI est relative, elle est résolue à partir de
l'URI de base d'un second
paramètre optionnel qui doit être un nœud d'un document. Le document
suivant files.xml
référence deux autres documents
europa.xml
et states.xml
par
l'intermédiaire des attributs href
des éléments
file
.
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <!-- Document files.xml --> <files> <file href="europa.xml"/> <file href="states.xml"/> </files>
Les deux documents europa.xml
et
states.xml
référencés par le document
files.xml
sont les suivants.
<?xml version="1.0" encoding="utf-8"> <?xml version="1.0" encoding="utf-8"?> <!-- Document europa.xml --> <!-- Document states.xml --> <cities> <cities> <city>Berlin</city> <city>Los Angeles</city> <city>Paris</city> <city>Chicago</city> </cities> </cities>
La feuille de style suivante collecte toutes les villes des
documents europa.xml
et
states.xml
pour en faire une liste unique. Pour
chaque élément file
de
files.xml
, l'appel
document(@href)
retourne le document référencé par
l'attribut href
.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="utf-8" indent="yes"/> <xsl:template match="/"> <list> <xsl:for-each select="files/file"> <xsl:apply-templates select="document(@href)/cities/city"> <!-- Tri des villes par leur nom --> <xsl:sort select="."/> </xsl:apply-templates> </xsl:for-each> </list> </xsl:template> <xsl:template match="city"> <item><xsl:value-of select="."/></item> </xsl:template> </xsl:stylesheet>
L'application de la feuille de style précédente au document
files.xml
donne le document ci-dessous. L'élément
xsl:sort
,
enfant de l'élément xsl:apply-templates
, trie
les villes à l'intérieur de chacun des documents
europa.xml
et states.xml
. En
revanche, les villes ne sont pas triées globalement.
<?xml version="1.0" encoding="utf-8"?> <list> <item>Berlin</item> <item>Paris</item> <item>Chicago</item> <item>Los Angeles</item> </list>
Le premier paramètre de la fonction document()
peut également être une liste de nœuds. Pour chaque nœud de cette
liste, la fonction document()
est appelée avec comme
premier paramètre la valeur
textuelle du nœud interprétée comme un URI. Le second paramètre
éventuel est également transmis. Chacun de ces appels
retourne le nœud racine d'un document. Le résultat global de la
fonction document()
est la liste des nœuds retournés
par ces différents appels. La feuille de style suivante collecte
également toutes les villes des documents
europa.xml
et states.xml
pour
en faire une liste unique. Le paramètre de la fonction
document()
est la liste des attributs
href
des éléments file
dans le
document files.xml
.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="utf-8" indent="yes"/> <xsl:template match="/"> <list> <xsl:apply-templates select="document(files/file/@href)/cities/city"> <!-- Tri des villes par leur nom --> <xsl:sort select="."/> </xsl:apply-templates> </list> </xsl:template> <xsl:template match="city"> <item><xsl:value-of select="."/></item> </xsl:template> </xsl:stylesheet>
L'application de la feuille de style précédente au document
files.xml
donne le document ci-dessous. L'élément
xsl:sort
,
enfant de l'élément xsl:apply-templates
, trie
les villes globalement.
<?xml version="1.0" encoding="utf-8"?> <list> <item>Berlin</item> <item>Chicago</item> <item>Los Angeles</item> <item>Paris</item> </list>
La fonction doc()
de XPath 2.0 a un rôle
similaire à la fonction document()
. La fonction
collection()
retourne également une liste de nœuds
provenant de documents externes. Le fonctionnement de cette fonction
est propre à chaque processeur XSLT.
L'élément xsl:result-document
introduit par
XSLT 2.0 permet à une seule feuille de style de produire plusieurs
documents résultat. Cet élément a un attribut href
qui donne l'URI d'un document. Cet URI est généralement le nom d'un
fichier mais il pourrait aussi utiliser un protocole de transfert de
fichiers comme sftp
à la condition que le processeur
XSLT implémente ce protocole. L'élément
xsl:result-document
a également un attribut
format
qui référence un élément xsl:output
par son
nom (donné par l'attribut name
de celui-ci). Cet
élément contrôle le format du document écrit dans le fichier. Le
contenu de l'élément xsl:result-document
détermine ce
qui est écrit dans le document. Il s'agit d'éléments XSLT qui sont
évalués et remplacés par le résultat de leur évaluation ainsi que
d'éléments hors de l'espace de noms XSLT qui sont recopiés à
l'identique. L'URI du document peut être calculé dynamiquement par une
expression XPath contenue
dans l'attribut href
. Dans la feuille de style
suivante, le nom du fichier est obtenu en concaténant la chaîne
multi-
avec la valeur de l'attribut
href
d'un élément file
du document
source.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="utf-8" indent="yes" name="format-xml"/> <xsl:template match="/"> <xsl:apply-templates select="files/file"/> </xsl:template> <xsl:template match="file"> <xsl:result-document href="{concat('multi-',@href)}" format="format-xml"> <xsl:comment> <xsl:text> Document </xsl:text> <xsl:value-of select="concat('multi-',@href)"/> <xsl:text> </xsl:text> </xsl:comment> <xsl:copy-of select="*"/> </xsl:result-document> </xsl:template> </xsl:stylesheet>
La feuille de style précédente peut être appliquée au document
suivant qui contient des éléments file
avec un attribut
href
encapsulés dans un élément
files
.
<?xml version="1.0" encoding="utf-8"?> <files> <file href="out1.xml"> <list><item>1</item><item>2</item><item>3</item></list> </file> <file href="out2.xml"> <list><item>3</item><item>2</item><item>1</item></list> </file> </files>
En appliquant le feuille de style précédente au document ci-dessus,
obtient les deux documents multi-out1.xml
et
multi-out2.xml
donnés ci-dessous.
<?xml version="1.0" encoding="utf-8"?> <!-- Document multi-out1.xml --> <list><item>1</item><item>2</item><item>3</item></list> <?xml version="1.0" encoding="utf-8"?> <!-- Document multi-out2.xml --> <list><item>3</item><item>2</item><item>1</item></list>
Il arrive que le document source ne soit pas suffisament structuré
et que la feuille de style ait besoin d'extraire des morceaux de texte.
L'élément xsl:analyze-string
permet d'analyser une
chaîne de caractères et de la découper en fragments. Ces fragments
peuvent ensuite être repris et utilisés. L'analyse est réalisée avec une
expression rationnelle.
La chaîne à analyser et l'expression rationnelle sont
respectivement données par les attributs select
et
regex
de l'élément
xsl:analyze-string
. Les deux enfants
xsl:matching-substring
et
xsl:non-matching-substring
de
xsl:analyze-string
donnent le résultat suivant que la
chaîne est compatible ou non avec l'expression.
La fonction XPath regex-group()
permet de
récupérer un fragment de la chaîne correspondant à un bloc délimité par
des parenthèses dans l'expression. L'entier fourni en paramètre donne le
numéro du bloc. Les blocs sont numérotés à partir de 1.
Dans l'exemple suivant, le contenu de l'élément
name
est découpé à la virgule pour extraire le prénom
et le nom de famille d'un nom complet écrit suivant la convention
anglaise. Les deux parties du nom sont ensuite utilisées pour construire
les enfants firstname
et lastname
de name
. Lorsque le contenu ne contient pas de
virgule, l'élément name
est laissé inchangé.
<xsl:template match="name"> <xsl:analyze-string select="." regex="([^,]*),\s*(.*)"> <xsl:matching-substring> <name> <firstname><xsl:value-of select="regex-group(2)"/></firstname> <lastname><xsl:value-of select="regex-group(1)"/></lastname> </name> </xsl:matching-substring> <xsl:non-matching-substring> <name><xsl:value-of select="."/></name> </xsl:non-matching-substring> </xsl:analyze-string> </xsl:template>
La feuille de style précédente peut être appliquée au document suivant.
<?xml version="1.0" encoding="iso-8859-1"?> <names> <name>Lagaffe, Gaston</name> <name>Talon, Achille</name> <name>Astérix</name> </names>
On obtient le document suivant où les noms sont mieux structurés.
<?xml version="1.0" encoding="iso-8859-1"?> <names> <name><firstname>Gaston</firstname><lastname>Lagaffe</lastname></name> <name><firstname>Achille</firstname><lastname>Talon</lastname></name> <name>Astérix</name> </names>
Les éléments xsl:include
et
xsl:import
permettent d'inclure les règles d'une
feuille de style au sein d'une autre feuille de style. La seule
différence entre les deux éléments est la gestion des priorités entre les
règles des deux feuilles de style. Ces priorités influencent les choix de
règles à appliquer sur les nœuds. Ces deux éléments ont un attribut
href
contenant l'URL de la feuille de style à inclure.
Cette URL est très souvent le nom relatif ou absolu d'un fichier local.
Les deux éléments xsl:include
et
xsl:import
doivent être enfants de l'élément racine
xsl:stylesheet
et ils doivent être placés avant toute
définition de règle par un élément xsl:template
.
L'exemple suivant est la feuille de style principale pour la
transformation de cet ouvrage au format DocBook en HTML.
<?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dbk="http://docbook.org/ns/docbook"> <!-- Import de la feuille de style docbook.xsl --> <xsl:import href="/usr/share/sgml/docbook/xsl-stylesheets/html/profile-docbook.xsl"/> <!-- Ajout des paramètres communs de configuration --> <xsl:include href="common.xsl"/> <!-- Feuille de style CSS --> <xsl:param name="html.stylesheet" select="'style.css'"/> <!-- Pour les paragraphes justifiés à droite --> <xsl:template match="dbk:para[@role = 'right']"> <p align="right"><xsl:apply-templates/></p> </xsl:template> </xsl:stylesheet>
Lorsque un processeur XSLT choisit une règle à appliquer à un nœud
du document source, il prend en compte deux paramètres qui sont la
priorité d'import entre les feuilles de style et les
priorités entre les règles. Lorsque la feuille de style est importée
avec l'élément xsl:include
, les deux feuilles de style
ont même priorité d'import comme si les règles des deux feuilles se
trouvaient dans une même feuille de style. Au contraire, lorsque la
feuille de style est importée avec l'élément
xsl:import
, la feuille de style importée a une
priorité d'import inférieure. Les règles de la feuille de style qui
réalise l'import, c'est-à-dire celle qui contient l'élément
xsl:import
, sont appliquées en priorité sur les règles
de la feuille importée. Ce mécanisme permet d'adapter la feuille de
style importée en ajoutant des règles dans la feuille de style qui
importe. Lorsqu'une règle ajoutée remplace une règles de la feuille
importée, elle peut encore utiliser la règle remplacée grâce à l'élément
xsl:apply-imports
.
Une feuille de style peut importer plusieurs feuilles de style avec
plusieurs éléments xsl:import
. Dans ce cas, les
feuilles de style importées en premier ont une priorité d'import
inférieure. L'ordre des priorités d'import est l'ordre des éléments
xsl:import
dans la feuille de style qui importe. Il
est également possible qu'une feuille de style importée par un élément
xsl:import
réalise elle-même des imports d'autres
feuilles de style avec des éléments xsl:import
.
Lorsqu'il y a plusieurs niveaux d'import, l'ordre d'import entre les
différentes feuilles de style est d'abord dicté par l'ordre des imports
de premier niveau puis l'ordre des imports de second niveau et ainsi de
suite. Ce principe est illustré par l'exemple suivant. Supposons qu'une
feuille de style A importe, dans cet ordre, les feuilles de style B et C,
que la feuille B importe, à son tour, les feuilles D et E et que la
feuille C importe, finalement, les feuilles F et G (cf. Figure). L'ordre
des feuilles par priorité d'import croissante est D, E, B, F, G, C, A.
Cet ordre correspond à un parcours suffixe de l'arbre des imports.