Tutoriel pour générer du SVG avec du XSLT

De EduTech Wiki
Aller à la navigation Aller à la recherche

Introduction

Ce tutoriel est une traduction de cet article sur EduTechWiki (en). Ce tutoriel vous apprend à générer des graphiques SVG à partir de données XML. Nous produirons du pur SVG et du HTML5 intégré dans le SVG.

Objectifs d'apprentissage

  • Comprendre le but du XSLT, par exemple être capable de penser au XSLT comme étant un langage de traduction.
  • Faire des transformations simples à partir d'un XML à un HTML.
  • Être capable d'utiliser des expressions simples XPath (balise et nom d'attribut) dans les sélecteurs de modèle, pour des éléments et pour l'extraction d'attributs.
Pré requis
Prochaines étapes

Mon premier SVG à partir du XML

Générer du SVG avec du XSLT est réellement simple si vous maitrisez quelques bases de la programmation en XSLT comme il est expliqué dans le Tutoriel XSLT débutant. Bien sûr, vous devriez aussi avoir quelques bases en SVG.

Au moment où cet article est écrit, vous pouvez utiliser le SVG de trois façons différentes sur le Web:

  1. Intégrer du SVG dans du XHTML 1. Cela nécessite que vous utilisiez le XHTML en tant que XML. Si vous ne comprenez pas ce que cela veut dire, passez cette option.
  2. Générer un pur fichier SVG. Tous les navigateurs modernes peuvent l'afficher.
  3. Générer un fichier HTML5 contenant du SVG. Tous les navigateurs modernes peuvent l'afficher.

Exemple SVG niveau 0

Nous allons commencer par montrer comment produire quelques graphiques SVG à partir du contenu d'un document XML simple. Nous montrerons ensuite comment procéder pour du HTML5.

Prenez le fichier XML suivant:

<?xml version="1.0" ?>
<?xml-stylesheet href="zero.xsl" type="text/xsl" ?>
<thing>
  <height>50</height>
  <width>100</width>
</thing>

Nous pouvons traduire ceci en un rectangle. Pour cela, nous devons écrire un template qui va extraire le contenu de la hauteur (height) et de la alrgeur (width), pour ensuite l'utiliser dans la définition des dimensions du rectangle SVG. Les débutants doivent savoir que les noms de ces balises XML importent peu, on aurait aussi pu appeler ces éléments a et b.

Ci-dessous se trouve le code XSLT. Comme vous pouvez le voir, il est assez simple. En ce qui concerne la transformation du XSLT à l'HTML, il y a deux différences:

  • Nous devrions déclarer l'espace (namespace) SVG dans l'élément racine xsl:stylesheet en haut. C'est pourquoi nous avons ajouté cette ligne: xmlns="http://www.w3.org/2000/svg".
  • Nous devrions configurer ce qui est produit en sortie, sinon le navigateur pourrait ne pas vouloir afficher le code SVG. C'est pourquoi nous avons ajouté la commande <xsl:output ... />
<?xml version="1.0"?>

<xsl:stylesheet version="1.0" 
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
		xmlns="http://www.w3.org/2000/svg"
		>
  <xsl:output
      method="xml"
      indent="yes"
      standalone="no"
      doctype-public="-//W3C//DTD SVG 1.1//EN"
      doctype-system="http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
      media-type="image/svg" />
  
  <xsl:template match="thing">
    <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" >
      <rect x="10" y="10" width="{width}" 
	    height="{height}" fill="red" stroke="black"/>  
    </svg>
  </xsl:template>
</xsl:stylesheet>

Maintenant examinons le template qui traite de "thing": <xsl:template match="thing">. Nous n'avons pas besoin d'un template pour l'élément racine ( / ). Dans le code au-dessus, le template XSL pour "thing" va générer la balise SVG au plus haut niveau.

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" >
   .....
</svg>

Ensuite, nous avons simplement à définir un rectangle SVG et définir les valeurs des attributs SVG pour la largeur width et la hauteurheight avec les valeurs que nous avons extrait à partir du XML. En d'autres termes, {width} et {height} vont être remplacés par le texte trouvé entre les balises <width> et <height> dans le document XML.

<rect x="10" y="10" width="{width}" height="{height}" fill="red" stroke="black"/>

Code (peut comprendre de légères variations):

Exemple HTML5 niveau 0

