IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

XML/XSL avec PHP et le mode XSLT

13/10/2003

Par Bob
Introduction :

Cet article présuppose que vous connaissez déjà XML/XSL et que vous avez quelques connaissances en PHP. Il n'est cependant pas nécessaire de maîtriser ces deux langages, quelques connaissances suffisent. Dans cet article, seul le code PHP sera expliqué. Si vous ne comprenez pas le code XSL utilisé, référez vous aux excellents tutoriels qui existent déjà.

Vous pourrez découvrir comment utiliser XML/XSL avec PHP pour générer des pages dynamiquement ou encore pour générer dynamiquement du contenu offline. L'utilité de générer du contenu dynamique offline sera expliqué en conclusion. Vous découvrirez le mode XSLT de PHP au cours d'un projet simple détaillant la réalisation d'un cours. Vous trouverez en fin de page une annexe comprenant entre autres des références à l'aide des fonctions PHP utilisées.

Présentation :

Le langage XML couplé aux feuilles de style XSL est très pratique pour structurer et publier des données. Prenons par exemple, la publication d'un cours. Nous pouvons le diviser en chapitres, puis en parties. Il serait très intéressant de pouvoir dissocier le contenu du cours de son affichage. De cette manière, on pourrait modifier l'apparence de ce cours, etc... sans avoir à refaire toutes les pages. C'est malheureusement le cas dans l'utilisation de pages HTML ou PHP traditionnelles. On peut également aborder le problème de la numérotation des pages. Insérer une partie ou un chapitre oblige le décalage de la numérotation des chapitres suivants. De plus, nous voudrions pouvoir fournir ce cours présenté sous divers affichages. Un affichage utilisant une page par partie pour la publication Internet, une page contenant tout le cours pour permettre son impression... Bien entendu il serait extrêmement pénible de devoir faire des copier/coller pour créer chacune de ces parties. De plus, la modification d'une partie du cours entraîne l'obligation de répercuter ces modifications sur tous les types de publication.

Tous ces inconvénients vont pouvoir être évités grâce à l'utilisation de XML/XSL, ainsi que de PHP. Le création d'un fichier XML lié à une feuille de style XSL permet en effet de placer le cours dans un fichier XML, de le structurer, et de gérer ensuite son affichage au travers de la feuille de style XSL. Cette méthode présente plusieurs inconvénients. Tout d'abord tous les navigateurs ne supportent pas le XML. Les personnes disposant de tels navigateurs ne pourront donc pas consulter les documents ainsi réalisés. De plus, la consultation d'une seule partie du cours entraîne l'obligation pour le client de télécharger l'ensemble du fichier XML contenant le cours. Si le cours est volumineux, ceci est irréalisable, car cela impliquerait des temps de téléchargements importants pour consulter quelques pages...

La solution que nous allons utiliser ici consiste en un mode ajouté à PHP. Ce mode, XSLT permet la transformation côté serveur de l'ensemble XML/XSL en une page HTML. Cette page créée dynamiquement, pourra ensuite être retournée au client sous forme d'une page HTML. Le navigateur du client n'aura donc pas besoin de reconnaître le XML. De plus, les manipulations du fichier XML se faisant du côté serveur, le client n'aura à aucun moment la nécessité de télécharger le fichier XML. Côté client, tout se passera donc comme si le site avait été réalisé de manière 'classique'.

Nous disposons donc d'une feuille XML ainsi que de plusieurs feuilles XSL permettant d'afficher cette feuille. Chaque feuille XSL permet de présenter le contenu du fichier XML d'une certaine manière. Par exemple, la feuille 1 permet d'afficher un index simple, le feuille 2 permet d'afficher un article donné, tandis que la feuille 3 permet d'afficher l'index et l'ensemble du cours à la suite, de manière à proposer une version imprimable. Selon la page PHP accédée, nous feront un appel à XSLT en lui passant le nom du fichier XSL à utiliser, le fichier XML étant toujours le même. XSLT convertit ensuite les données en HTML et le tout est renvoyé au client qui n'a aucune connaissance du mécanisme permettant d'afficher la page.

