Accueil
 chercher             Plan du site             Info (English version) 
L'histoire de XML s'écrit en ce moment même. XMLfr vous aide à la suivre et à en dégager les tendances.Les listes de discussions XMLfr sont à votre disposition pour réagir sur nos articles ou simplement poser une question.Si vous ètes passionnée(e) par XML, pourquoi ne pas en faire votre métier ?XMLfr n'est heureusement pas le seul site où l'on parle de XML. Découvrez les autres grâce à XMLfr et à l'ODP.Les partenaires grâce auxquels XMLfr peut se développer.Pour tout savoir sur XMLfr.XMLfr sans fil, c'est possible !Pour ceux qui veulent vraiment en savoir plus sur XML.L'index du site.
 Si vous vous posez une question, vous n'êtes peut-être pas le premier...Les traductions en français des bibles XML.Ces articles sont des références dans leur domaine.Tout ce qu'il faut savoir pour démarrer sur un sujet XML...


Parser un nœud texte en XML avec XSLT

Comment transformer en XML un nœud texte dans lequel les caractères de balisages sont représentés par des références à des entités prédéfinies (« & », « < », « > », ...)?

Eric van der Vlist, Dyomedea (vdv@dyomedea.com).
mercredi 11 octobre 2006

Table des matières

Introduction

Utilisation de disable-output-escaping="yes"

Facile à mettre en œuvre

Mais pas universelle

Et limitée

Contournements

Côté serveur

Côté client

Conclusion

Introduction

Certains vocabulaires XML tels que RSS 1.0 permettent d'encoder des fragments XHTML (ou HTML) sous forme de texte. C'est le cas par exemple dans le fragment de flux RSS 1.0 suivant (extrait du canal RSS du carnet Web de Karl Dubost publié sous licence CC by-nc-sa) :

<content:encoded>&lt;p xmlns="http://www.w3.org/1999/xhtml"
  class="resume"&gt;L'&lt;a href="http://www.w3.org/QA/" 
  shape="rect"&gt;activité QA du W3C&lt;/a&gt; vient de lancer un
e 
  première version de Unicorn. C'est un nouvel outil de 
  communication pour faciliter le processus de vérification de qu
alité 
  d'un site Web. Le système fonctionne comme une gare de triage. 
  Le produit accepte des ressources, les envoie à des observateur
s 
  (validateurs par exemple), collecte les résultats et renvoie le
 
  résultat compilé. Le tout fonctionne grâce à des services Web e
t 
  donc il n'est pas nécessaire de développer un module pour 
  unicorn, mais plutôt le service Web qui permettra de communique
r
  avec ce dernier. Le projet est open source et il est ouvert à 
  participation de tous.&lt;/p&gt;&lt;p 
  xmlns="http://www.w3.org/1999/xhtml" class="cat"&gt;W3C,
  validateur, qualité, QA&lt;/p&gt;</content:encoded>

Comment traiter ce type de contenu avec XSLT pour générer une page (X)HTML?

Utilisation de disable-output-escaping="yes"

Facile à mettre en œuvre

La première solution pour traiter ce type de contenu est d'utiliser l'attribut disable-output-escaping="yes".

L'attribut disable-output-escaping peut être placé sur les élements xsl:text et xsl:value-of pour demander à un processeur XSLT de ne pas remplacer les caractères de balisage par des références à des entités prédéfinies. Dans notre cas, il suffit donc d'écrire :

<xsl:value-of select="content:encoded" disable-output-escaping="y
es"/>

[encodage-preservespace.xsl] pour que l'élément en question soit « décodé » et transformé en :

<p xmlns="http://www.w3.org/1999/xhtml" class="resume">L'<a
  href="http://www.w3.org/QA/" shape="rect">activité QA du 
  W3C</a> vient de lancer une première version de Unicorn.
  C'est un nouvel outil de communication pour faciliter le 
  processus de vérification de qualité d'un site Web. Le 
  système fonctionne comme une gare de triage. Le produit 
  accepte des ressources, les envoie à des observateurs 
  (validateurs par exemple), collecte les résultats et renvoie
  le résultat compilé. Le tout fonctionne grâce à des services
  Web et donc il n'est pas nécessaire de développer un module 
  pour unicorn, mais plutôt le service Web qui permettra de 
  communiquer avec ce dernier. Le projet est open source et il 
  est ouvert à participation de tous.</p>
  <p xmlns="http://www.w3.org/1999/xhtml" class="cat">W3C, 
  validateur, qualité, QA</p>

Mais pas universelle