Maintenant produisons le HTML5. Le principe est exactement le même, sauf que la déclaration pour le output est un peu différente et que le template est plus long car nous devons produire plus de code.

<?xml version="1.0"?>

<xsl:stylesheet version="1.0" 
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
		xmlns="http://www.w3.org/2000/svg"
		>
<xsl:output
     method="xml"
     doctype-system="about:legacy-compat"
     omit-xml-declaration = "yes"
     encoding="UTF-8"
     indent="yes" />
  
  <xsl:template match="thing">
   <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
       <meta charset="utf-8"></meta>
       <title>XHTML5 + SVG example</title>
     </head>
     <body>
       <p>This line is HTML, embedded SVG is below. Read the <a
       href="http://edutechwiki.unige.ch/en/XSLT_to_generate_SVG_tutorial">
       XSLT to generate SVG tutorial</a></p>

       <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" >
	 <rect x="10" y="10" width="{width}" 
	       height="{height}" fill="red" stroke="black"/>  
       </svg>
     </body>
   </html>
  </xsl:template>
  
</xsl:stylesheet>

Code (peut comprendre de légères variations):

Visualisation de nombres

L'exemple suivant (cliquez ici pour voir le résultat) montre comment remplacer un nombre par une barre qui apparait dans un élément de la liste.

Dans le XML ci-dessous, nous aimerions montrer les contenus de la balise age de façon plus intéressante:

<?xml version="1.0"?>
<?xml-stylesheet href="one-html5.xsl" type="text/xsl"?>
<project>
 <title>Simple XML to SVG demo</title>
 <people>
  <person>
    <FirstName>Kaspar</FirstName>
    <age>50</age>
    <description>A small man with a green tie</description>
  </person>
  <person>
    <FirstName>Jonathan</FirstName>
    <age>9</age>
    <description>A young person</description>
  </person>
  <person>
   <FirstName>Julie</FirstName>
   <age>30</age>
   <description>A young woman</description>
  </person>
 </people>
</project>

Le XSLT utilise simplement "des règles basées" sur ce qui a été codé dans le fichier XSLT. Le seul template qui nous intéresse concerne la balise age. En outre, nous ajoutons un peu de CSS pour modifier l'apparence des balises HTML, par exemple les boites div avec une bordure, et nous laisserons la balise racine SVG flotter sur la droite.

