JeuWeb - Crée ton jeu par navigateur
Article XSL - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Article XSL (/showthread.php?tid=8217)



XSL - Xenos - 14-09-2020

XSL

Première approche

Aide concernant cette partie du tuto

Cela fait plusieurs fois que j'interviens sur le forum en parlant de XML et de XSLT.

Apparemment, peu de gens connaissent et ou utilisent cette technologie (pourtant validée par le W3C fin 1999).
Je vais donc essayer de vous présenter un peu mieux ce langage en m'appuyant sur les quelques connaissances que je possède.

XSLT, c'est quoi

XSLT veut dire eXtensible Stylesheet Language Transformations.
C'est un langage fonctionnel de transformation XML, les documents XSLT étant eux même des documents XML.

Basiquement, XSLT permet de mettre en forme des documents XML.
En général, le résultat donnera un document XHTML, affichable dans un navigateur.

XSLT inclus également un petit language d'interrogation des fichiers XML nommé XPath. Xpath permet d'extraire certaines parties du document XML et de faire des traitements dessus via XSLT.

Voila pour les quelques définitions.
(Il est conseillé à ceux qui ont déja attrapé mal à la tête de prendre une aspirine avant de continuer)

De quoi ai-je besoin pour faire du XSLT

A la base, un simple éditeur de texte et un navigateur capable de faire des transformations XSL suffit.
Avec ça, vous pourrez commencer à expérimenter le XSL.

Les avantages du XSL

Séparation des données et du rendu

XSL prend en entrée des données brutes au format XML et les mets en page.
Plus besoin de modifier son code PHP (avec les risques que cela implique) lorsque l'on souhaite réorganiser les données sur une page. En modifiant le XSL, on peut modifier toute la mise en page sans toucher au reste.

Moins gourmand en bande passante

Les fichiers XSL se mettent naturellement en cache sur les postes clients (comme les fichiers js ou css).
Avec un analyseur de flux HTTP, c'est flagrant.
J'avais une page qui pesait 36Ko générée à partir de PHP seul.
En mettant en place ma procédure XML/XSL, j'en suis arrivé à un fichier XSL de 30Ko et un fichier XML généré par PHP de 18 Ko.
Lorsque la page est chargée la première fois, le serveur envoie 30Ko + 18Ko soit 48Ko.
Si je réactualise la page, je ne charge plus que la partie dynamique (le XML généré par PHP) pour un total de 18Ko seulement car le fichier XSL est en cache. Sur des pages accédées souvent, le gain en bande passante est impressionnant au final.

Moins de traitements effectués par PHP

Avantage non négligeable. Plus besoin de travailler la mise en page dans le code PHP. Que ce soit directement ou par le biais d'un moteur de template, toute la partie affichage est gérée par le client. C'est autant de puissance économisée et utilisable pour autre chose. De plus, XSL est capable de faire des boucles, des tests, des tris sur les fichiers XML. Ce sont autant de traîtements qui pourront soulager votre serveur PHP.

Les Templates

XSL utilise un système de templates assez intéressant.
Le premier type de template permet d'appliquer des transformations XSL sur certains noeuds du document XML.
Le deuxième type de template ressemble à s'y méprendre à un appel de fonction (avec ou sans paramètres évidemment) ce qui est inestimable pour afficher des éléments qui se retrouvent souvent sur le site (entêtes/pieds de page/menus/…). Utilisé avec la capacité d'import/inclusion de fichiers externes XSL, cela permet une grande modularité.

Les désavantages du XSL

Evidemment, il y en a.

Apprendre un nouveau "langage"

Etape obligatoire, pour faire du XSL, il faut apprendre certaines choses. Les instructions XSL et XPath sont incontournables.

Plus de travail

Si la tâche est simplifiée pour la partie PHP, il faut mettre en place la couche XSL.
Cela implique plus de fichiers à gérer et plus d'espace disque consommé.

Pas de variables dynamiques