Cette solution pose hélas un certain nombre de problèmes. Cette fonctionnalité a été introduite comme à regret dans XSLT 1.0 et la recommandation écrit :

"Désactiver la production littérale des caractères sur un nœud textuel utilisé pour autre chose (qu'un nœud textuel) dans l'arbre de sortie est une erreur. Aussi, désactiver la production littérale sur l'élément xsl:value-of ou xsl:text qui est utilisé pour produire un commentaire, une instruction de traitement ou un nœud d'attribut est une erreur; c'est également une erreur que de convertir un fragment de l'arbre résultat en un nombre ou une chaîne si ce fragment contient un nœud textuel pour lequel la production littérale a été désactivée. Dans les deux cas, un processeur XSLT peut signaler une erreur; s'il ne le fait pas, il doit la rattraper en ignorant l'attribut disable-output-escaping."

Dans notre cas, nous générons des nœuds de type élément et texte ainsi que des attributs en utilisant cette fonctionnalités. Suivant l'interprétation de ce paragraphe, un processeur XSLT est en droit de considérer que c'est une erreur et soit générer une erreur soit simplement ignorer notre attribut disable-output-escaping.

Les versions récentes de Saxon, Xalan, libxslt et MSXML ont une lecture plus libérale de la recommandation et réagissent comme nous le souhaitons. Par contre les processeurs XSLT de Firefox et Opera ignorent silencieusement l'attribut disable-output-escaping et cette méthode ne peut donc pas être utilisée de manière fiable dans des transformations effectuées dans les navigateurs Web.

Et limitée

Cette méthode permet uniquement de recopier les nœuds dans le document résultat et elle ne permet pas d'y accéder pour les modifier ou rechercher des informations. Ainsi par exemple, dans le canal RSS de Karl Dubost, les images utilisent des URIs relatives, de la forme :

<img src="/2006/09/10-cafe" alt="Montage de photos prises dans un
 café"/>

Si l'on ne fait rien pour résoudre ce problème, les images ne seront donc pas visibles sur la page XHTML que nous générons. On peut certes utiliser l'élément XHTML base et préciser que l'URI de base est la page d'accueil du carnet de Karl Dubost mais cela impose de préciser les adresses de toutes les autres ressources de manière absolue et cela ne serait pas généralisable à une page qui réunirait plusieurs canaux RSS posant le même problème. On peut donc préférer au contraire transformer les adresses des images en adresses relatives. Ce serait facile si le contenu était exprimé en XML, c'est beaucoup plus complexe s'il est encodé comme c'est le cas ici.

Contournements

Les solutions de contournement sont différentes suivant que l'on se trouve sur un serveur ou dans un navigateur.

Côté serveur

La technique côté serveur consiste à écrire les fragments à décoder dans des documents séparés en utilisant la méthode de sortie « text » pour éviter que les caractères de balisage ne soient encodés. Cette écriture pourra se faire en utilisant une extension XSLT 1.0 ou en XSLT 2.0 en utilisant l'instruction xsl:result-document. Les fragments sont ensuite lus en tant que documents XML avec la fonction document() et peuvent être manipulés comme on le souhaite.

Pour modifier les adresses des images, on pourra par exemple écrire :

  <xsl:template match="rdf:li">
    <xsl:for-each select="key('items', @rdf:resource)">
      <div>
        <h2>
          <xsl:value-of select="rss:title"/>
        </h2>
        <xsl:variable name="tmpfile" select="concat('file:///tmp/
fragment', generate-id(), '.xml')"/>
        <exslt:document href="{$tmpfile}" method="text" encoding=
"utf-8">
          <xsl:text><![CDATA[<root>]]></xsl:text>
          <xsl:value-of select="content:encoded"/>
          <xsl:text><![CDATA[</root>]]></xsl:text>
        </exslt:document>
        <xsl:apply-templates select="document($tmpfile)/root/*" m
ode="content"/>
      </div>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="@*|node()" mode="content">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" mode="content"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@src[starts-with(., '/')]" mode="content">
    <xsl:attribute name="href">
      <xsl:value-of select="concat('http://www.la-grange.net', .)
"/>
    </xsl:attribute>
  </xsl:template>

[encodage-document.xsl]

On notera que les noms de fichiers temporaires ne doivent pas être réutilisés dans une transformation. En effet, la plupart des processeurs XSLT « cachent » la lecture des documents externes et risquent de réutiliser la première version du document à la place des versions suivantes.

Côté client

