Accueil

Exemples XSLT

Afficher plein écran
 
Emmanuel Lazinier
Création de la page : 2000-09-06
Dernière modification : 2000-10-17

Transformer en XML un fichier à valeurs séparées par virgules (comma separated values)... ou tous autres séparateurs (XSLT 1.1)

Le fichier suivant, copie en réduction d'un exemple réel récupéré sur le Web sur un site d'astronomie, se compose de lignes, elles mêmes constituées de données élémentaires séparées par des espaces, lesdits espaces étant au demeurant en nombre variable.
  1    HD 334   181  174  182  178  184  195
  2    HD 896   219  218  222  222  233  249
  3    HD 1094   293  295  293  299  302  323
  4    HD 1352   338  339  342  343  349  355
  5    HD 1832   173  175  192  182  192  208
  6    HD 7193   491  494  508  508  514  538
  7    HD 7805   143  140  137  143  141  152
Le but de l'exercice est de transformer ce fichier en XML ayant la structure suivante :
<doc>
 <ligne>
  <no-ligne/>
  <data-1/>
  <data-2/>
  <data-3/>
  <data-4/>
  <data-5/>
  <data-6/>
  <data-7/>
 </ligne>
 <ligne>
    .....
 </ligne>
.....
</doc>
Notre feuille de transformation XSLT s'inspire librement d'un exemple publié par Eric van der Vlist sur la liste xml-tech de son site <?XML?>fr.

Au préalable on aura transformé le fichier source en fichier XML bien formé en l'encadrant  par des balises <doc> et </doc> et en ajoutant en tête une processing instruction XML : <?xml version="1.0"?>
 
 La première partie de notre feuille de transformation XSLT va consister en déclaration de paramètres :

<!-- séparateur par défaut : 2 espaces -->
<xsl:param name="separateurdefaut" select="'  '"/>

<!-- modèle du XML à obtenir -->
<xsl:param name="modele">
<doc>
 <ligne separateur="&#xA;" repetable="oui"> <!-- séparateur : retour ligne -->
  <no-ligne separateur="    "/> <!-- séparateur : 4 espaces -->
  <data-1 separateur=" "/> <!-- séparateur : 1 espace -->
  <data-2 separateur="   "/> <!-- séparateur : 3 espaces -->
  <data-3/>
  <data-4/>
  <data-5/>
  <data-6/>
  <data-7/>
 </ligne>
</doc>
</xsl:param>

Le premier paramètre définit un séparateur par défaut, en l'occurence l'espace double.

Le deuxième paramètre (modele) est un fragment XML qui décrit la structure du fichier XML que l'on souhaite obtenir en sortie. Sur les éléments de ce fragment on a spécifié, chaque fois que nécessaire, sous forme d'attributs :

Note. Aux termes de la spécification XSLT 1.0, le paramètre modele tel que déclaré ici est un fragment XML et n'est pas considéré comme constituant du XML bien formé. A ce titre il est aujourd'hui illégal de naviguer, comme nous allons le faire, à l'aide d'expressions XPath à l'intérieur de ce fragment. C'est sans doute pourquoi Eric van der Vlist a choisi d'insérer ce modèle directement à l'intérieur de sa feuille de transformation en le distinguant du reste par un espace de nommage (namespace) différent. Nous n'avons pas suivi cette façon de faire un peu complexe parce que :
  1. Il se trouve que le parseur MSXML 3.0, que nous avons utilisé :
    1. acceptait cet usage illégal de fragment XML comme XML bien formé dans sa version de juillet 2000 que nous avons utilisée au départ
    2. ne l'accepte plus dans sa version de septembre 2000, mais permet désormais de contourner cette interdiction par l'utilisation de la fonction d'extension propriétaire msxsl:node-set(). Voir la Feuille de transformation XSLT modifiée en conséquence
  2. Il est très probable que cette illégalité disparaîtra dès la version 1.1 de XSLT. Voir XSL Transformations Requirements Version 1.1
Voir aussi  Getting Value from XSL Parameters, par Kurt Cagle.
 
Viennent ensuite les templates qui sont seulement au nombre de deux.