Tout d'abord, nous stockons la valeur de l'élément en question (c'est-à-dire, les contenus) de l'âge dans une variable appelée years. Ce n'est pas nécessaire ici, mais cela permet de simplifier des expressions XPath complexes. Remarquez qu'une variable est utilisée dans une expression XPath avec un signe $ juste avant, par exemple $years.

 <xsl:variable name="years" select="."/>

La partie SVG ressemble donc au code ci-dessous. Remarquez comment la hauteur du rect (rectangle) est calculée: l'âge multiplié par 1.2 ({$years*1.2}).

   <svg style="background-color:yellow;float:right" width="20" height="100" 
	xmlns:xlink="http://www.w3.org/1999/xlink" 
	xmlns="http://www.w3.org/2000/svg">
     <rect x="5px" y="5px" height="{$years*1.2}" width="10px" fill="green" />
   </svg>

Ci-dessous le code XSLT complet:

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

<xsl:output
     method="xml"
     doctype-system="about:legacy-compat"
     omit-xml-declaration = "yes"
     encoding="UTF-8"
     indent="yes" />

 <xsl:strip-space elements="tasks participants"/>

 <xsl:template match="/">
   <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
       <meta charset="utf-8"></meta>
       <title>XHTML5 + SVG example</title>
     </head>
     <body>
       <p>XHTML5 contents below are generated from XML with XSLT. 
       Contents of and age elements are display in both text and with SVG as a green bar.
       Look at the source of this page for the XML and at the <a
       href="http://tecfa.unige.ch/guides/svg/ex/html5-xslt/one-html5.xsl">one-html5.xsl</a>
       file. Read the <a href="http://edutechwiki.unige.ch/en/XSLT_to_generate_SVG_tutorial">
       XSLT to generate SVG tutorial</a></p>
       <hr/>
       <xsl:apply-templates/>
     </body>
 </html>
 </xsl:template>

 <xsl:template match="people">
   <h1>People</h1>
   <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="person">
   <div style="border-style:dotted;margin:10px;padding:5px;width:200px;height:100px;">
     <xsl:apply-templates/>
   </div>
 </xsl:template>

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

 <xsl:template match="age">
   Age: <xsl:apply-templates/>
   <xsl:variable name="years" select="."/>
   <svg style="background-color:yellow;float:right" width="20" height="100" 
	xmlns:xlink="http://www.w3.org/1999/xlink" 
	xmlns="http://www.w3.org/2000/svg">
     <rect x="5px" y="5px" height="{$years*1.2}" width="10px" fill="green" />
   </svg>
 </xsl:template>

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

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

</xsl:stylesheet>

Fichiers source:

Un exemple plus complexe est montré plus loin sous la section Créer des fiches simples à partir de structures d'informations tabulaires.

Créer un diagramme bâton (bar chart) simple

L'intérêt de l'exemple présenté à la section précédente est relativement limité. Dans les données XML, il y a généralement davantage de nombres. Nous verrons dans cette section ce qu'il est possible de faire avec une simple liste de nombres.

Prenez le fichier xml suivant :

<?xml version="1.0" ?>
<?xml-stylesheet href="intro.xsl" type="text/xsl" ?>
<list>
 <item>10</item>
 <item>15</item>
 <item>12</item>
 <item>20</item>
 <item>5</item>
</list>

Une telle liste rendrait bien avec un diagramme à bâton.

C'est ce que le code ci-dessous permet de faire. Bien sûr c'est un exemple qui reste simple, un code plus complet devrait également calculer les paramètres de la hauteur et de la largeur en prenant en compte nos données et l'espace de dessin que nous voulons utiliser.

La différence entre l'exemple "zéro" et celui-ci est que nous faisons quelques calculs simples et que nous utilisons aussi les fonctions XSLT count() et position().

  • La coordonnée y du rectangle est 100 moins le nombre trouvé dans le XML {100- .}
  y="{100- .}
  • Nous utilisons la fonction count() pour savoir le nombre d'éléments item que nous avons dans la liste (balise list).
    <rect x="10" y="105" width="{10 * count(item)}" 
	  height="5" fill="black" stroke="red"/>
  • Notez également que les espaces vides entre les éléments comptent comme des nœuds selon la norme XML. Par conséquent, vous devez supprimer ces nœuds vides avec l'instruction suivante :
  <xsl:strip-space elements="list"/>
  • Chaque rectangle est positionné dans le SVG en fonction de sa positions dans la liste XML:
    <rect x="{10*position()}" y="{100- .}" width="10" 
	  height="{.}" fill="red" stroke="black"/>

Le code source complet se trouve ci-dessous. Nous ne discuterons pas de la version HTML5 ici, mais vous trouverez ci-dessous est le lien qui vous fournira le code.

<?xml version="1.0"?>
<!-- keep all three namespaces, xlink may not be needed, but the others are -->
<xsl:stylesheet version="1.0" 
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
		xmlns:xlink="http://www.w3.org/1999/xlink"
		xmlns="http://www.w3.org/2000/svg"
		>
  
  <!-- **** output/input configuration -->
  
  <xsl:output
      method="xml"
      indent="yes"
      standalone="no"
      doctype-public="-//W3C//DTD SVG 1.1//EN"
      doctype-system="http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
      media-type="image/svg" />
  
  <!-- must remove white spaces within the list element, 
       otherwise count will not work -->
  <xsl:strip-space elements="list"/>
  
  <xsl:template match="/">
    <svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" >
      <!-- here we could draw a background rectangle -->
      <xsl:apply-templates/>
    </svg>
  </xsl:template>
  
  <xsl:template match="list">
    <rect x="10" y="105" width="{10 * count(item)}" 
	  height="5" fill="black" stroke="red"/>
    <xsl:apply-templates/>
  </xsl:template>
  
  <xsl:template match="item">
    <rect x="{10*position()}" y="{100- .}" width="10" 
	  height="{.}" fill="red" stroke="black"/>  
  </xsl:template>
  
</xsl:stylesheet>

Les exemples (avec un rectangle en arrière plan inclus)

Nous pourrions aussi produire une version pour les programmeurs effrayés par la programmation basée sur des règles. Pour cela, vous pouvez utiliser le code comme ceci:

<xsl:template match="list">
 <xsl:for-each select="item">
    <rect x="{10*position()}" y="{100- .}" width="10" height="{.}" 
          fill="red" stroke="black"/>            
  </xsl:for-each>
  </xsl:template>

Exemple:

Créer un diagramme bâton simple et flexible

Capture d'écran du XML au diagramme bâton SVG

L'exemple précédent présente un défaut : la hauteur et la largeur des barres ne s'ajustent pas automatiquement à:

  • La taille du canevas SVG
  • Le nombre d'items
  • La valeur maximum d'un item

Le résultat devrait ressembler à l'image à droite. Le diagramme bâton devrait combler le canevas fournis par le SVG. Bien sûr nous voulons aussi être capable de traiter n'importe quel ensemble (raisonnable) de nombre positifs, c'est-à-dire que le fichier XML pourrait inclure plus ou moins d'items et aussi des valeurs différentes.

Comme cet exemple inclus quelques éléments un peu plus complexes, et introduit des variables XSLT et des traitements de listes, vous pouvez le passer et y revenir plus tard.

Premièrement, nous allons permettre aux utilisateurs de cette feuille de style de définir la taille du canevas avec des variables XSLT.

  <!-- you could change these in any way you like -->
  <xsl:variable name="svg_width" select="400"/>
  <xsl:variable name="svg_height" select="300"/>
  <xsl:variable name="padding" select="5"/>
Remarque: Les variables XSLT ne sont pas vraiment des variables, par exemple vous ne pouvez pas réaffecter une valeur dans le même contexte une fois qu'elle a été fixée durant le temps d'exécution. Par exemple, l'instruction suivante:
<xsl:variable name="qual" select="engagement"/>

va lier le contenu de l'élément '^'engagement à une variable qui peut être rappelée en utilisant le signe $, par exemple $qual dans notre cas. À chaque fois que le XSLT exécute un template, un nouveau $qual lié sera créé.

Retour à notre projet. Sachant que la taille du canevas est une des informations dont nous avons besoin pour manipuler la largeur, la hauteur et la position des bâtons. Nous devons ensuite déterminer combien d'items nous avons obtenu. L'expression suivante va calculer la largeur d'un bâton comme étant la largeur du canevas SVG en soustrayant un peu de marge, divisé par le nombre d'items:

  <!-- x-width with respect to N elements -->
  <xsl:variable name="x_width"
		select="($svg_width - 2*$padding) div count(//list/item)"/>

Nous avons aussi besoin d'une sorte de "y-step" qui, multiplié par le nombre dans la balise item, va définir la hauteur du bâton. Cela va dépendre du nombre le plus grand contenu dans l'ensemble des balises item.

Trouver un maximum avec XSLT 1.0 est un casse-tête. Typiquement, pour de tels problèmes, vous pouvez Google pour une réponse ou prendre par exemple une solution provenant d'un site Web comme Stack Overflow.

L'expression suivante va calculer la hauteur du bâton:

  <xsl:variable name ="y_steps">
    <xsl:for-each select="//list/item">
      <xsl:sort select="." data-type="number" order="descending"/>
      <xsl:if test="position() = 1">
	<xsl:value-of select="($svg_height - 2*$padding) div ."/>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

Le reste est maintenant simple. Voici le template pour item:

  <xsl:template match="item">
    <rect x="{$padding + $x_width * (position() - 1) }"
	  y="{($svg_height - $padding) - $y_steps * .}" 
	  width="{$x_width}" 
	  height="{. * $y_steps}"
	  fill="red" stroke="black"/>  
  </xsl:template>

Exemple:

Exemple W3C depuis la spécification XSLT

La spécification XSLT 1.0 inclut un exemple de diagramme bâton que nous avons légérement modifié et adapté au HTML5. Cet exemple montre comment produire du texte en sortie, utilise un seul template avec une construction "for-each" ("pour chaque" élément) à la place de templates qui définissent comment traduire les éléments XML.

Créer des cartes simples à partir d'informations tabulaires

Regardez d'abord le résultat:

Ce fichier permet de visualiser une structure d'information simple pour des projets. Le fichier XML inclut principalement deux listes:

  • participants: Chaque participant a un prénom, un niveau d'engagement et une description
  • tasks: Chaque task (tâche) inclut un titre, une description, un members (membre) qui renvoie aux participants, un niveau de difficulté et un niveau d'achèvement, une liste d'idées et une liste de résultats.

Extrait du fichier XML:

<?xml version="1.0"?>
<?xml-stylesheet href="xpath-jungle-links-1.xsl" type="text/xsl"?>
<project>
 <title>The XSLT and SVG project</title>
 <description>This project will explore some simple means to generate SVG from XSLT</description>
 <participants>
  <participant id="p1">
    <FirstName>Daniel</FirstName>
    <engagement>4</engagement>
    <description>Daniel will be the project manager</description>
  </participant>
  ..........
 </participants>
 <tasks>
  <task id="t1">
    <title>Initial task</title>
    <description>Project preparation</description>
    <members><member idref="p1"/><member idref="p2"/></members>
    <difficulty level="3">This task should not be too hard</difficulty>
    <completion level="8">Fairly well understood so far</completion>
    <ideas>
      <item val="low">Buy a XSLT book</item>
      <item val="low">Buy an SVG book</item>
      <item val="high">Do some exploratory tiny examples</item>
    </ideas>
    <results>
      <result><a href="http://edutechwiki.unige.ch/en/XSLT_to_generate_SVG_tutorial">Wiki page</a> started</result>
      <result><a href="http://edutechwiki.unige.ch/en/XPath_tutorial_-_basics">XPath tutorial revised</a></result>
    </results>
  </task>
  ......... 
 </tasks>
</project>

Ci-dessous se trouvent les deux fragments XSLT les plus importants, et qui mériteraient de plus amples explications:

 <xsl:template match="participant">
   <div style="float:left;border-style:dotted;margin:10px;padding:5px;width:200px;height:200px;">
     <xsl:apply-templates select="FirstName"/>
     <xsl:variable name="qual" select="engagement"/>
     <svg style="background-color:yellow" width="100" height="100" 
	  xmlns:xlink="http://www.w3.org/1999/xlink" 
	  xmlns="http://www.w3.org/2000/svg">
       <circle id="greencircle" cx="{$qual*5}" cy="{$qual*5}" r="{$qual*5}" fill="green" />
     </svg>
     <xsl:apply-templates select="engagement"/>
     <xsl:apply-templates select="description"/>
   </div>
 </xsl:template>
 <!-- task - programmed as a big block for change -->
 <xsl:template match="task">
   <div style="float:left;border-style:solid;margin:10px;padding:5px;width:300px">
     <!-- title -->
     <p style="font-size:120%;">    
       <xsl:value-of select="position()"/>.
       <xsl:value-of select="title"/>
     </p>
     <p><xsl:value-of select="description"/></p>

     <!-- difficulty -->
     <p>Difficulty: <xsl:value-of select="difficulty"/></p>     
     <svg width="210" height="10" 
	  xmlns:xlink="http://www.w3.org/1999/xlink" 
	  xmlns="http://www.w3.org/2000/svg">
       <rect x="5" y="1" width="{difficulty/@level * 20}" height="5" fill="red" />
       <rect x="4" y="0" width="201" height="6" fill="none" stroke="black" />
     </svg>
     
     <!-- completion -->
     <p>Completion: <xsl:value-of select="completion"/></p>
     <svg width="210" height="10" 
	  xmlns:xlink="http://www.w3.org/1999/xlink" 
	  xmlns="http://www.w3.org/2000/svg">
       <rect x="5" y="1" width="{completion/@level * 20}" height="5" fill="red" />
       <rect x="4" y="0" width="201" height="6" fill="none" stroke="black" />
     </svg>
     <xsl:apply-templates select="ideas"/>
     <xsl:apply-templates select="results"/>
   </div>
 </xsl:template>

Fichiers source:

Travaux en cours

(Certains des exemples ci-dessous ont besoin de davantage de documentation, et d'autres ne fonctionnent pas)

En cours

Projet de visualisation:

Autres:

  • XSLT use with PHP 5. Cet exemple montre comment appliquer une feuille de style (stylesheet) XSLT à un fichier XML, afin de produire un SVG avec PHP5. Pour cela, PHP5 doit pouvoir lire le XML et le XSLT dans un objet DOM.

Cercles

Liens

Tutoriels

Tutoriels obsolètes

Les liens ci-dessous pointent vers des textes qui contiennent des informations utiles car ils tentent des choses plus ambitieuses. Cependant, certaines parties sont obsolètes, en particulier les plaintes concernant les mauvaises implémentations de SVG et de l'utilisation du XSLT côté serveur à la place de son utilisation côté client. Le code XSLT en soit me semble correct, mais je ne suis pas un expert que ce soit en SVG ou en XSLT - Daniel K. Schneider (talk) 19:00, 20 March 2013 (CET)

Bon exemples