Lorsque la transformation s'exécute côté client dans un navigateur, pour des raisons de sécurité évidentes, la transformation ne peut pas enregistrer de documents. La méthode précédente n'est donc pas applicable. Par contre, le document généré peut utiliser JavaScript pour se modifier. Le contournement consistera donc à générer un document HTML dans lequel le contenu ne sera pas nécessairement décodé et à effectuer le décodage en JavaScript.

La transformation XSLT pourra être :

  <xsl:template match="rdf:li">
    <xsl:for-each select="key('items', @rdf:resource)">
      <div>
        <h2>
          <xsl:value-of select="rss:title"/>
        </h2>
        <div class="encoded">
          <xsl:value-of select="content:encoded"/>
        </div>
      </div>
    </xsl:for-each>
  </xsl:template>

[encodage-js.xsl]

L'attribut « class » permet d'identifier les éléments qu'il faudra décoder en JavaScript.

La fonction JavaScript qui permet de décoder ces éléments div est la suivante :

function decode () {
  var divs = document.getElementsByTagName('div');
  for(var i = 0; i < divs.length; i++) { 
    var div = divs[i];
    if (div.className == "encoded") {
      div.innerHTML = div.firstChild.nodeValue;
      changeBaseUrl(div);
    }
  }
}

[encodage.js]

Cette fonction boucle sur tout les éléments div, et décode ceux dont la classe est « encoded ». Le décodage lui-même se fait en copiant le nœud texte dans la propriété « innerHTML » de l'élément, ce qui a comme effet de parser le texte comme si c'était un fragment de HTML.

La fonction appelle ensuite la fonction changeBaseUrl pour modifier les URIs des images. Cette fonction est la suivante :

function changeBaseUrl(e) {
   if (e.nodeName.toLowerCase() == "img") {
   var src = e.src;
   e.src = "/";
   if (src.indexOf(e.src) == 0) {
      e.src = "http://www.la-grange.net/" + src.substring(e.src.l
ength);
    } else {
      e.src = src;
    }
  } else {
    if (e.hasChildNodes)
      for (var i = 0; i < e.childNodes.length; i++ ) {
        changeBaseUrl(e.childNodes[i]);
      }
  }
}

[encodage.js]

Elle boucle de manière récursive sur tout les nœuds enfants et lorsqu'elle rencontre une image, elle fait de son mieux pour tester si son adresse est relative. Internet Explorer convertit systématiquement les URIs relatives en URIs absolues, que ce soit dans la propriété src ou dans l'attribut correspondant et le principe du test consiste à regarder si l'URI de l'image commence de la même manière que l'URI relative « / » convertie en URI absolue.

Lorsque c'est le cas, l'URI est modifiée pour pointer sur le carnet Web de Karl Dubost.

Cette transformation et le script qui l'accompagne ont été testés sur Firefox, Opera et Internet Explorer.

Conclusion

Manipuler des fragments XML encodés sous forme de texte (au moyen de sections CDATA ou en utilisant les entités prédéfinies) en XSLT n'est pas possible de manière fiable et générique.

Il existe néanmoins des solutions de contournement dont le choix est guidé par le fait que l'on doive ou non accéder au contenu des fragments XML, que l'on dispose d'un processeur XSLT qui accepte l'attribut disable-output-escaping="yes" dans ce contexte et que l'on exécute la transformation côté client ou côté serveur.

Ces solutions de contournement restent cependant lourdes à mettre en œuvre et il est préférable d'éviter d'utiliser des documents XML contenant des fragments XML encodés sous forme de texte.

Dans le cas de figure de RSS 1.0, il est possible pour cela d'utiliser l'attribut rdf:parseType= ce qui permet d'éviter totalement le problème :

    <rss:description rdf:parseType="literal" 
      xmlns="http://www.w3.org/1999/xhtml">
      <p class="resume">L'<a href="http://www.w3.org/QA/" 
        shape="rect">activité QA du W3C</a> vient
        de lancer une première version de Unicorn. 
        .../...</p>
      <p class="cat">W3C, validateur, qualité, QA</p>
    </rss:description>

Atom offre également la possibilité d'inclure des fragments XHTML sans encodage mais RSS 2.0 ne le permet pas.

Les exemples sont téléchargeables : [archive]

Copyright 2006, Eric van der Vlist


 

Mots clés.



L'histoire de XML s'écrit en ce moment même. XMLfr vous aide à la suivre et à en dégager les tendances.


Les documents publiés sur ce site le sont sous licence "Open Content"
Conception graphique
  l.henriot  

Conception, réalisation et hébergement
Questions ou commentaires
  redacteurs@xmlfr.org