Création du projet :

Nous allons tout d'abord étudier l'exemple du cours présenté ci-dessus. Nous désirons réaliser un cours, divisé en chapitres puis en parties. Toutes les numérotations de chapitres et de parties seront réalisées automatiquement, de manière à éviter des modifications pénibles dans le cas d'ajout d'une partie ou d'un chapitre. Un index sera généré automatiquement. Le cours sera consultable partie par partie ou en une version imprimable. La version imprimable reprendra sur une seule page l'index et l'ensemble du cours. La version partie par partie disposera d'en-tête et de pied de page permettant de revenir au sommaire ou de passer à la page précédente ou suivante. L'en-tête et le pied de page seront générés automatiquement, de manière là encore à éviter les modifications dans le cas d'un ajout. Les fichiers XML et XSL qui sont présentés ci-après ne seront jamais accessibles au client. Le client appellera une page PHP qui retournera la page HTML mise en forme.

Voici un exemple de fichier XML :

<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
  <chapitre titre="Titre du chapitre 1">
    <partie titre="Titre de la partie 1">
      Texte de la partie 1 du chapitre 1
    </partie>
    <partie titre="Titre de la partie 2">
      Texte de la partie 2 du chapitre 1
    </partie>
  </chapitre>
  
  <chapitre titre="Titre du chapitre 2">
    <partie titre="Titre de la partie 1">
      Texte de la partie 1 du chapitre 2
    </partie>
    <partie titre="Titre de la partie 2">
      Texte de la partie 2 du chapitre 2
    </partie>
  </chapitre>
</root>

Nous voyons bien ici la structure du cours. Il est divisé en chapitres, chaque chapitre pouvant contenir autant de parties que nécessaire. Remarquons l'absence totale de la numérotation. Une fois la structure du fichier XML précisée, il nous reste à créer les 3 fichiers XSL qui permettront d'afficher le cours. Nous allons définir 4 fichiers XSL, index.xsl qui générera l'index, article.xsl qui affichera une partie du cours et full.xsl qui affichera le tutorial en version imprimable. Un fichier annexe balises.xsl sera créé et inclus au début de chaque fichier XSL. Ce fichier contiendra les données sur les balises de mise en forme du texte. De cette manière la modification de la mise en page du cours nécessitera simplement la modification d'un fichier.

Fichier index.xsl :

<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version="1.0">
<xsl:output encoding="ISO-8859-1"/>

<xsl:template match="root">
  <h2 align="center">Index</h2>
  <p>Version Imprimable [<a href="full.php" target="_blank">HTML</a>]</p>
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="chapitre">
  <br/>
    <xsl:variable name="cchap">
    <xsl:value-of select="position() div 2" />
    </xsl:variable>
  <p style="text-indent=0cm;margin-top:2px;margin-bottom:2px;">
    <b>Chapitre 
    <xsl:value-of select="$cchap"/> : 
    <xsl:value-of select="./@titre"/></b>
    </p>
  <xsl:for-each select="partie">
    <xsl:variable name="n">
    <xsl:value-of select="count(//chapitre[not(position()>=$cchap)]//partie)+position()"/>
    </xsl:variable>
    <p style="text-indent=1cm;margin-top:2px;margin-bottom:2px;">
      <a href="article.php?id={$n}">
      <xsl:value-of select="position()"/>. 
      <xsl:value-of select="@titre"/></a>
    </p>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Ce fichier est relativement court. Il génère l'index et numérote automatiquement les chapitres et les parties. L'affichage des parties sera géré par la page article.php et le fichier XSL article.xsl.

Fichier article.xsl :

<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version="1.0">
<xsl:output encoding="ISO-8859-1"/>

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

<xsl:param name="id"/>
<xsl:variable name="tchap">
  <xsl:value-of select="count(//chapitre)" />
