INaLCO - M2 Ingénierie Multilingue

Techniques XML, cours n°2

Jean-François Perrot

XSLT
eXtensible Stylesheet Language Transform

I. Origines, mise en œuvre
  1. Un langage pour des feuilles de style
  2. Mise en œuvre des feuilles de style
II. Premiers exemples
  1. Visualisation d'un fichier de noms et de notes dans un navigateur
  2. Commentaire
  3. Première variation : attributs
  4. Deuxième variation : liste
  5. Troisième variation : attributs + liste
III. Vue d'ensemble de la mécanique
  1. L'unité de base de la programmation XSLT est la règle.
  2. Mécanisme récursif
  3. Application d'une règle à un sommet (nœud) de l'arbre XML
  4. Retour sur les règles implicites (ou "par défaut" : default rules)
  5. Variations avec le matériel dont nous disposons
IV. Rudiments de XPath
  1. XPath
  2. Chemins simples
  3. Les prédicats
  4. Les fonctions
  5. Un exemple "international"
    1. Le fichier XML : ExInter.xml
    2. Le spectacle produit :
    3. La feuille de style : list.xsl
V. Structures de contrôle, règles paramétrées, règles nommées
  1. Pilotage des règles par <xsl:sort .../>
  2. Structures de contrôle
  3. Appels récursifs explicites
  4. Règles paramétrées
  5. L'exemple de la génération des histogrammes
VI. Un exemple (relativement) complet
  1. But
  2. Principe
  3. La feuille de style finale HtmlMIL.xsl
  4. La feuille de style principale PrettyMIL.xsl
  5. La feuille spéciale pour les expressions ExpArBoolMIL2ap.xsl

I. Origines, mise en œuvre

  1. Un langage pour des feuilles de style

    separation of concerns : séparer la structure de la présentation

  2. Mise en œuvre des feuilles de style

II. Premiers exemples

  1. Visualisation d'un fichier de noms et de notes dans un navigateur :

    à partir du fichier XML [Nom_note_2.xml] :

    <?xml version="1.0" ?>
    <liste>
    <eleve>
    <nom> Toto</nom> <note> 12 </note>
    </eleve>
    <eleve>
    <nom> Tata</nom> <note> 13 </note>
    </eleve>
    <eleve>
    <nom> Tutu</nom> <note> 17 </note>
    </eleve>
    <eleve>
    <nom> Titi</nom> <note> 11 </note>
    </eleve>
    </liste>

    on veut obtenir ce spectacle :

    Table

    Pour cela, il faut transformer l'arbre XML en un texte HTML interprétable par le navigateur :

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <title>Visualisation</title>
    </head>
    <body>
    <h2> Voici le tableau des noms et des notes</h2>
    <table border="2" bgcolor="yellow">
    <tr>
    <th>Nom</th>
    <th>Note</th>
    </tr>
    <tr>
    <td> Toto</td>
    <td> 12 </td>
    </tr>
    <tr>
    <td> Tata</td>
    <td> 13 </td>
    </tr>
    <tr>
    <td> Tutu</td>
    <td> 17 </td>
    </tr>
    <tr>
    <td> Titi</td>
    <td> 11 </td>
    </tr>
    </table>
    </body>
    </html>

    C'est l'objet de la feuille de style XSLT que voici [table.xsl]:


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

    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
    <html><head><title>Visualisation</title></head>
    <body>
    <h2> Voici le tableau des noms et des notes</h2>
    <table border="2" bgcolor="yellow">
    <tr>
    <th>Nom</th>
    <th>Note</th>
    </tr>
    <xsl:apply-templates/>
    </table>
    </body>
    </html>
    </xsl:template>

    <xsl:template match="liste/eleve">
    <tr>
    <td><xsl:value-of select="nom"/></td>
    <td><xsl:value-of select="note"/></td>
    </tr>
    </xsl:template>

    </xsl:stylesheet>
  2. Commentaire

    La feuille de style est elle-même un fichier XML, qui fait appel à l'espace de noms
    "http://www.w3.org/1999/XSL/Transform".

    Les éléments <xsl:template> représentent des règles,
    dont l'application à un fichier XML a pour effet d'engendrer le texte en sortie.
    Leur attribut "match" détermine leur domaine d'application.

    La première s'applique au document XML tout entier (symbolisé par "/").
    Elle a  pour effet
    La seconde s'applique aux éléments de nom <eleve> qui sont fils de <liste>.
    Pour chacun d'entre eux, elle engendre une ligne <tr> composée de deux celleules <td>
    contenant la "valeur" des éléments-fils <nom> et <note> respectivement.
  3. Première variation : attributs

    Pour obtenir le même résultat à partir d'un fichier XML où les noms et les notes
    sont représentés par des attributs et non par les éléments-fils (cf. la discussion dans le Cours n° 1),
    comme ceci [Nom_note_1.xml]:

    <?xml version="1.0" ?>
    <liste>
    <eleve nom="Toto" note="12"/>
    <eleve nom="Tata" note="13"/>
    <eleve nom="Tutu" note="17"/>
    <eleve nom="Tutu" note="11"/>
    </liste>

    il suffit de modifier la seconde règle ainsi [tableAt.xsl]:

    <xsl:template match="liste/eleve">
        <tr>
            <td><xsl:value-of select="@nom"/></td>
            <td><xsl:value-of select="@note"/></td>
        </tr>
    </xsl:template>


    La présence de l'arrobas dans select="@nom" et dans select="@note" 
    indique qu'on s'intéresse aux attributs de l'élément considéré et non pas à ses enfants.

    Notons que <xsl:value-of> renvoie
  4. Deuxième variation : liste

    On veut maintenant afficher ceci, à partir de notre premier fichier XML.

    Liste

    En raisonnant par analogie, on voit que la feuille de style suivante devrait faire l'affaire [list.xsl] :


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

    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
    <html><head><title>Visualisation</title></head>
    <body>
    <h2> Voici la liste des noms et des notes</h2>
    <ol>
    <xsl:apply-templates/>
    </ol>
    </body>
    </html>
    </xsl:template>

    <xsl:template match="liste/eleve">
    <li>
    <xsl:value-of select="nom"/> :
    <xsl:value-of select="note"/>
    </li>
    </xsl:template>

    </xsl:stylesheet>
  5. Troisième variation : attributs + liste

    Vous en savez assez pour rédiger une feuille de style propre à afficher en mode liste le fichier Nom_note_1.xml,
    et, toujours par analogie, pour en rédiger deux autre pour afficher le fichier ex.xml déjà exploité au cours n° 2 :
    <?xml version="1.0"?>
    <liste>
    <eleve nom='Pierre'> <note> 12 </note>
    </eleve>
    <eleve nom="Paul"> <note> 13 </note>
    </eleve>
    <eleve nom='Jacques'> <note>17 </note>
    </eleve>
    </liste>
    en mode table et en mode liste.

