TD XML, XSLT
1
COURS
Nous allons
d’abord travailler sur le fichier
coursXML.xml qui est :
6 3 2 ENSEIGNANTS SUJET
<?xml
version="1.0" encoding="ISO-8859-1"?>
<COURS
CODE="TC234">
PROGRAMME
<SUJET>Publication
XSLT</SUJET>
<ENSEIGNANTS>
<!-- Enseignant responsable -->
8 7 5 4 Publication XSLT
<NOM>Amann</NOM>
SEANCE SEANCE NOM NOM
<NOM>Rigaux</NOM>
</ENSEIGNANTS>
<PROGRAMME>
Rigaux Amann
<SEANCE
ID="1">Documents XML</SEANCE>
ID=2 ID=1
<SEANCE ID="2">Programmation XSLT</SEANCE>
<ANNEE>2002</ANNEE>
</PROGRAMME>
</COURS>
q Un fichier XSLT qui affiche le
contenu du document
<?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"
version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template
match="/">
<xsl:value-of select="/"/>
</xsl:template>
</xsl:stylesheet>
La première partie de
la règle sélectionne un nœud et la deuxième affiche le contenu du nœud.
q Un fichier XSLT qui récupère les
noms des enseignants :
q Première tentative :
<?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"
version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template
match="/">
<xsl:value-of select="COURS/ENSEIGNANTS/NOM"/>
</xsl:template>
</xsl:stylesheet>
A l’exécution, ce fichier ne retourne qu’un seul nom. Essayons de comprendre pourquoi. Ce fichier contient une règle qui est déclenchée dès qu’on trouve un nœud associé au template. La règle est donc déclenchée pour un seul nœud (donc déclenchée une seule fois).
L’action de la règle est elle aussi
déclenchée une seule fois. L’exécution de l’action consiste à chercher
un nœud à partir du nœud contexte (racine) un nœud qui satisfait l’expression
de chemin (s’y trouvant au bout) COURS/ENSEIGNANT/NOM et en récupérer la valeur
associée. Comme l’action n’est exécutée qu’une seule fois, seule la valeur du
premier nœud trouvé sera retournée (ça sera Amann)
2.
Deuxième tentative :
<?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" version="1.0" encoding="UTF-8"
indent="yes"/>
<xsl:template match="/COURS">
<xsl:value-of
select="ENSEIGNANTS/NOM"/>
</xsl:template>
</xsl:stylesheet>
Pour les mêmes
raisons que précédemment, la règle n’est déclenchée qu’une seule fois donc un
seule nom sera retournée.
3.
Troisième tentative :
<?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" version="1.0" encoding="UTF-8"
indent="yes"/>
< !-- règle 1 -->
<xsl:template
match="/">
<xsl:apply-templates select="//NOM"/>
</xsl:template>
<!-- règle 2 -->
<xsl:template
match="NOM">
<xsl:value-of
select="."/>
</xsl:template>
</xsl:stylesheet>
Dans ce fichier, nous avons deux
règles. La première sélectionne un nœud : le nœud racine (1). Son action
consiste à appliquer (apply-templates) à chaque nœud du type NOM (4 et
5). Le système va donc chercher les templates dans le texte dont la partie
match satisfait les nœuds sélectionnés.
Le système prend le premier nœud (i.e 4) et vérifie s’il est du type NOM
(condition de la règle 2). C’est le cas, donc la règle est déclenchée et son
action est exécutée. Le même raisonnement tient pour le nœud 5.
Remarques :
§ Dans la deuxième règle, si dans la
partie match, on avait ENSEIGNANTS/NOM le résulat aurait été le même.
§ Dans la deuxième règle, si on
avait mis dans la partie match juste le caractère * (qui veut dire, n’importe quoi,
le résultat aurait été aussi le même.
q
Dupliquer la règle 2 et vérifier le résultat. Les noms ne sont pas
affichés deux fois. La raison est que les processeurs XSLT, pour un nœud donné,
appliquent une seule règle parmi
celles qu’ils trouvent applicables. Il est possible de définir des priorités
entre les règles, mais ceci est une autre histoire …
q
Tester quand même ces deux dernières alternatives après avoir ajouté des
éléments NOM aux séances.
q
Maintenant, on veut afficher le nom du deuxième auteur.
<?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" version="1.0" encoding="UTF-8"
indent="yes"/>
<!--
règle 1-->
<xsl:template
match="/">
<xsl:apply-templates select="//NOM[position()=2]"/>
</xsl:template>
<!--
règle2-->
<xsl:template
match="NOM">
<xsl:value-of
select="."/>
</xsl:template>
</xsl:stylesheet>
q Générer un fichier
XML qui est de la forme
<Noms><Nom>Amann</Nom></Nom>Rigaux</Nom></Noms>.
<?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"
version="1.0" encoding="UTF-8" indent="yes"/>
<!-- règle 1-->
<xsl:template
match="/">
<NOMS>
<xsl:apply-templates select="//NOM"/>
</NOMS>
</xsl:template>
<!-- règle2-->
<xsl:template
match="NOM">
<NOM>
<xsl:value-of
select="."/>
</NOM>
</xsl:template>
</xsl:stylesheet>
q Compter le nombre
de fils que le nœud ENSEIGNANTS a.
<xsl:template
match="/">
<xsl:apply-templates
select="COURS/ENSEIGNANTS"/>
</xsl:template>
<xsl:template
match="ENSEIGNANTS">
<xsl:value-of
select="count(./*)"/>
</xsl:template>
q Toujours avec
l’exemple précédent. On veut maintenant parcourir toutes les séances de cours,
afficher pour chacune son numéro, le nombre total de séances et l’intitulé de
la séance.
<?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"/>
<xsl:template
match="/">
<PROGRAMME>
<xsl:apply-templates
select="//PROGRAMME"/> ßOn sélectionne
un nœud PROGRAMME
</PROGRAMME>
</xsl:template>
<xsl:template
match="PROGRAMME">
Programme:
<SEANCES>
<xsl:for-each
select="SEANCE"> ß pour chaque séance trouvée (à partir du nœud PROGRAMME)
Séance <xsl:value-of select="concat(position(), '/',
last())"/> : ß le nœud courant ici est un
nœud SEANCE
<xsl:value-of select="."/>
</xsl:for-each>
</SEANCES>
Valable
pour l'année <xsl:value-of
select="ANNEE"/> ß le nœud courant ici est le
nœud PROGRAMME
</xsl:template>
</xsl:stylesheet>
Question : Est-ce qu’on
aurait pu se passer de la boucle for-each en
faisant des appels récursifs (combinaisons de template
et apply-templates) ?
q
XSLT
permet l’utilisation de variables. Les différentes manières de les déclarer
sont illustrées par les exemples suivants
<xsl :variable
name=’v1’ select=’12’/> ß définit
une variable v1 et initialise sa valeur à 12
<xsl :variable
name=’v2’ select=’/COURS/ENSEIGNANTS’/> ß définit une variable v2. Sa valeur est l’ensemble des
nœuds de type ENSEIGNANTS
<xsl :variable
name=’v3’> Ceci est <mot-cle>le
contenu </mot-cle> de v3 </xsl :variable>
Pour chaque film, on veut afficher son titre et un message
disant qu’il est récent ou ancien selon qu’il soit sorti avant ou après 1970
(fichiers VarGlobal.xsl et ListeFilms.xml)
<?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"/>
<xsl:variable name="annee" select="1970"/> ß déclaration de la
variable.
<xsl:template
match="FILMS">
<xsl:apply-templates
select="FILM"/>
</xsl:template>
<xsl:template
match="FILM">
<xsl:choose> ß
c’est l’équivalent du case of des langages de programmation
<xsl:when test="ANNEE <
$annee"> ß
on teste si la valeur courante du nœud ANNEE est inférieur à la valeur de la
variable
"<xsl:value-of
select="TITRE"/>" est ancien
</xsl:when>
<xsl:when
test="ANNEE >= $annee">
"<xsl:value-of select="TITRE"/>" est récent
</xsl:when>
<xsl:otherwise> ß si aucune des deux conditions précédentes n’est vérifiée
De
quand date "<xsl:value-of select="TITRE"/>" ?
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
o
En vous inspirant du document précédent, écrire un fichier
XSLT qui permet d’afficher le titre de chacun des films ainsi qu’un message
disant s’il est plus ancien, plus récent ou contemporain du film Alien. Puis affiche le nombre de films plus anciens et plus
récents que ce film.
q
Soit le fichier Notes.xml . On veut afficher la moyenne des
notes de chaque film dans un tableau HTML.
<?xml version="1.0"
encoding="ISO-8859-1"?>
<FILMS>
<FILM>
<TITRE>Vertigo</TITRE>
<NOTES>
<NOTE>3</NOTE>
<NOTE>3</NOTE>
<NOTE>3</NOTE>
<NOTE>5</NOTE>
</NOTES>
</FILM>
<FILM>
<TITRE>Alien</TITRE>
<NOTES>
<NOTE>4</NOTE>
<NOTE>3</NOTE>
<NOTE>5</NOTE>
</NOTES>
</FILM>
<FILM>
<TITRE>Titanic</TITRE>
<NOTES>
<NOTE>2</NOTE>
<NOTE>2</NOTE>
<NOTE>4</NOTE>
<NOTE>1</NOTE>
</NOTES>
</FILM>
<FILM>
<TITRE>Sleepy Hollow</TITRE>
<NOTES>
<NOTE>2</NOTE>
<NOTE>4</NOTE>
<NOTE>4</NOTE>
</NOTES>
</FILM>
<FILM>
<TITRE>American Beauty</TITRE>
<NOTES>
<NOTE>5</NOTE>
<NOTE>4</NOTE>
<NOTE>4</NOTE>
</NOTES>
</FILM>
<FILM>
<TITRE>Impitoyable</TITRE>
<NOTES>
<NOTE>2</NOTE>
<NOTE>1</NOTE>
<NOTE>5</NOTE>
</NOTES>
</FILM>
</FILMS>
L’idée
consiste donc à parcourir la liste des notes de chaque film, en faire la
somme puis diviser cette somme par le nombre de notes.
Le
pseudo-code suivant décrit la fonction :
Fonction
somme (liste l)
Début
Si (l n’est pas vide) alors
Retourner l[1] + somme(l[2..n])
Sinon
Retourner 0
Fin
Voici la
portion XLT qui permet de la simuler
<xsl:template
name="CalculTotal"> ß
nom de la règle
<xsl:param name="listeNotes"/>
ß déclaration d’un paramètre
<xsl:choose>
<xsl:when test="$listeNotes"> ß
on teste si la liste des notes n’est pas vide
<xsl:variable name="note"
select="$listeNotes[1]"/> ß
on affecte la première note
<xsl:variable name="autresNotes">
ß on déclare une variable
<xsl:call-template name="CalculTotal"> ß
appel de la règle
<xsl:with-param name="listeNotes"
select="$listeNotes[position() != 1]"/>
ß on sélectionne toutes les notes
sauf la 1ère
</xsl:call-template>
</xsl:variable>
<xsl:value-of
select="$note + $autresNotes"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
l’appel à
la fonction se fait à l’intérieur d’une règle. On définit d’abord la première
règle qui construit la table et qui sélectionne les nœuds FILM
<xsl:template
match="FILMS">
<html>
<head><title>Statistiques</title></head>
<body
bgcolor="white">
<center>
<h1>Moyenne
des notes pour chaque film</h1>
<table
border='1'>
<xsl:apply-templates select="FILM"/> ß sélection
des nœuds FILM
</table>
</center>
</body>
</html>
</xsl:template>
Ensuite on
définit une 2ème règle qui utilise chacun de ces nœuds pour calculer
la moyenne des notes
<xsl:template
match="FILM">
<xsl:variable
name="total">
<xsl:call-template
name="CalculTotal">
ß appel à la règle « CalculTotal »
<xsl:with-param name="listeNotes"
select="NOTES/NOTE"/>
ß la liste des notes est affectée à listeNotes
</xsl:call-template>
</xsl:variable>
<tr>
<td><b><xsl:value-of
select="TITRE"/></b></td>
<td align="right">
<xsl:value-of select="$total div
count(NOTES/NOTE)"/>
</td>
</tr>
</xsl:template>
Après la définition de ces deux règles, on
reprend la 3ème règle qui fait le calcul de la fonction. Vous pouvez
reprendre le fichier BoucleNotes.xsl
q
Considérons maintenant le fichier
suivant (FilmsGroup.xml):
<?xml version="1.0"
encoding="ISO-8859-1"?>
<FILMS>
<FILM Annee="1997">
<TITRE>Titanic</TITRE>
<GENRE>Drame</GENRE>
<PAYS>
</FILM>
<FILM Annee="1999">
<TITRE>Sleepy Hollow</TITRE>
<GENRE>Fantastique</GENRE>
<PAYS>
</FILM>
<FILM Annee="1999">
<TITRE>American Beauty</TITRE>
<GENRE>Comédie</GENRE>
<PAYS>
</FILM>
<FILM Annee="1992">
<TITRE>Impitoyable</TITRE>
<GENRE>Western</GENRE>
<PAYS>
</FILM>
<FILM Annee="1997">
<TITRE>Volte/Face</TITRE>
<GENRE>Action</GENRE>
<PAYS>
</FILM>
<FILM Annee="1995">
<TITRE>Seven</TITRE>
<GENRE>Policier</GENRE>
<PAYS>
</FILM>
<FILM Annee="1995">
<TITRE>L'armée des douze singes</TITRE>
<GENRE>Science-fiction</GENRE>
<PAYS>USA</PAYS>
</FILM>
</FILMS>
On veut afficher pour chaque année, l’ensemble des titres
de films. Il s’agit donc de faire un regroupement a
Ayant
résolu ce problème, et en supposant que l’année courante soit stockée dans une
variable $anneeCourante, il est facile d’obtenir les
titres et genres de film avec
<ol>
<xsl:for-each select="//FILM[@Annee=$anneeCourante]">
<li>
<xsl:value-of select="concat(TITRE, ', ', GENRE)"/>
</li>
</xsl:for-each>
</ol>
Le principe
de l’élimination des doublons d’une liste consiste à trier cette liste ensuite
ne garder un élément que s’il est différent de son prédécesseur
Fonction eliminationDoublon(Liste l)
Début
Trier l
Garder l[1]
Pour i=2 à longueur(l) Faire
Si l[i] est différent de
l[i-1] Alors
Garder l[i]
FinSi
FinPour
Fin
L’idée
consiste donc à récupérer la liste des films triée sur l’année. Pour chacun,
n’afficher son année que si c’est la première fois qu’on la rencontre. Ceci
peut se faire par :
<xsl:for-each select="FILM">
<xsl:sort select="@Annee"/> ß Cette structure permet de parcourir les FILM selon
l’ordre croissant de l’année
A chaque
itération, on récupère l’année courante dans la variable $anneeCourante :
<xsl:variable name="anneeCourante" select="@Annee"/>
On vérifie
ensuite que c’est la première fois que l’on rencontre cette année :
<xsl:if test="not(preceding-sibling::FILM[@Annee=$anneeCourante])">
ß si le frère précédent qui est du type FILM n’a pas une année qui est égale à l’année courante
Si le test est concluant, on affiche cette année ainsi que le titre et le genre de tous les films de cette même année.
Vous pouvez voir le fichier GroupVariable.xsl
q En vous inspirant de ce qui précède, donner le nombre de genres distincts qu’il y a ?