</xsl:variable>
<xsl:variable name="mpart">
  <xsl:value-of select="count(//chapitre//partie)"/>
</xsl:variable>

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

<xsl:template match="chapitre">
  <xsl:variable name="cchap">
    <xsl:value-of select="position() div 2" />
  </xsl:variable>
  <xsl:for-each select="partie">
  <xsl:variable name="cpos">
    <xsl:value-of select="count(//chapitre[not(position()>=$cchap)]//partie)+position()"/>
  </xsl:variable>
  <xsl:variable name="pcpos">
    <xsl:value-of select="$cpos - 1"/>
  </xsl:variable>
  <xsl:variable name="ncpos">
    <xsl:value-of select="$cpos + 1"/>
  </xsl:variable>

  <xsl:if test="$cpos = $id">

    <div align="center">Notre cours</div>
    <table border="0" width="100%">
    <tr>
      <xsl:if test="$cpos = 1">
        <td width="100" align="left">Précédent</td>
      </xsl:if>
      <xsl:if test="$cpos > 1">
        <td width="100" align="left">
        <a href="article.php?id={$pcpos}">Précédent</a>
        </td>
      </xsl:if>
      <td align="center"><a href="index.php">Index</a></td>
      <xsl:if test="$cpos = $mpart">
        <td width="100" align="right">Suivant</td>
      </xsl:if>
      <xsl:if test="not ($cpos = $mpart)">
        <td width="100" align="right">
        <a href="article.php?id={$ncpos}">Suivant</a>
        </td>
      </xsl:if>
    </tr>
    </table>
    <hr size="1" width="100%"/>

    <h1 align="center">Chapitre <xsl:value-of select="$cchap"/></h1>
    <h2 align="center"><xsl:value-of select="../@titre"/></h2>
    <h2>
      <xsl:value-of select="position()"/>. 
      <xsl:value-of select="./@titre"/>
    </h2>
    <xsl:apply-templates/>

    <hr size="1" width="100%"/>
    <table border="0" width="100%">
    <tr>
      <xsl:if test="$cpos = 1">
        <td width="100" align="left">Précédent</td>
      </xsl:if>
      <xsl:if test="$cpos > 1">
        <td width="100" align="left">
        <a href="article.php?id={$pcpos}">Précédent
        </a></td>
      </xsl:if>
      <td align="center"><a href="index.php">Index</a></td>
      <xsl:if test="$cpos = $mpart">
        <td width="100" align="right">Suivant</td>
      </xsl:if>
      <xsl:if test="not ($cpos = $mpart)">
        <td width="100" align="right">
        <a href="article.php?id={$ncpos}">Suivant</a>
        </td>
      </xsl:if>
    </tr>
    </table>

  </xsl:if>

  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Ce fichier s'occupe de l'affichage d'un article. Il crée les en-têtes et pieds de page et numérote chapitre et partie. Remarquons la balise 'xsl:param' en début de fichier. Cette balise permet de récupérer le numéro de l'article à afficher. Ce paramètre est passé par le fichier PHP réalisant l'appel. Ici il s'agit donc de la page article.php.

Fichier full.xsl

<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version="1.0">
<xsl:output encoding="ISO-8859-1"/>

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

<xsl:param name="id"/>
<xsl:variable name="tchap">
  <xsl:value-of select="count(//chapitre)" />
</xsl:variable>
<xsl:variable name="mpart">
  <xsl:value-of select="count(//chapitre//partie)"/>
</xsl:variable>