III. Vue d'ensemble de la mécanique

  1. L'unité de base de la programmation XSLT est la règle.

  2. Mécanisme récursif

  3. Application d'une règle à un sommet (nœud) de l'arbre XML

    C'est par rapport à ce sommet que sont évaluées les expressions qui apparaissent
    dans les attributs select (exemples ci-dessus) et test (voir plus loin) du corps de la règle.

    Prenons quelques exemples :

    1. avec la seconde règle de table.xsl :

      <xsl:template match="liste/eleve">
          <tr>
              <td><xsl:value-of select="nom"/></td>
              <td><xsl:value-of select="note"/></td>
          </tr>
      </xsl:template>

      appliquée au sommet (élément) de Nom_note_2.xml :

         <eleve>
          <nom> Toto</nom> <note> 12 </note>
         </eleve>

      La chaîne "nom" dans select="nom" s'évalue comme
      le premier sommet-fils de l'eleve en question dont la balise est "nom",
      et l'opérateur xsl:value-of renvoie la chaîne " Toto".

      De même, la chaîne "note" dans select="note" s'évalue comme
      le premier sommet-fils de l'eleve en question dont la balise est "note",
      et l'opérateur xsl:value-of renvoie la chaîne " 12 ".

      D'où pour l'application de la règle une contribution totale en sortie :
          <tr>
              <td> Toto</td>
              <td> 12 </td>
          </tr>


      Après quoi on passe au prochain sommet à traiter, qui en l'occurrence sera
      le suivant dans l'arbre XML, à savoir
        <eleve>
          <nom> Tata</nom> <note> 13 </note>
        </eleve>


    2. Idem, avec la seconde règle de tableAt.xsl (1ère variation ci-dessus),
      appliquée à l'élément homologue de Nom_note_1.xml :

          <eleve nom="Toto" note="12"/>

      La chaîne "@nom" dans select="@nom" s'évalue comme
      l'unique attribut de l'eleve en question dont la clef est "nom",
      et l'opérateur xsl:value-of renvoie la chaîne "Toto".

      De même, la chaîne "@note" dans select="@note" s'évalue comme
      l'unique attribut de l'eleve en question dont la clef est "note",
      et l'opérateur xsl:value-of renvoie la chaîne "12".

      Au total on obtient la même contribution en sortie
      (à quelques blancs près, qui sont sans importance pour le navigateur destinataire).
      après quoi on passe au suivant :
          <eleve nom="Tata" note="13"></eleve>


    3. Revenons au début du calcul : l'unique sommet-candidat est le Document.
      Dans nos exemples, la seule règle applicable est donc la première (match = "/").
      Elle engendre d'abord du texte en sortie, à savoir
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <title>Visualisation</title>
      </head>
      <body>
      <h2> Voici le tableau des noms et des notes</h2>
      <table border="2" bgcolor="yellow">
      <tr>
      <th>Nom</th>
      <th>Note</th>
      </tr>

      Après quoi, l'exécution de <xsl:apply-templates /> provoque l'activation du mécanisme récursif :
      la liste des sommets-enfants du sommet courant est empilée sur la pile des sommets-candidats,
      et on passe au premier de cette liste (c-à-d. on essaie toutes les règles sur ce sommet).

    4. Continuons : le sommet initial Document a un seul fils <liste>.
      Aucune règle ne lui est applicable : la première ne s'applique qu'au Document,
      la seconde demande un element <eleve> qui soit fils immédiat d'un element <liste>.
      Par conséquent le mécanisme récursif s'enclenche et tous les enfants de <liste> sont empilés.
      Comme <liste> n'a pas de contenu textuel, rien n'est envoyé en sortie.

    5. Poursuivons : le premier sommet-candidat est celui que nous avons envisagé en i. ci-dessus, à savoir
         <eleve>
          <nom> Toto</nom> <note> 12 </note>
         </eleve>

      La seconde règle est (seule) applicable, puisqu'il s'agit bien d'un <eleve> qui est fils immédiat d'une <liste>.
      Ce qui se produit alors a été décrit en i.

      Après quoi on passe au deuxième fils de <liste>, etc jusqu'à épuisement de la liste.

    6. Achevons : après que le dernier fils de <liste> ait été traité, la pile de sommets créée par le
      <xsl:apply-templates /> de la première règle est épuisée,
      on revient donc à l'exécution de ladite première règle, en séquence, et on envoie en sortie les balises HTML fermantes :
       </table>
      </body>
      </html>
      et le calcul se termine.

  4. Retour sur les règles implicites (ou "par défaut" : default rules)

  5. Variations avec le matériel dont nous disposons

    On obtiendra exactement le même résultat qu'avec la feuille de style table.xsl vue ci-dessus