Même si XSL possède un système de variables, il n'a pas dans sa version 1.0 la possibilité de faire des variables dynamiques (a=a+1). Jusque là, cela ne m'a pas dérangé. Il existe cependant un moyen de simuler ce genre de comportement avec quelques limitations.

Navigateurs supportant XSL

Les navigateurs de dernière génération ont de grandes chances de supporter XSLT 1.0 sans soucis. Cela inclus
  • Internet Explorer 7+

  • Firefox 2+

  • Opera 9.2 +

  • Safari 3.2+


Apprendre par l'exemple

Un exemple étant souvent bien plus efficace que de longues explications, entrons directement dans le vif du sujet.

Nous allons partir d'un petit fichier XML basique, non optimisé, contenant quelques informations.
On compliquera par la suite.
Le fichier contient la description d'une troupe d'aventuriers avec l'ID du joueur, son nom, sa classe, sa race et ses compétences.

Voici un exemple pour un joueur unique
<joueur>
<id>4</id>
<nom>Altinae</nom>
<classe>Voleur</classe>
<race>Humain</race>
<competences>
<competence>
<nom>Crochetage</nom>
<niveau>2</niveau>
</competence>
<competence>
<nom>Pièges</nom>
<niveau>3</niveau>
</competence>
<competence>
<nom>Epée Courte</nom>
<niveau>3</niveau>
</competence>
<competence>
<nom>Armure Légère</nom>
<niveau>2</niveau>
</competence>
</competences>
</joueur>

Le fichier complet est visible ici

Maintenant, il faut préparer le fichier de mise en forme XSL.
Voici son contenu.

<?xml version="1.0" encoding="UTF-8"? >
<!--<? /*ceci est juste un faux tag PHP (<?) pour permettre la colorisation du code*/ -->
<xslConfusedtylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
 
<html>
<body>
<table cellpadding="2" cellspacing="0" border="1">
<tr>
<th>ID</th>
<th>Nom</th>
<th>Classe</th>
<th>Race</th>
<th>Compétence</th>
<th>Niveau</th>
</tr>
<xsl:for-each select="/page/joueur">
<tr>
<td valign="top"><xsl:value-of select="id"/></td>
<td valign="top"><xsl:value-of select="nom"/></td>
<td valign="top"><xsl:value-of select="classe"/></td>
<td valign="top"><xsl:value-of select="race"/></td>
<td>
<xsl:for-each select="competences/competence">
<xsl:value-of select="nom"/><br />
</xsl:for-each>
</td>
<td>
<xsl:for-each select="competences/competence">
<xsl:value-of select="niveau"/><br />
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
 
</xsl:template>
</xslConfusedtylesheet>

A première vue, cela ressemble fort à un fichier HTML normal excepté les balises qui commencent par <xsl:
Ces balises vont être interprétées par votre navigateur pour faire la mise en page.

Maintenant, analysons le fichier XSL

Comme expliqué plus haut, une feuille XSL doit respecter le format XML.
On commence donc par la déclaration adéquate
Code :
<?xml version="1.0" encoding="UTF-8" ?>

Il faut ensuite prévenir que le document est une feuille de style XSL.
Code :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

Ensuite, on rentre dans le vif du sujet.
Code :
<xsl:template match="/page">
L'élément
Code :
<xsl:template>
définit un modèle à appliquer que la partie du fichier XML spécifié par l'attribut match.
Ici, on veut travailler sur tout le document donc on part de la racine de celui-ci "/"

Les éléments contenus à l'intérieur de la balise <xsl:template> contiennent du HTML qui sera renvoyé à l'écran.

On commence a préparer l'entête du tableau avant d'arriver au moment ou on doit afficher le contenu du fichier XML.
Code :
<xsl:for-each select="/page/joueur">
Cette instruction permet de parcourir tous les élements du fichier XML spécifié dans l'attribut select.
Ici, on demande à parcourir tous les noeuds "joueur" placés sous noeud "page".

