Introduction

Un inconvénient qui me dérange dans les moteurs de templates courants est leur aspect purement “textuel”. On ne manipule que des chaînes de caractères et rien n’est fait pour s’assurer que le résultat final (généralement une page web ou un document XML) soit correct.

C’est ce genre de défaut qui peut coûter beaucoup de temps en débuggage sur un projet web. Une balise non fermée dans un template perdu dans la masse peu engendrer des pages invalides. Cela induit des erreurs d’interprétation du code qui peuvent varier d’un navigateur à l’autre et créer des bugs d’affichage (là encore, différents d’un navigateur à l’autre) et c’est la fête du saucisson dès qu’il s’agit de trouver le template en cause.

De plus, l’ordre de construction de la page web est assez restrictif: une fois qu’une page ou une zone a été construite, il est très difficile de revenir dessus.

Un exemple type serait celui de la génération automatique d’une table des matières basées sur le code final de la page.

Ainsi le code suivant :

<body>
  <h1>Hello world</h1>
  <h2>Partie 1</h2>
  <p>...</p>
  <p>...</p>
  <h2>Partie 2</h2>
  <p>...</p>
  <p>...</p>
</body>

qui deviendrait :

<body>
  <h1>Hello world</h1>
  <ul id="toc">
    <li><a href="#auto-generated-id-afb5f">Partie 1</a></li>
    <li><a href="#auto-generated-id-ff42c">Partie 2</a></li>
  </ul>
  <h2 id="auto-generated-id-afb5f">Partie 1</h2>
  <p>...</p>
  <p>...</p>
  <h2 id="auto-generated-id-ff42c">Partie 2</h2>
  <p>...</p>
  <p>...</p>
</body>

Cette transformation est quasiement impossible à réaliser avec un moteur de template conventionnel. Ou bien nécessite des expressions régulières de la mort qui tue, très consommatrices en puissance de calcul (et en cafés pour le pauvre malheureux qui doit se palucher le code).

S’il y a des fans de MVC et des templates logicless dans la salle… Je les entends d’ici dire: “mais c’est au contrôleur de s’adapter et d’injecter les données concernant le contenu et celles concernant la table des matières”.

Je suis d’accord à 100% avec ce point de vue à ceci près que dans le cas présent nous effectuons un traitement basé sur la vue elle-même, et non pas sur les données qui ont servi à la construire. Ce genre de transformation et d’ailleurs très souvent réalisé côté client via du javascript pour cette même raison.

Les transformations XSL quant à elles permettent de résoudre ce problème du côté serveur sans difficulté avec le code suivant:

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

  <xsl:template match="h1">
    <h1><xsl:apply-templates /></h1>
    <ul>
      <xsl:for-each select="following::h2">
        <li>
          <a href="#{generate-id(.)}"><xsl:value-of select="text()" /></a>
        </li>
      </xsl:for-each>
    </ul>
  </xsl:template>

  <xsl:template match="h2">
    <h2 id="{generate-id(.)}"><xsl:apply-templates /></h2>
  </xsl:template>

  <xsl:template match="p">
    <p><xsl:apply-templates /></p>
  </xsl:template>

  <xsl:template match="body">
    <body><xsl:apply-templates /></body>
  </xsl:template>

</xsl:stylesheet>

Via l’utilisation de la fonction generate-id(), XSLT permet même la résolution d’un problème aussi vieux que les premiers templates HTML eux-même, à savoir: “comment puis-je faire pour m’assurer qu’un ID généré dans un template ne se retrouve pas en double dans ma page web?”

En travaillant à un niveau d’abstraction permettant de manipuler des objets DOM au lieu de simples chaînes de caractères, il devient possible d’identifier des noeuds de façon unique.

De plus, le fait de travailler avec l’API DOM permet aussi d’avertir le développeur qui écrit ses templates: Comme chacun de ses template est alors un DOMDocument, toute erreur dans son code peut être détecté lors de son chargement.

Comment fait-on en PHP?

La documentation est succinte mais suffisante sur l’utilisation de XSLTProcessor mais, dans l’exemple, il est fait usage de la fonction transformToXML() qui retourne le code XML du document plutôt que le document DOM lui-même. Je préfère donc pour ma part utiliser la fonction transformToDoc() qui retourne simplement le nouveau document DOM.

<?php

// Chargement du document d'origine
$doc = new DOMDocument();
$doc->load('monfichier.xml');

// Chargement du template XSL
$xsl = new DOMDocument();
$xsl->load('monfichier.xsl');

// Instanciation du moteur de template
$proc = new XSLTProcessor();

// Import du template
$proc->importStylesheet($xsl);

// Récupération d'un nouveau document
$doc = $proc->transformToDoc($doc);

Et les variables ?

Alors oui… Les transformations c’est bien beau mais où est le côté dynamique? Pour l’instant on ne fait que transformer des fichiers avec d’autres fichiers. Comment est-ce que j’intègre un résultat dynamique là dedans?

Tout simplement en utilisant le XSLTProcessor comme la plupart des moteurs de template. C’est-à-dire en lui injectant des variables:

<?php
// ...
$proc->setParameter('', 'MaVariable', "world");

Puis dans le fichier XSL:

<h1>Hello <xsl:value-of select="$MaVariable" /></h1>

Comment faire si je n’ai pas de document de départ ?

Ok! Pour transformer un document via des templates XSL, je sais faire… Mais comment faire quand on part de rien?

C’est la question qui m’a fait laisser de côté XSL pendant un moment.

Je me disais que les moteurs de templates ne servaient pas uniquement qu’à transformer du contenu en un autre contenu… Car ils en fabriquent de toute pièce aussi.

Partant de ce principe, je pensais qu’une transformation XSL aurait forcément des limites dans un framework PHP. Mais en réalité, il est tout à fait possible d’initialiser une transformation d’un document vide en un autre document:

<?php
// Chargement du template XSL
$xsl = new DOMDocument();
$xsl->load('monfichier.xsl');

// Instanciation du moteur de template
$proc = new XSLTProcessor();
$proc->setParameter('', 'MaVariable', "world");

// Import du template
$proc->importStylesheet($xsl);

// Récupération d'un nouveau document
$doc = $proc->transformToDoc(new DOMDocument());

Il suffit pour cela que le fichier XSL contienne un template pour la racine du document via un <xsl:template match="/"> :

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

  <xsl:template match="/">
    <body>
      <h1>Hello <xsl:value-of select="$MaVariable" /></h1>
      <h2>Partie 1</h2>
      <p>...</p>
      <p>...</p>
      <h2>Partie 2</h2>
      <p>...</p>
      <p>...</p>
    </body>
  </xsl:template>

</xsl:stylesheet>

Conclusion

Nous avons vu qu’il était tout à fait possible d’utiliser un fichier XSL en tant que fichier de template traditionnel en y utilisant des variables injectées via PHP.

Nous avons vu aussi que nous pouvions aller plus loin en modifiant simultanément plusieurs parties d’un même document en une seule opération sans recourir aux expressions régulières.

Et nous avons vu que l’utilisation de XSL comme langage de template permettait une meilleure détection des erreurs en systématisant l’usage du modèle DOM.