<xsl:template match="root">
  <h2 align="center">Notre cours</h2>
  <xsl:for-each select="chapitre">
    <p style="text-indent=0cm;margin-top:2px;margin-bottom:2px;">
      <b>Chapitre <xsl:value-of select="position()"/> : 
      <xsl:value-of select="./@titre"/></b>
    </p>
    <xsl:variable name="cchap">
      <xsl:value-of select="position()"/>
    </xsl:variable>
    <xsl:for-each select="partie">
      <xsl:variable name="n">
        <xsl:value-of select="count(//chapitre[not(position()>=$cchap)]//partie)+position()"/>
      </xsl:variable>
      <p style="text-indent=1cm;margin-top:2px;margin-bottom:2px;">
        <a href="#{$n}"><xsl:value-of select="position()"/>. 
        <xsl:value-of select="@titre"/></a>
      </p>
    </xsl:for-each>
  </xsl:for-each>
  <br/><br/><br/><br/><br/>
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="chapitre">
  <xsl:variable name="cchap">
    <xsl:value-of select="position() div 2" />
  </xsl:variable>
  <h1 align="center">Chapitre <xsl:value-of select="$cchap"/></h1>
  <h2 align="center"><xsl:value-of select="./@titre"/></h2>
  <xsl:for-each select="partie">
    <xsl:variable name="cpos">
      <xsl:value-of select="count(//chapitre[not(position()>=$cchap)]//partie)+position()"/>
    </xsl:variable>
    <xsl:variable name="pcpos">
      <xsl:value-of select="$cpos - 1"/>
    </xsl:variable>
    <xsl:variable name="ncpos">
      <xsl:value-of select="$cpos + 1"/>
    </xsl:variable>
    <xsl:variable name="n">
      <xsl:value-of select="count(//chapitre[not(position()>=$cchap)]//partie)+position()"/>
    </xsl:variable>
    <h2>
      <a name="{$n}"/><xsl:value-of select="position()"/>. 
      <xsl:value-of select="./@titre"/>
    </h2>
    <xsl:apply-templates/>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Ce fichier affiche l'index suivi du cours complet. Les en-têtes et pieds de page ont été supprimés et le titre des chapitres n'est plus affiché qu'au moment du passage d'un chapitre à l'autre.

Fichier balises.xsl

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

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

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

</xsl:stylesheet>

Ce fichier contient l'ensemble des balises de mise en forme. Il est ici dans une version excessivement concise. Il est cependant très aisé d'ajouter autant de balises que nécessaire. Ce fichier pourra être édité pour modifier la présentation générale du cours.

Les différentes parties seront consultées à partir de pages PHP. Nous allons maintenant nous occuper de la création des pages PHP, index.php, article.php et full.php. Ces pages PHP utiliseront les feuilles XSL de même nom. La plupart des implémentations du mode XSLT utilisent SABLOTRON comme parser XML. Pour connaître le parser utilisé par PHP, utilisez la commande phpinfo().

Voici le fichier index.php :

<?php
$xh = xslt_create();

$file=fopen("cours.xml","r");
$xml=fread($file,16384);
fclose($file);

$file=fopen("index.xsl","r");
$xsl=fread($file,16384);
fclose($file);

$arguments = array(
  '/_xml' => $xml,
  '/_xsl' => $xsl
  );

$result = xslt_process($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);

xslt_free($xh);

print "$result";
?>

Nous allons analyser le contenu de ce fichier :

$xh = xslt_create();

Nous créons ici une instance du parseur XML.

$file=fopen("cours.xml","r");
$xml=fread($file,16384);
fclose($file);