IV. Rudiments de XPath

  1. XPath

    Les expressions qui figurent comme valeurs des attributs match, select ou test sont rédigées
    dans un langage particulier appelé XPath, dont la syntaxe n'est pas du XML.
    Ce langage a pour but de décrire des ensembles de chemins dans un arbre XML,
    et par suite des ensembles de sommets (ceux qu'on peut atteindre en suivant ces chemins).
    Nous n'en donnerons ici que le strict nécessaire à nos besoins.
    Pour en savoir davantage, voyez Wikipedia (en anglais), ou l'excellent chapitre 9 du livre XML in a Nutshell.

    Il faut prendre garde que nous décrivons ci-dessous les différentes formes d'expressions courantes
    sans distinguer celles qui sont autorisées dans un filtre match de celles qui sont acceptées dans un select
    ou dans un test. Rien ne remplace l'expérience !

    Tous les exemples dans les paragraphes 2, 3, 4 suivants seront pris sur le fichier qr.xml 
    qui décrit l'arbre syntaxique d'un petit programme, et qui fournira plus loin la base d'un de nos exemples.
    Il est suffisamment compliqué pour mettre en œuvre quelques ressources de XPath !

    <?xml version='1.0'?>

    <Prog>
    <Variables> <Var nom="a"/> <Var nom="b"/> <Var nom="q"/> <Var nom="r"/>
    </Variables>
    <Sequence>
    <Lecture> <Var nom="a"/> </Lecture>
    <Lecture> <Var nom="b"/> </Lecture>
    <Affectation> <Var nom="q"/> <Cte val="0"/> </Affectation>
    <Affectation> <Var nom="r"/> <VarExp> <Var nom="a"/> </VarExp> </Affectation>
    <Boucle>
    <Comparaison op="&gt;="> <VarExp> <Var nom="t"/> </VarExp>
    <VarExp> <Var nom="b"/> </VarExp>
    </Comparaison>
    <Sequence>
    <Affectation> <Var nom="q"/>
    <Bin op="+"> <VarExp> <Var nom="q"/> </VarExp> <Cte val="1"/> </Bin>
    </Affectation>
    <Affectation> <Var nom="r"/>
    <Bin op="-"> <VarExp><Var nom="r"/></VarExp>
    <VarExp><Var nom="b"/></VarExp>
    </Bin>
    </Affectation>
    </Sequence>
    </Boucle>
    <Ecriture> <VarExp> <Var nom="q"/> </VarExp> </Ecriture>
    <Ecriture> <VarExp> <Var nom="r"/> </VarExp> </Ecriture>
    </Sequence>
    </Prog>

    On les trouvera tous rassemblés (éventuellement enfermés dans des commentaires pour éviter des incompatibilités)
    en situation de fonctionnement dans la feuille de style XPath.xsl.
  2. Chemins simples

    L'expression de base en XPath désigne un chemin par la suite des noms des nœuds qui le composent,
    séparés par des obliques "/", à la manière exacte des chemins dans l'arbre des répertoires en Unix.

    Il faut bien distinguer

    Exemples :
    1. /Prog/Sequence/Lecture/Var :
      chemin absolu qui désigne les deux variables des deux instructions de lecture au début du programme
      (dont les noms sont "a" et "b").
    2. Sequence/Affectation/Var
      chemin relatif qui désigne deux variables (dont les noms sont "q" et "r"),
      soit à partir de /Prog/Sequence, soit à partir de /Prog/Sequence/Boucle/Sequence

    Comme en Unix,

    Différemment de Unix, on peut terminer un chemin en demandant non pas un sommet-enfant
    mais un attribut :
    1. /Prog/Sequence/Lecture/Var/@nom :
      chemin absolu qui désigne les noms des deux variables des deux instructions de lecture
      au début du programme (à savoir "a" et "b").
    2. Sequence/Affectation/Var/@nom
      chemin relatif qui désigne les noms de deux variables (en l'occurrence, "q" et "r"),
  3. Les prédicats

    On peut insérer dans un chemin des conditions portant sur les sommets du rang considéré.
    Ces conditions s'expriment par des expressions booléennes appelées prédicats (référence à la logique),
    qui sont placées entre crochets.

    N.B. dans ces prédicats, il est fait grand usage de l'égalité qui s'écrit "=" et non "==" comme en Java,
    et de la différence qui s'écrit "!=" comme en Java.
    Les symboles des inégalités numériques "<", ">", "<=" et ">=" posent évidemment un problème typographique
    dans le contexte d'un langage à balises.
    En XSLT il faut les écrire avec des "entités" : "&lt;", "&gt;", "&lt;=" et "&gt;=".

    Exemples :
    1. /Prog/Sequence/Lecture/Var[@nom = 'a']
      sélectionne la variable de la première des deux instructions de lecture au début du programme

    2. Sequence/Affectation/Bin[@op = '+']/VarExp/Var
      sélectionne la variable en partie gauche de la première des deux affectations de la boucle

    3. Sequence/Affectation/Var[@nom = 'q']/../Bin
      choisit l'expression arithmétique en partie droite de l'affectation dont la variable en partie gauche
      s'appelle "q" (il n'y en a qu'une).
  4. Les fonctions

    XPath possède une panoplie de fonctions standard diverses dont voici les plus importantes :
    Il y a aussi un jeu complet d'opérateurs arithmétiques et booléens (ces derniers s'écrivant and, or, not).

    Avec ces fonctions, on peut composer des prédicats élaborés et calculer rapidement
    des résultats intéressants.

    Exemples :
  5. Un exemple très "international".

    1. Le fichier XML : ExInter.xml

      <?xml version="1.0"?>
      <Capitales>
      <France>Paris</France>
      <España>Madrid</España>
      <Česko>Praha</Česko>
      <Україна>Київ</Україна>
      <България>София</България>
      <Ελλάς>Αθήνα</Ελλάς>
      <Россия>Москва</Россия>
      <საქართველო>თბილისი</საქართველო>
      <Հայաստան>Երեւան</Հայաստան>
      <!-- <ኢትዮጵያ>አዲስ አበባ</ኢትዮጵያ> -->
      <भारत>नई दिल्ली</भारत>
      <தமிழ்_நாடு>ென்னை</தமிழ்_நாடு>
      <ประเทศไทย>กรุงเทพฯ</ประเทศไทย>
      <Việt_Nam>Hà Nội</Việt_Nam>
      <中華>北京</中華>
      <日本国>東京</日本国>
      </Capitales>

    2. Le spectacle produit :

      Capitales

    3. La feuille de style : list.xsl
      Même structure d'ensemble que table.xsl et al,
      emploi de la fonction name() et des symboles "*" (tous les enfants) et "." (le sommet courant).

      <?xml version='1.0'?>
      <xsl:stylesheet
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
      <xsl:output method="html" encoding="utf-8" />

      <xsl:template match="/">
      <html><head><title>Visualisation</title></head>
      <body>
      <h2> Voici une liste de pays avec leurs capitales</h2>
      <ul>
      <xsl:apply-templates/>
      </ul>
      </body>
      </html>
      </xsl:template>

      <xsl:template match="Capitales/*">
      <li>
      <xsl:value-of select="name()"/> :
      <xsl:value-of select="."/>
      <!-- le contenu textuel du sommet courant -->
      </li>
      </xsl:template>

      </xsl:stylesheet>

V. Structures de contrôle, règles paramétrées, règles nommées

  1. Pilotage des règles par <xsl:sort.../>

    Normalement, les règles lancées par s'appliquent aux éléments XML dans l'ordre où ils apparaissent
    dans le document, et par conséquent leurs produits figurent dans le même ordre en sortie,
    comme on a pu le voir abondamment dans les exemples précédents.
    Dans de nombreuses situations, typiquement dans le traitement de listes, on souhaite obtenir un résultat trié
    (par ordre alphabétique, par ordre numérique, etc).
    On utilise pour ce faire la construction <xsl:sort ... />.

  2. Structures de contrôle

    Il peut arriver que le parcours normal de l'arbre par <xsl:apply-templates/> s'avère inadéquat,
    [même si on le contrôle par un attribut select : <xsl:apply-templates select="unFiltre"/>
    et même si on gère l'ordre d'application des règles avec <xsl:sort ... /> (voir ci-dessus)]
    ou du moins malcommode à utiliser (p. ex. augmentation démesurée du nombre de règles).
    On peut alors recourir à un jeu de structures de contrôle qui comporte

  3. Appels récursifs explicites

    L'absence de variables et d'instruction d'affectation au sens usuel des langages de programmation impératifs
    limite sévèrement la programmation itérative.
    Les boucles sont indexées par la structure du fichier, non par un compteur géré par le programmeur !
    (Ce n'est pas le test qui manque, cf. ci-dessus, mais bien l'incrémentation du compteur à chaque tour de boucle.)

    Un mot sur la notion de variable en XSLT : il ne s'agit pas d'une variable au sens de C ou de Java,
    dont la valeur est modifiable par affectation au gré du programmeur,
    mais d'un nom pour une valeur, laquelle ne change pas une fois la nomination effectuée
    (comme dans les langages fonctionnels purs).
    Exemple (à suivre) :

    <xsl:variable name="indentElem" select="'   '"/> <!-- 4 espaces -->

    et dans la portée de cette déclaration la valeur de la "variable" est accessible par
    select="$indentElem".

    En revanche, XSLT offre la possibilité d'écrire des fonctions récursives, par un mécanisme de règles nommées.
    Comme les boucles sont équivalentes à des procédures récursives terminales, tout devient possible !
    Bien évidemment, les conditionnelles <xsl:if> et <xsl:choose> vont être mises à contribution
    (voir le § précédent).

  4. Règles paramétrées

    On peut également paramétrer des règles "ordinaires"(non nommées)
    qui seront alors déclenchées par

    <xsl:apply-templates select="leFiltreAdéquat">
        <xsl:with-param name="leParam" select="laValeur"/>
    </xsl:apply-templates>

    On trouvera cette technique mise en œuvre dans le pretty-print en section VI.
  5. L'exemple de la génération des histogrammes

    Toute cette mécanique permet une réalisation impeccable de la génération de l'histogramme
    dans le format demandé par le logiciel XML/SWF Charts à partir du fichier XML des noms et des notes.

    Fichier-donnée XML des noms et des notes : NomsNotes.xml
    (construit par un programme DOM à partir d'un fichier-texte) :

    <?xml version="1.0?>
    <liste >
    <eleve nom="Luc" note="12" />
    <eleve nom="Maurice" note="18" />
    <eleve nom="Juliette" note="07" />
    <eleve nom="Max" note="07" />
    <eleve nom="Pierre" note="08" />
    <eleve nom="Kevin" note="09" />
    <eleve nom="Christine" note="12" />
    <eleve nom="Joseph" note="09" />
    <eleve nom="Elisabeth" note="07" />
    <eleve nom="Elsa" note="09" />
    <eleve nom="Jules" note="11" />
    <eleve nom="Jean-Pierre" note="09" />
    <eleve nom="Franck" note="12" />
    <eleve nom="Francine" note="13" />
    <eleve nom="Aline" note="12" />
    <eleve nom="Jacques" note="09" />
    <eleve nom="Mauricette" note="12" />
    <eleve nom="Alexandre" note="09" />
    <eleve nom="Antoinette" note="12" />
    <eleve nom="Paulette" note="09" />
    <eleve nom="Ernestine" note="18" />
    <eleve nom="Julien" note="13" />
    <eleve nom="Josette" note="19" />
    </liste>

    Fichier de l'histogramme produit par la transformation : hhh.xml
    (XML sans-en-tête au format XML/SWF Charts)

    <chart>
    <chart_data>
    <row>
    <null/>
    <string>0</string>
    <string>1</string>
    <string>2</string>
    <string>3</string>
    <string>4</string>
    <string>5</string>
    <string>6</string>
    <string>7</string>
    <string>8</string>
    <string>9</string>
    <string>10</string>
    <string>11</string>
    <string>12</string>
    <string>13</string>
    <string>14</string>
    <string>15</string>
    <string>16</string>
    <string>17</string>
    <string>18</string>
    <string>19</string>
    <string>20</string>
    </row>
    <row>
    <null/>
    <number>0</number>
    <number>0</number>
    <number>0</number>
    <number>0</number>
    <number>0</number>
    <number>0</number>
    <number>0</number>
    <number>3</number>
    <number>1</number>
    <number>7</number>
    <number>0</number>
    <number>1</number>
    <number>6</number>
    <number>2</number>
    <number>0</number>
    <number>0</number>
    <number>0</number>
    <number>0</number>
    <number>2</number>
    <number>1</number>
    <number>0</number>
    </row>
    </chart_data>
    </chart>


    Sa visualisation par XML/SWF Charts

    Histogramme


    La feuille de style Xml2Histo.xsl :
    comme il s'agit de produire du XML et non plus du HTML,
    au lieu d'envoyer du texte en sortie on produit explicitement les éléments XML visés,
    par la constructions <xsl:element> (il existe aussi <xsl:attribute>, <xsl:text>, etc).
    On note l'emploi décisif de <xsl:value-of select="count(//eleve[@note=$nbase])"/>
    pour obtenir d'un seul coup le nombre de notes égal à la valeur $nbase considérée

    <?xml version='1.0'?>
    <xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
    <xsl:output method="xml"
    omit-xml-declaration="yes" indent="yes" encoding="utf-8" />

    <xsl:variable name="noteMax" select="'20'" />

    <xsl:strip-space elements="*" />

    <xsl:template name="Base">
    <!-- Réalisation de l'axe des abscisses, de 0 à 20
    c'est le premier élément <row> de l'histogramme -->
    <xsl:param name="nbase"/>
    <xsl:if test="$nbase &lt;= $noteMax">
    <xsl:element name="string">
    <xsl:value-of select="$nbase"/>
    </xsl:element>
    <xsl:call-template name="Base"> <!-- récursion terminale -->
    <xsl:with-param name="nbase" select="$nbase+1"/>
    </xsl:call-template>
    </xsl:if>
    </xsl:template>

    <xsl:template name="Val">
    <!-- Réalisation de l'axe des ordonnées,
    c'est le second élément <row> de l'histogramme -->
    <xsl:param name="nbase"/>
    <xsl:if test="$nbase &lt;= $noteMax">
    <xsl:element name="number">
    <xsl:value-of select="count(//eleve[@note=$nbase])"/>
    </xsl:element>
    <xsl:call-template name="Val"> <!-- récursion terminale -->
    <xsl:with-param name="nbase" select="$nbase+1"/>
    </xsl:call-template>
    </xsl:if>
    </xsl:template>

    <xsl:template match="/">
    <xsl:element name="chart">
    <xsl:element name="chart_data">
    <xsl:element name="row">
    <xsl:element name="null"/>
    <xsl:call-template name="Base">
    <xsl:with-param name="nbase" select="0"/>
    </xsl:call-template>
    </xsl:element>
    <xsl:element name="row">
    <xsl:element name="null"/>
    <xsl:call-template name="Val">
    <xsl:with-param name="nbase" select="0"/>
    </xsl:call-template>
    </xsl:element>
    </xsl:element>
    </xsl:element>
    </xsl:template>

    </xsl:stylesheet>



VI. Un exemple (relativement) complet

  1. But

    Il s'agit de réaliser avec XSLT le pretty-print de petits programmes comme qr.xml que nous avons vu plus haut.
    On souhaite pouvoir le faire en mode texte (ici, sous Unix en ligne de commande ):

    jfp% xsltproc qr.xml
    VAR a; b; q; r; LIRE a ;
    LIRE b ;
    q := 0 ;
    r := a ;
    TANTQUE t &gt;= b FAIRE
    q := q + 1 ;
    r := r - b
    FINTQ ;
    ECRIRE q ;
    ECRIRE r
    .
    jfp%


    On souhaite en outre pouvoir faire apparaître le programme dans une page Web, comme de juste.

    qr

    Voyez un exemple plus complet, avec un programme un peu plus compliqué : qrd.xml.
  2. Principe

    L'opération se fait en composant 3 feuilles de style.
    En effet, le travail principal consiste à effectuer le pretty-print en mode texte.
    Dans cette tâche, on distingue le travail particulièrement délicat de l'impression des expressions
    arithmétiques et booléennes avec un parenthésage minimum, compte-tenu des règles traditionnelles
    de précédence entre opérateurs [on écrit "2 * x + 1" et non pas "((2 * x) + 1)"].
    L'impression des expressions est donc effectuée par une feuille de style indépendante
    ExpAr/ExpArBoolMIL2ap.xsl, qui est importée par la feuille principale MIL/PrettyMIL.xsl.
    L'insertion du texte ainsi produit dans une page Web (grâce à la balise HTML <pre>)
    est effectuée par une troisième feuille de style HtmlMIL.xsl, qui importe à son tour PrettyMIL.xsl.

  3. La feuille de style finale HtmlMIL.xsl

    Cette feuille de style se borne à mettre en scène le produit de PrettyMIL.xsl dans un "cadre HTML".
    Par conséquent elle proclame <xsl:output method="html" ... />,
    au contraire des deux autres qui sont neutres vis-à-vis de la méthode de sortie, pour pouvoir être employées
    dans toutes les situations.


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

    <xsl:output method="html"
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" encoding="utf-8"
    indent="yes"/>

    <xsl:include href="PrettyMIL.xsl"/>

    <xsl:template match="/">
    <html>
    <head>
    <title>Programme MIL</title>
    </head>
    <body>
    <pre>
    <xsl:apply-templates/>
    </pre>
    </body>
    </html>
    </xsl:template>

    </xsl:stylesheet>

  4. La feuille de style principale PrettyMIL.xsl

    C'est elle qui fait le gros du travail : le traitement des instructions.
    Les expressions forment un sous-langage à part, où les questions d'indentation n'entrent pas.
    La syntaxe à précédences dans les expressions arithmétiques offre suffisamment de difficultés
    pour qu'on sépare le traitement des expressions de celui des instructions,
    en le confiant à une feuille de style séparée.
    On peut ainsi mettre au point les deux feuilles séparément, par exemple en faisant fonctionner PrettyMIL
    avec un imprimeur d'expressions en notation complètement parenthésée, beaucoup plus facile à réaliser.

    <?xml version='1.0'?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
    <xsl:output omit-xml-declaration="yes"/> <!-- non committal -->

    <!-- Pretty MIL (format XML version ISTM-2007)
    avec indentations et importation des ExpAr -->

    <xsl:strip-space elements="*" />

    <xsl:include href="../ExpAr/ExpArBoolMIL2ap.xsl"/>

    <xsl:variable name="indentElem" select="' '"/>

    <xsl:template name="indent">
    <!-- en l'absence d'effets de bord, la boucle habituelle se réalise
    avec une récursion terminale -->
    <xsl:param name="prof"/>
    <xsl:if test="$prof>0">
    <xsl:value-of select="$indentElem"/>
    <xsl:call-template name="indent">
    <xsl:with-param name="prof" select="$prof - 1"/>
    </xsl:call-template>
    </xsl:if>
    </xsl:template>

    <xsl:template match="/Prog">
    <xsl:apply-templates select="Variables"/> <!-- marque explicitement l'ordre -->
    <xsl:apply-templates select="Sequence">
    <xsl:with-param name="prof" select="0"/>
    </xsl:apply-templates>
    <xsl:text>.</xsl:text> <!-- le point final -->
    </xsl:template>

    <xsl:template match="Variables">
    <xsl:text>VAR&#32;</xsl:text> <!-- ASCII-32 = espace -->
    <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="Variables/Var[position() != last()]">
    <xsl:value-of select="@nom"/>
    <xsl:text>; </xsl:text>
    </xsl:template>

    <xsl:template match="Variables/Var[position() = last()]">
    <xsl:value-of select="@nom"/>
    <xsl:text>.&#10;</xsl:text>
    </xsl:template>

    <xsl:template match="Sequence">
    <xsl:param name="prof"/>

    <!-- l'emploi d'une itération évite de dédoubler toute la collection
    des règles relatives aux différentes instructions
    -->
    <xsl:for-each select="*"> <!-- tous les enfants, dans l'ordre -->
    <xsl:call-template name="indent">
    <xsl:with-param name="prof" select="$prof"/>
    </xsl:call-template>
    <xsl:apply-templates select="."> <!-- chaque enfant -->
    <xsl:with-param name="prof" select="$prof"/>
    </xsl:apply-templates>
    <xsl:if test="position()&lt;last()"> <!-- l'entité est obligatoire en XSLT -->
    <xsl:text> ;&#10;</xsl:text> <!-- point-virgule et ASCII-10 = LF -->
    </xsl:if>
    <xsl:if test="position()=last()">
    <xsl:text>&#10;</xsl:text> <!-- LF sans point-virgule -->
    </xsl:if>
    </xsl:for-each>
    </xsl:template>

    <xsl:template match="Espace">
    <xsl:text>ESP</xsl:text>
    </xsl:template>

    <xsl:template match="Ligne">
    <xsl:text>LIG</xsl:text>
    </xsl:template>

    <xsl:template match="Lecture">
    <xsl:text>LIRE&#32;</xsl:text>
    <xsl:value-of select="Var/@nom"/>
    </xsl:template>

    <xsl:template match="Ecriture">
    <xsl:text>ECRIRE&#32;</xsl:text>
    <xsl:apply-templates select="*"/>
    </xsl:template>

    <xsl:template match="Affectation">
    <xsl:value-of select="Var/@nom"/>
    <xsl:text>&#32;:=&#32;</xsl:text>
    <xsl:apply-templates select="*"/>
    </xsl:template>

    <xsl:template match="Boucle">
    <xsl:param name="prof"/>

    <xsl:text>TANTQUE&#32;</xsl:text>
    <xsl:apply-templates select="Comparaison"/>
    <xsl:text>&#32;FAIRE&#10;</xsl:text>
    <xsl:apply-templates select="Sequence">
    <xsl:with-param name="prof" select="$prof + 1"/>
    </xsl:apply-templates>

    <xsl:call-template name="indent">
    <xsl:with-param name="prof" select="$prof"/>
    </xsl:call-template>
    <xsl:text>FINTQ</xsl:text>
    </xsl:template>

    <xsl:template match="Conditionnelle">
    <xsl:param name="prof"/>

    <xsl:text>SI&#32;</xsl:text>
    <xsl:apply-templates select="Comparaison"/>
    <xsl:text>&#32;ALORS&#10;</xsl:text>
    <!-- voir ExpAr/Bin : il ya deux fils "Sequence" à distinguer ! -->
    <xsl:apply-templates select="./*[position()=2]">
    <xsl:with-param name="prof" select="$prof + 1"/>
    </xsl:apply-templates>

    <xsl:call-template name="indent">
    <xsl:with-param name="prof" select="$prof"/>
    </xsl:call-template>
    <xsl:text>SINON&#10;</xsl:text>
    <xsl:apply-templates select="./*[position()=3]">
    <xsl:with-param name="prof" select="$prof + 1"/>
    </xsl:apply-templates>

    <xsl:call-template name="indent">
    <xsl:with-param name="prof" select="$prof"/>
    </xsl:call-template>
    <xsl:text>FINSI</xsl:text>
    </xsl:template>

    <!-- ======== Expressions arithmétiques & booléennes : importées ======== -->

    <!-- et voila tout ! -->
    </xsl:stylesheet>


  5. La feuille spéciale pour les expressions ExpArBoolMIL2ap.xsl

    Pour illustrer le propos relatif à la division entre instructions et expressions, voici d'abord
    une feuille de style réalisant l'impression des expressions en syntaxe complètement parenthésée,
    utile pour faire des essais.

    <?xml version='1.0'?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
    <xsl:output omit-xml-declaration="yes"/> <!-- non committal -->

    <xsl:strip-space elements="*" />

    <!-- ======== Expressions arithmétiques & booléennes (version 2007) ========

    munies d'une racine postiche "ExpAr" pour essais.
    Nécessité de préciser quels éléments-fils sont visés par apply-templates
    sous peine de voir répercuter les blancs précédants,
    à cause des règles par défaut !
    De même, <xsl:text>(</xsl:text> vaut mieux que '(' sec,
    car le saut de ligne suivant vient avec !
    -->

    <xsl:template match="/ExpAr">
    <!--Marqueur sans valeur sémantique, appel récursif global -->
    <xsl:apply-templates/>
    </xsl:template>

    <!-- Traitement différencié des trois sortes d'expressions -->

    <xsl:template match="Bin"> <!-- Le cas intéressant ! -->
    <xsl:text>(</xsl:text>
    <!-- On ne peut pas demander simplement "Bin|Cte|VarExp", car
    alors les DEUX opérandes seraient traités simultanément !!!
    Il faut absolument les distinguer par position -->
    <xsl:apply-templates select="./*[1]"/>
    <!-- on écrit l'opérateur, entre deux blancs -->
    <xsl:value-of select="concat('&#32;', @op, '&#32;')"/>
    <!-- appel récursif global sur l'opérande droit -->
    <xsl:apply-templates select="./*[2]"/>
    <!-- enfin on écrit la parenthèse fermante -->
    <xsl:text>)</xsl:text>
    </xsl:template>

    <xsl:template match="Cte">
    <xsl:value-of select="@val"/>
    </xsl:template>

    <xsl:template match="VarExp">
    <xsl:value-of select="Var/@nom"/>
    </xsl:template>

    <xsl:template match="Comparaison">
    <!-- sur le même modèle que Bin -->
    <xsl:apply-templates select="./*[1]"/>
    <xsl:value-of select="concat('&#32;', @op, '&#32;')"/>
    <xsl:apply-templates select="./*[2]"/>
    </xsl:template>

    <!-- et voila tout ! -->
    </xsl:stylesheet>

    Et voici la version officielle, avec le parenthésage minimal
    compte-tenu des précédences entre opérateurs.

    <?xml version='1.0'?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
    <xsl:output omit-xml-declaration="yes" encoding="utf-8" />

    <!-- ======== Expressions arithmétiques & booléennes (version 2007) ========
    Une réalisation de l'impression en syntaxe à précédences (2 niveaux)
    -->

    <xsl:strip-space elements="*" />

    <xsl:template match="/ExpAr">
    <!--Marqueur sans valeur sémantique, appel récursif global -->
    <xsl:apply-templates/>
    </xsl:template>

    <!-- L'impossibilité de définir une fonction de précédence
    conduit à traiter séparément les 2 classes d'opérateurs -->

    <xsl:template match="Bin[@op = '+' or @op = '-']">
    <!-- on ne parenthèse pas l'opérande gauche
    (association à gauche ou précédence) -->
    <xsl:apply-templates select="./*[1]"/>
    <!-- on écrit l'opérateur -->
    <xsl:value-of select="concat('&#32;', @op, '&#32;')"/>
    <!-- on parenthèse l'op. droit sauf si précédence -->
    <xsl:choose>
    <xsl:when test="./*[position()=2 and (@op='+' or @op='-')]">
    <xsl:text>(</xsl:text>
    <xsl:apply-templates select="./*[2]"/>
    <xsl:text>)</xsl:text>
    </xsl:when>
    <xsl:otherwise>
    <xsl:apply-templates select="./*[2]"/>
    </xsl:otherwise>
    </xsl:choose>
    </xsl:template>

    <xsl:template match="Bin[@op = '*' or @op = '/']">
    <!-- on parenthèse l'opérande gauche si précédence) -->
    <xsl:choose>
    <xsl:when test="./*[position()=1 and (@op='+' or @op='-')]">
    <xsl:text>(</xsl:text>
    <xsl:apply-templates select="./*[1]"/>
    <xsl:text>)</xsl:text>
    </xsl:when>
    <xsl:otherwise>
    <xsl:apply-templates select="./*[1]"/>
    </xsl:otherwise>
    </xsl:choose>
    <!-- on écrit l'opérateur -->
    <xsl:value-of select="concat('&#32;', @op, '&#32;')"/>
    <!-- on parenthèse l'op. droit dans tous les cas Bin -->
    <xsl:choose>
    <xsl:when test="./*[position()=2 and name()='Bin']">
    <xsl:text>(</xsl:text>
    <xsl:apply-templates select="./*[2]"/>
    <xsl:text>)</xsl:text>
    </xsl:when>
    <xsl:otherwise>
    <xsl:apply-templates select="./*[2]"/>
    </xsl:otherwise>
    </xsl:choose>
    </xsl:template>

    <xsl:template match="Cte">
    <xsl:value-of select="@val"/>
    </xsl:template>

    <xsl:template match="VarExp">
    <xsl:value-of select="Var/@nom"/>
    </xsl:template>

    <xsl:template match="Comparaison">
    <!-- sur le même modèle que Bin -->
    <xsl:apply-templates select="./*[position()=1]"/>
    <xsl:value-of select="concat('&#32;', @op, '&#32;')"/>
    <xsl:apply-templates select="./*[position()=2]"/>
    </xsl:template>

    <!-- et voila tout ! -->
    </xsl:stylesheet>