Il faut ensuite afficher les informations contenues dans le XML. Cela se fait au moyen de l'instruction xsl:value-of
Code :
<xsl:value-of select="id"/>
l'attribut select permet de spécifier les valeurs à afficher.
On demande ici à XSL de renvoyer le contenu du noeud "id" placé dans le noeud courant.
Je vous rappelle que nous sommes actuellement en train de parcourir les noeuds "joueur" qui contiennent tous un noeud fils "id".
On fait la même chose pour le nom, la classe et la race.

On continue en faisant la liste des compétences et de leur niveau.
Code :
<xsl:for-each select="competences/competence">
On recommence alors une boucle sur les noeuds "competence" du noeud "joueur" en cours.
La première sert à afficher la compétence, la deuxième le niveau.

Chaque boucle XSL doit être terminée par une balise </xsl:for-each>.

Le document lui même doit être fermé correctement dans le respect des règles XML
Code :
</xsl:template>
Code :
</xsl:stylesheet>

Le lien entre le XML et le XSL se fait via PHP par un simple code

<?php
$xml='<?xml version="1.0" encoding="iso-8859-1" ?>';
$xml.='<?xml-stylesheet type="text/xsl" href="xsl/tuto_xsl_1.xsl"?>';
$xml.=file_get_contents('xml/tuto_xsl_1.xml');
header('content-type: text/xml');
echo $xml;

Attributs, tests et tris

Aide concernant cette partie du tuto

Continuons la découverte du XSL avec une petite partie pour traiter de plusieurs choses
  • Les attributs dans le fichier XML

  • Les tests en XSL

  • définir un attribut HTML via XSL.

  • Les Tris


Les attributs dans le fichier XML

La premiere partie utilisait un fichier XML avec ce format:

<joueur>
<id>4</id>
<nom>Altinae</nom>
<classe>Voleur</classe>
<race>Humain</race>
<competences>
<competence>
<nom>Crochetage</nom>
<niveau>2</niveau>
</competence>
<competence>
<nom>Pièges</nom>
<niveau>3</niveau>
</competence>
<competence>
<nom>Epée Courte</nom>
<niveau>3</niveau>
</competence>
<competence>
<nom>Armure Légère</nom>
<niveau>2</niveau>
</competence>
</competences>
</joueur>

Maintenant, nous allons simplifier le fichier XML en utilisant les attributs.
Après un peu de travail, on en arrive à ce modèle

<joueur id="4" nom="Altinae" classe="Voleur" race="Humain">
<competences>
<competence nom="Crochetage" niveau="2"/>
<competence nom="Pièges" niveau="3"/>
<competence nom="Epée Courte" niveau="3"/>
<competence nom="Armure Légère" niveau="2"/>
</competences>
</joueur>

Moins de texte, plus compact… que du mieux donc.
Le fichier complet est visible ici.

Pour récupérer la valeur d'un atributs XML, il suffit de faire précéder son nom par un @
Ainsi, si je suis sur un noeud "joueur", <xsl:value-of select="@nom"/> me renverra son nom.

Les Tests

Il serait aussi intéressant de faire une petite colorisation de la liste, disons une ligne sur deux.
Nous pouvons travailler pour ce faire avec les tests XSL et les fonctions XPath

Un test XSL ressenble à ça

<xsl:if test="mettre_ici_la_condition">
... informations à afficher ...
</xsl:if>

Il n'y a pas de "else" en XSL. Par contre, pour les tests multiples, il existe un "case"/"otherwise"

Si je veux colorier une ligne sur deux, je dois savoir si je suis sur une ligne paire ou impaire.
La fonction XPath position() va m'aider à le faire.
Position() renvoie l'index du noeud en cours dans l'arbre ou il se trouve.
Mod renvoie le modulo entre deux chiffres.

Code :
<xsl:if test="position() mod 2 = 0">
renverra vrai une ligne sur deux.

Définir un attribut HTML via XSL