Un premier template va naviguer sur les éléments du fichier source. (Notre exemple n'en comporte qu'un mais notre feuille de style est d'usage général et pourrait fonctionner pour "sausissonner" un ou plusieur éléments à l'intérieur d'une page XML, ou XHTML, complexe)

Ce template va tester si l'élément du fichier source à le même nom qu'un élément du fragment modele. Si ce n'est pas le cas il va simplement reproduire l'élément. Si c'est le cas, par contre, il va en déduire qu'il s'agit d'un élément à saucisonner et il va, après avoir reproduit l'élément, passer en mode "saucisonne" et donner la main à notre deuxième template qui, lui, va naviguer sur le modèle et non plus sur le fichier source. Et à ce deuxième template il va transmettre en paramètre la chaîne de caractère à découper, autrement dit le contenu de l'élément. (dans notre exemple il s'agira du contenu de la balise <doc> donc de l'ensemble du fichier d'origine.)

<xsl:template match="*">
<!-- Navigue sur les éléments du fichier *source* -->
<xsl:copy>
<xsl:choose>

<xsl:when test="$modele/*[name()=name(current())]">
<!-- si dans le modele il y a un élément de même nom, on lance le saucissonnage de son contenu (passé en paramètre) en naviguant sur le *modèle* -->
 <xsl:apply-templates  select="$modele/*[name()=name(current())]/*[position()=1]"  mode="saucissonne">
  <xsl:with-param name="chaine" select="."/>
 </xsl:apply-templates>
</xsl:when>

<xsl:otherwise>
<!-- si dans le modele il n'y a pas d'élément de même nom, on recopie simplement -->
 <xsl:apply-templates select="node()|@*"/>
</xsl:otherwise>

</xsl:choose>
</xsl:copy>
</xsl:template>

Notre second template, lui, va donc naviguer sur le modèle. Il va le parcourir élément pas élément et à chaque fois créer dans le fichier résultat un élément de mêm nom. Dans cet élément il va insérer le premier tronçon (délimité par le séparateur par défaut ou spécifique) de la chaîne de caractères qu'il a reçue en paramètre. Tant que les éléments du modèle n'auront pas été épuisé, ce template va se rappeler lui-même en se transmettant le reste de la chaîne de caractère qu'il vient de découper. Ce rappel récursif a trois modalités différentes (non exclusives): Dans les deux derniers cas il n'est pas nécessaire de prévoir un test de fin de recursion, puisque la récursion prendra fin avec l'épuisement des éléments du modèle. Dans le premier cas ce test est indispensable pour ne pas boucler indéfiniment.
<xsl:template match="*" mode="saucissonne">
<!-- template de saucisonnage. Navigue sur le modèle et non le fichier source, la chaîne à découper étant passée en paramètre -->

<xsl:param name="chaine"/>

<!-- détermine le séparateur à utiliser -->
<xsl:variable name="separateur">
<xsl:choose>
<xsl:when test="@separateur">
 <xsl:value-of select="@separateur"/>
</xsl:when>
<xsl:otherwise>
 <xsl:value-of select="$separateurdefaut"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<!-- détermine la rondelle à découper -->
<xsl:variable name="rondelle">
<xsl:choose>
<xsl:when test="contains($chaine, $separateur)">
 <xsl:value-of select="substring-before($chaine, $separateur)"/>
</xsl:when>
<xsl:otherwise>
 <xsl:value-of select="$chaine"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<!-- détermine le reste du saucisson -->
<xsl:variable name="reste">
<xsl:value-of select="substring-after($chaine, $separateur)"/>
<!-- le reste est vide si pas de séparateur dans la chaîne -->
</xsl:variable>

<xsl:copy>
<xsl:choose>
<xsl:when test="child::*">
<!-- si l'élément à des enfants, leur renvoyer la balle -->
 <xsl:apply-templates select="child::*[position()=1]" mode="saucissonne">
  <xsl:with-param name="chaine" select="$rondelle"/>
 </xsl:apply-templates>

</xsl:when>
<xsl:otherwise>
<!-- si l'élément n'a pas d'enfant, y mettre la rondelle -->
 <xsl:value-of select="normalize-space($rondelle)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>

<!-- relancer le processus de saucisonnage sur les éléments suivants du modèle -->
<xsl:if test="string-length($reste) > 0"> <!-- ce test est facultatif : il supprime d'éventuels éléments vides -->
 <xsl:apply-templates select="following-sibling::*[position()=1]" mode="saucissonne">
  <xsl:with-param name="chaine" select="$reste"/>
 </xsl:apply-templates>
</xsl:if>

<!-- si l'élément est répétable, relancer sur le *même* élément -->
<xsl:if test="(@repetable='oui')and(string-length($reste) > 0)">
<!-- ici le test sur la longueur de chaîne restante est indispensable pour arrêter la boucle -->
 <xsl:apply-templates select="." mode="saucissonne">
  <xsl:with-param name="chaine" select="$reste"/>
 </xsl:apply-templates>
</xsl:if>
</xsl:template>

Un troisième template pourrait être ajouté pour reproduire à l'identique les éventuels attributs du fichier origine (voir l'exemple d'Eric van der Vlist). Nous l'avons supprimé dans un but de simplification et parce qu'il était dans notre cas inutile.
 
Voir/Télécharger les fichiers de l'exemple :


Contribuez ! Faites-nous profiter de vos exemples les plus significatifs.

Retour à la page d'accueil.