Chapitre 8. XSLT

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/

8.1. Principe

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.

Principe de XSLT

Figure 8.1. Principe de XSLT


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.

8.2. Premier programme : Hello, World!

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"1
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"2 
                xmlns="http://www.w3.org/1999/xhtml">3
  <xsl:template match="/">4
    <html>5
      <head>
        <title>Hello World!</title>
      </head>
      <body>
        <h1>Hello world!</h1>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet> 

1

Élément racine xsl:stylesheet de la feuille de style.

2

Déclaration de l'espace de noms XSLT associé au préfixe xsl.

3

Déclaration de l'espace de noms XHTML comme espace de noms par défaut.

4

Définition d'une règle s'appliquant à la racine '/' du document source.

5

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.

8.3. Modèle de traitement

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.

Traitement

Figure 8.2. Traitement


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.

8.4. Entête

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.

8.4.1. Traitement des espaces

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>

8.5. Définition et application de règles

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.

8.5.1. Définition de règles

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().

8.5.2. Application implicite

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.

8.5.3. Priorité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 expr-1 | ... | expr-N, le processeur considère chacun des motifs 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 *:name ou prefix:* comme *:p ou dbk:*,

0

pour les motifs de la forme name ou @name comme section ou @type,

0.5

pour tous les autres motifs comme chapter/section ou section[@type].

8.5.4. Application de règles moins prioritaires

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>

8.5.5. Application explicite

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.

8.6. Construction de contenu

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.

8.6.1. Contenu brut

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> 

8.6.1.1. Substitution d'espaces de noms

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>
  ...

8.6.2. Règles par défaut

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.

8.6.3. Expression XPath en attribut

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.

8.6.4. Texte brut

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 '&lt;', '&gt;' et '&amp;'</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>

8.6.5. Expression XPath

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"/>

8.6.6. Élément et attribut

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.

8.6.6.1. Groupe 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> 

8.6.7. Commentaire et instruction de traitement

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.

8.6.8. Liste d'objets

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.

8.6.9. Copie superficielle

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.

8.6.10. Copie profonde

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> 

8.6.11. Numérotation

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().

8.6.11.1. Formats

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.

FormatRé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.

FormatRé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


8.6.11.2. Calcul du numéro

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].

Modes de xsl:number

Figure 8.3. Modes de xsl:number


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>

8.6.12. Formatage de nombres

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>
  ...

8.7. Structures de contrôle

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.

8.7.1. Conditionnelle sans alternative

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>

8.7.2. Conditionnelle à alternatives multiples

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>

8.7.3. Itération simple

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>

8.7.4. Itération sur des groupes

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>

8.8. Tris

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> 

8.8.1. Tri de listes

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> 

8.9. Variables et paramètres

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.

8.9.1. Variables

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.

8.9.2. Paramètres

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/>

8.9.2.1. Paramètres globaux

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

8.9.2.2. Paramètres locaux

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> 

8.9.3. Récursivité

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, '&quot;')">
      <xsl:call-template name="quote.string.char">
        <xsl:with-param name="string" select="$string"/>
        <xsl:with-param name="char"   select="'&quot;'"/>
      </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(., '([&quot;\\])', '\\$1')">
</xsl:template>

8.9.4. Paramètres tunnel

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>

8.10. Fonctions d'extension XPath

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')"/>

8.11. Modes

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>

8.12. Indexation

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> 

8.13. Documents multiples

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.

8.13.1. Documents multiples en entrée

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.

8.13.2. Documents multiples en sortie

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>

8.14. Analyse de chaînes

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>

8.15. Import de feuilles de style

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.

Priorités d'import

Figure 8.4. Priorités d'import