Cette partie lit le contenu du fichier XML (jusqu'à 16384 octets). Si votre fichier XML est plus volumineux, pensez à augmenter cette valeur.

$file=fopen("index.xsl","r");
$xsl=fread($file,16384);
fclose($file);

Cette partie est similaire à la précédente. Elle lit le fichier XSL.

$arguments = array(
  '/_xml' => $xml,
  '/_xsl' => $xsl
  );

$result = xslt_process($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);

Ici, le fichier XML et le fichier XSL sont passés aux parseur instancié plus tôt. Le fichier HTML résultant sera récupéré dans la variable $result.

xslt_free($xh);

On détruit le parseur XML.

print "$result";

Il ne reste plus qu'à afficher le fichier HTML créé.

Le fichier full.php ne présente rien de particulier. Il est similaire au fichier index.php. Le seul changement est le nom de la feuille de style utilisée.

Fichier full.php :

<?php
$xh = xslt_create();

$file=fopen("cours.xml","r");
$xml=fread($file,16384);
fclose($file);

$file=fopen("full.xsl","r");
$xsl=fread($file,16384);
fclose($file);

$arguments = array(
  '/_xml' => $xml,
  '/_xsl' => $xsl
  );

$result = xslt_process($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);

xslt_free($xh);

print "$result";
?>

Le fichier article.php présente quelques différences :

Voici une analyse des changements apportés à ce fichier :

if(isset($_GET['id']))
  $xslt_params["id"] = $_GET['id'];
else
  $xslt_params["id"] = "1";

$result = xslt_process($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments,$xslt_params);

Cette partie diffère des fichiers précédents et mérite donc quelques explications. Tout d'abord, nous vérifions que le paramètre 'id' a bien été passé dans l'URL. Ce paramètre représente le numéro de la partie à afficher. Un tableau contenant une clé 'id' est ensuite créé. Si un paramètre a été passé dans l'URL, il est utilisé. Sinon, la valeur par défaut 1 est utilisée. Les paramètres sont ensuite passés au fichier XSL. Le fichier XSL pourra récupérer ces paramètres grâce à la balise 'xsl:param'. De cette manière, le fichier XSL n'affichera qu'un seul article.

Voici donc un exemple complet et fonctionnel. Il ne reste plus qu'à modifier le fichier XML et à ajouter les titres de chapitre et partie ainsi que le texte. Il faudrait également ajouter au fichier balises.php les balises nécessaires à la mise en page. Cette méthode de publication présente une étonnante flexibilité. La modification ou l'ajout de parties sur ce cours est extrêmement simple. De plus, l'inconvénient de la taille de fichier XML est réglé puisque ce fichier est traité côté serveur.

Ajout de code PHP dans les fichiers XSL

Pour l'instant nous n'avons pas ajouté de code PHP à la page générée par la feuille XSL. Ceci présente en effet quelques difficultés supplémentaires. Il peut cependant être très utile de pouvoir ajouter du code PHP à la page générée. Ce code PHP sera ensuite traité par PHP avant l'affichage de la page. De cette manière, il est possible d'inclure par exemple un fichier d'en-tête supplémentaire en PHP en début de page.

Il est impossible d'afficher les balises <?php et ?> directement dans le fichier XSL. En effet, le format de ces balises n'est pas conforme à la norme. Il va donc falloir créer des balises <php> et </php>. Ces balises n'auront aucun sens particulier. Elles seront par la suite remplacées par les balises php réelles. Enfin, la page sera passée à PHP. Un fois la page traitée par PHP, le résultat obtenu sera envoyé au client.

Ce traitement est plus complexe. Il n'est pas forcément nécessaire. Il peut cependant être très pratique pour insérer un contenu dynamique sur les pages (ex. : l'heure ou la date).

Comme nous l'avons dit, les balises <php> et </php> n'ont aucun sens précis. La page renvoyée à PHP contiendra donc ces balises. Nous utiliseront la fonction str_replace() 2 fois de suite pour remplacer ces balises par des balises <?php et ?>. Une fois ce remplacement effectué la fonction eval() sera appelée de manière à traiter le PHP contenu dans la page. La fonction eval() affichera ensuite automatiquement la page résultante.

Voici la page index.php modifiée de manière à gérer les balises <php> et </php> :

<?php
$xh = xslt_create();

$file=fopen("cours.xml","r");
$xml=fread($file,16384);
fclose($file);

$file=fopen("index.xsl","r");
$xsl=fread($file,16384);
fclose($file);

$arguments = array(
  '/_xml' => $xml,
  '/_xsl' => $xsl
  );

$result = xslt_process($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);

xslt_free($xh);

$result=str_replace("<php>","<?php",$result);
$result=str_replace("</php>","?>",$result);
$result=eval("?".chr(62).$result.chr(60)."?");
?>