Reste a mettre à jour l'attribut HTML
<xsl:attribute> permet de définir les attributs HTML à la volée.
Dans l'exemple qui m'intéresse, je veux mettre l'arrière plan en gris, une ligne sur deux.
Je dois donc spécifier le background-color du style de mon tag TR

Cela se fait comme ceci.
Code :
<xsl:attribute name="style">background-color:lightgrey;</xsl:attribute>

Cette instruction doit être placée juste après mon tag TR.

Les Tris

Enfin, pour finir cet exercice, je souhaite trier les compétences par ordre alphabétique, histoire de faire joli.
J'utilise pour cela
Code :
xsl:sort
qui permet de trier les données à l'intérieur d'un
Code :
xsl:for-each
Code :
xsl:sort
doit être placé juste après
Code :
xsl:for-each
.
Pour trier sur plusieurs critères à la fois, on peut mettre plusieurs
Code :
xsl:sort
l'un après l'autre.

Dans certains cas, il est nécessaire de spécifier si on tri des dates, des nombres, des chaines de caractère et dans quel ordre.
Les attributs order et data-type sont là pour ça.
Donc, pour trier par nom, je dois mettre juste après ma boucle sur les compétences
Code :
<xsl:sort select="@nom" data-type="text" order="ascending"/>
(ne pas oublier de le mettre dans le deux boucles pour la cohérence des données.

En prenant compte des informations et restrictions spécifiées au dessus, mon fichier XSL ressemble maintenant à

<?xml version="1.0" encoding="UTF-8" ?>
<!--<? /*ceci est juste un faux tag PHP (<?) pour permettre la colorisation du code*/ -->
<xslConfusedtylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
 
<html>
<body>
<table cellpadding="2" cellspacing="0" border="1">
<tr>
<th>ID</th>
<th>Nom</th>
<th>Classe</th>
<th>Race</th>
<th>Compétence</th>
<th>Niveau</th>
</tr>
<xsl:for-each select="page/joueur">
<tr>
<!--// je fais ici mon test sur le N° du noeud -->
<xsl:if test="position() mod 2 = 0">
<!--// je définis mon attribut de style -->
<xsl:attribute name="style">background-color:lightgrey;</xsl:attribute>
</xsl:if>
<td valign="top"><xsl:value-of select="@id"/></td>
<td valign="top"><xsl:value-of select="@nom"/></td>
<td valign="top"><xsl:value-of select="@classe"/></td>
<td valign="top"><xsl:value-of select="@race"/></td>
<td>
<xsl:for-each select="competences/competence">
<!--// Un petit tri pour organiser les données -->
<xslConfusedort select="@nom" data-type="text" order="ascending"/>
<xsl:value-of select="@nom"/><br />
</xsl:for-each>
</td>
<td>
<xsl:for-each select="competences/competence">
<!--// Même tri pour la cohérence -->
<xslConfusedort select="@nom" data-type="text" order="ascending"/>
<xsl:value-of select="@niveau"/><br />
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
 
</xsl:template>
</xslConfusedtylesheet>

Variables XSL et fonctions d'aggrégation

Troisième chapître sur XSL et ses possibilités.
Accrochez vous, ça commence à devenir sportif.
Je vous recommande de lire au préalable le contenu des deux premieres parties si vous n'avez pas une bonne expérience sur XSL.

Les Variables

Une variable en XSL se déclare grace au tag <xsl:variable name="" select=""/>
L'attribut name contient le nom de la variable
L'attribut select contient sa valeur
Les variables s'utilisent en préfixant leur nom par un $.

Petit exemple
<xsl:variable name="varxsl" select="'contenu de ma variable XSL'"/>
Le contenu de ma variable nommée varxsl est <xsl:value-of select="$varxsl"/>

Les variables XSL sont différentes des variables PHP sur bien des points
  • Elles ne sont pas dynamiques.


Il est impossible en effet de changer à la volée le contenu d'une variable. On doit la redéfinir si on veut changer sa valeur. <xsl:variable name="incremental" select="$incremental + 1"/>, ne fonctionnera pas.
  • De la même manière, on ne peut pas déclarer deux fois la même variable dans un même bloc.

<xsl:variable name="varxsl" select="'première déclaration'"/>
<xsl:variable name="varxsl" select="'deuxième déclaration déclaration'"/>

va générer une erreur.
  • Elles ont une portée limitée au noeud dans lequel elles sont déclarées


Une variable est visible dans le noeud qui l'a déclaré et dans tous les noeuds enfants.

<div>
<!--La variable créee ci dessous ne sera visible que dans le DIV en cours et les éléménts enfants.-->
<xsl:variable name="myvar" select="'variable XSL'"/>
<!--Ici, ma variable est valide-->
<xsl:value-of select="$myvar"/>
<span>
<!--Ici, ma variable est toujours valide-->
<xsl:value-of select="$myvar"/>
</span>
</div>
<!--Ici, ma variable n'est plus connue-->
<!--Cet appel générera une erreur car il est fait en dehors du DIV ou a été créé la variable-->
<xsl:value-of select="$myvar"/>
  • Elles peuvent contenir des valeurs fixes ou une partie de l'arborescence d'un document XML


C'est ici que les variables montrent leur puissance.
Dans l'attribut select, il est possible de spécifier une expression XPath de manière à stocker le résultat pour une utilisation ultérieure.
Prenons par exemple le XML suivant

<page>
<unitetype>
<type id="1" att="10" def="3">Cavalerie</type>
<type id="2" att="5" def="0">Archer</type>
<type id="3" att="2" def="2">Fantassin</type>
</unitetype>
<uniteequip>
<eqp id="1" def="1">Armure de Cuir</eqp>
<eqp id="2" def="2">Cotte de Mailles</eqp>
<eqp id="3" def="3">Armure de Plates</eqp>
<eqp id="4" att="3">Epée Longue</eqp>
<eqp id="5" att="2">Epée Courte</eqp>
<eqp id="6" att="2">Hache</eqp>
<eqp id="7" att="3">Lance</eqp>
<eqp id="8" att="1">Fourche</eqp>
<eqp id="9" att="3">Arbalète</eqp>
<eqp id="10" att="1">Arc Court</eqp>
<eqp id="11" att="2">Arc Long</eqp>
<eqp id="12" def="1">Bouclier</eqp>
</uniteequip>
</page>

Je vais créer une variable XSL contenant la liste des équipements possibles et l'utiliser pour afficher des informations

<!--je récupère dans une variable tous les éléménts de type eqp-->
<xsl:variable name="equiplist" select="/page/uniteequip/eqp" />
<!-- Affiche le contenu de l élément correspondant à ID=8 en utilisant la variable-->
<xsl:value-of select="$equiplist[@id='8']"/>
<!-- Même chose à partir du fichier XML-->
<xsl:value-of select="/page/uniteequip/eqp[@id='8']"/>
<!-- Créé une variable statique-->
<xsl:variable name="staticid" select="'10'" />
<!-- Affiche le contenu de l élément correspondant à ID=10 en utilisant les variables-->
<xsl:value-of select="$equiplist[@id=$staticid]"/>

Les fonctions d'aggrégation

Ce sont les fonctions de type sum(), count(), etc. XSL peut faire ce genre d'opérations sur les fichiers XML.
Ces fonctions prennent en paramètre une expression XPath
<xsl:value-of select="count(<em>eqp)" /> va renvoyer le nombre de noeuds ayant "eqp" pour nom
<xsl:variable name="max_att" select="max(</em>unite/@att)" /> va renvoyer la valeur maximum de l'attribut "att" existant pour les noeuds "unite" et mettre le tout dans la variable $max_att

Voici un fichier le XSL qui sert d'exemple pour illustrer ces explications sur les variables et les fonctions d'aggrégation.
Il y a une utilisation intensive des variables, des fonctions d'aggrégation

<?xml version="1.0" encoding="UTF-8" ?>
<!--<? /*ceci est juste un faux tag PHP (<?) pour permettre la colorisation du code*/ -->
<xslConfusedtylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
 
<html>
<head>
<style>
html {font-family:verdana, helvetica, sans-serif; font-size:11px;}
th {text-align:left;}
div.unit {position:relative; float:left;borderConfusedolid black 1px;margin:5px;}
div.header {border-bottomConfusedolid black 1px;padding:2px;}
div.body {position:relative;padding:2px; padding-right:40px;}
div.att {font-weight:bold; position:absolute; right:3px;
top:3px; borderConfusedolid black 1px;
width:30px;height:30px;
text-align:center; vertical-align:baseline;}
div.def {font-weight:bold; position:absolute; right:3px;
bottom:3px; borderConfusedolid black 1px;
width:30px;height:30px;
text-align:center; vertical-align:center;}
table.detail {borderConfusedolid black 1px;}
tr.values {font-weight:bold;}
tr.equip {background-color:lightgrey;}
tr.head {font-weight:bold; background-color:lightgrey;}
tr.head th {border-bottomConfusedolid black 1px;}
td.values {text-align:center; border-leftConfusedolid black 1px;}
</style>
</head>
<body>
<xsl:variable name="equiplist" select="/page/uniteequip/eqp"/>
<xsl:variable name="typelist" select="/page/unitetype/type"/>
 
 
<xsl:for-each select="/page/unites/unite">
<xsl:variable name="unit" select="."/>
<xsl:variable name="eqpunit" select="$unit//equipement/@id"/>
 
<div class="unit">
<div class="header">
Type : <xsl:value-of select="$typelist[@id=$unit/@type]"/><br />
Unité : <xsl:value-of select="@nom"/><br />
Quantité : <xsl:value-of select="@total"/><br />
</div>
<div class="body">
<table class="detail" cellspacing="0" cellpadding="2">
<tr class="head">
<th>Détail</th>
<th>Att</th>
<th>Déf</th>
</tr>
<tr class="values">
<td>Valeur de Base</td>
<td class="values"><xsl:value-of select="$typelist[@id=$unit/@type]/@att"/></td>
<td class="values"><xsl:value-of select="$typelist[@id=$unit/@type]/@def"/></td>
</tr>
<xsl:for-each select="./equipement">
<tr class="equip">
<xsl:variable name="equipid" select="@id"/>
<td>- <xsl:value-of select="$equiplist[@id=$equipid]/."/></td>
<td class="values"><xsl:value-of select="$equiplist[@id=$equipid]/@att"/></td>
<td class="values"><xsl:value-of select="$equiplist[@id=$equipid]/@def"/></td>
</tr>
</xsl:for-each>
<tr class="values">
<td>Total</td>
<td class="values">
<xsl:value-of select="sum($equiplist[@id=$eqpunit]/@att) + $typelist[@id=$unit/@type]/@att"/>
</td>
<td class="values">
<xsl:value-of select="sum($equiplist[@id=$eqpunit]/@def) + $typelist[@id=$unit/@type]/@def"/>
</td>
</tr>
</table>
<div class="att">
ATT<br />
<xsl:value-of select="(sum($equiplist[@id=$eqpunit]/@att) + $typelist[@id=$unit/@type]/@att) * @total"/>
</div>
<div class="def">
DEF<br />
<xsl:value-of select="(sum($equiplist[@id=$eqpunit]/@def) + $typelist[@id=$unit/@type]/@def) * @total"/>
</div>
</div>
</div>
</xsl:for-each>
 
</body>
</html>
 
</xsl:template>
</xslConfusedtylesheet>

Fichiers utilisés pour ce tutorial
  • Premiere Partie


Fichier XML
Fichier XSL
Résultat
  • Seconde Partie


Fichier XML
Fichier XSL
Résultat
  • Troisieme Partie


Fichier XML
Fichier XSL
Résultat

Composée par Roworll sur le forum de jeuphp.net

Mis en forme par Zamentur