Les balises <php> et </php> sont remplacées, dans la page HTML résultante, par des balises PHP valides. Une fois ce remplacement effectué, il ne reste plus qu'à passer la page obtenue à PHP pour évaluation. Le code PHP est traité par la fonction eval() puis affiché. Remarquons que la fonction eval() affiche d'elle même le résultat. Le print() utilisé dans la version précédente de cette page n'est donc plus nécessaire. La fonction eval() peut évaluer un code PHP simple (sans balises) ou une page complète, comprenant donc des balises <?php et ?>. L'analyse est faite dans la continuité de la page courante. C'est-à-dire que le début du fichier qui appelle eval() est pris en compte pour l'évaluation. Il est donc nécessaire de fermer les balises du fichier courant par '"?".chr(62)' puis de les rouvrir par char(60)."?".

Génération dynamique offline

Il peut être très utile de générer du contenu dynamique offline. Pour cela, il vous faut bien sûr installer PHP et XSLT sur votre machine. La solution la plus simple est alors de générer les pages comme indiqué plus haut, puis d'afficher la source et de la copier/coller dans un fichier séparé. Cette méthode est assez lourde. Voici une alternative permettant de générer le contenu de la page et de l'écrire directement dans un fichier.

Voici un exemple générant offline le fichier index.php :

<?php
$xh = xslt_create();

$file=fopen("cours.xml","r");
$xml=fread($file,16384);
fclose($file);

$file=fopen("index.xsl","r");
$xsl=fread($file,16384);
fclose($file);

$arguments = array(
  '/_xml' => $xml,
  '/_xsl' => $xsl
  );

$result = xslt_process($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);

xslt_free($xh);

$result=str_replace("<php>","<?php",$result);
$result=str_replace("</php>","?>",$result);

ob_start();
$result=eval("?".chr(62).$result.chr(60)."?");
$result=ob_get_contents();
ob_clean();

$file=fopen("index.php","w");
fwrite($file,"$result");
fclose($file);
print "Done";
?>

Comme la fonction eval() de PHP affiche directement le contenu évalué, il nous faut trouver un moyen de le récupérer. Les fonctions de bufferisation de sortie fournies avec le coeur de PHP permettent cela.

La fonction ob_start() indique à PHP de rediriger la sortie standard (normalement la page PHP) vers un buffer. On récupère ensuite dans une variable le contenu du buffer avec la fonction ob_get_content(). La fonction ob_clean() indique a PHP de détruire les buffers utilisés et de rediriger la sortie standard vers la page PHP.

Il ne reste ensuite plus qu'à écrire le buffer récupéré dans un fichier. Si vous désirez générer plusieurs pages offline, vous pouvez utilisez ce type de code dans une boucle.

Conclusion

L'utilisation de XML/XSL avec le mode XSLT est très puissante. Elle permet de générer dynamiquement des pages et de créer des patrons. Modifier l'apparence d'une page créée de cette manière ne nécessite que la modification des fichiers XSL. De plus les copier/coller lourds sont évités même si plusieurs présentations sont proposées. Il faut tout de même comprendre que cette génération dynamique a un coût. La charge processeur du serveur qui génère ces pages sera plus importante. Si ces pages sont souvent parcourues ou si vous ne disposez pas du mode XSLT chez votre hébergeur, vous pouvez installer PHP et XSLT chez vous et générer les pages offline. Vous n'avez ensuite plus qu'à envoyer les pages PHP statiques créées sur votre serveur. De cette manière, vous bénéficiez des avantages de cette technique tout en évitant de trop grosses charges serveur.

Annexe :

Le mode XSLT utilisé : http://www.php.net/manual/fr/ref.xslt.php

Bufferisation de sortie : http://fr.php.net/manual/fr/ref.outcontrol.php

Détails des fonctions : xslt_create xslt_process xslt_free ob_start ob_get_content ob_clean

Référence sur XML : http://www.w3c.org/XML/

Référence sur XSL/XSLT : http://www.w3c.org/Style/XSL/