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 va vous apprendre à 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 reectangle. 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.
  • Nous devrions configurer ce qui est produit en sortie, sinon votre navigateur pourrait ne pas vouloir afficher le code SVG.
<?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 avec 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 width et height 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 quelques nombres

L'exemple suivant (regardez ici) 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.

Premièrement, nous enregistrons la valeur de l'élément (courant) par exemple le contenu de la balise age dans une variable appelée years. Ce n'est pas nécessairement le cas 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 à ça. Remarquez comment la hauteur du rect est calculée (age*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'exemple que nous avons introduit au-dessus était inintéressant. Dans les données XML on trouve souvent plus que juste deux nombres. Alors voyons ce que nous pouvons faire avec une liste simple 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:

Le code suivant va faire ça. Bien sûr cela reste simple. Par exemple un bon code good code devrait aussi 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.
  y="{100- .}
  • Nous utilisons la fonction count() pour savoir le nombre de balises item que nous avons dans la balise list.
    <rect x="10" y="105" width="{10 * count(item)}" 
	  height="5" fill="black" stroke="red"/>
  • Veuillez aussi remarquer s'il vous plait que les espaces vides entre les éléments comptent comme des noeuds selon le standard XML. Donc vous devez supprimer ces noeuds vides avec l'instruction suivante.
  <xsl:strip-space elements="list"/>
  • Chaque rectangle est positionné dans le SVG en accord avec 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 en-dessous se trouve un 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. Vous utiliseriez 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 a le défaut que l'hauteur et la largeur des bâtons ne s'ajuste 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, par exemple le fichier XML devrait inclure plus ou moins d'items et aussi des valeurs différentes.

Comme cet exemple inclus quelques éléments un peu plus complexes, 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. Alors nous devons savoir combien d'items nous avons. L'expression suivante va calculer la largeur d'un bâton comme étant la largeur du canevas SVG moins 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, j'utiliserais google pour une réponse et finirais par prendre une solution provenant d'un site Web cool 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. Nous montrons juste 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. Cela démontre comment produire du texte en sortie et cela utilise un seul template avec un "for-each" à la place de templates qui définissent comment traduire les éléments XML.

Créer des fiches simples à partir de structures d'informations tabulaires

Regardez en premier le résultat:

Ce fichier visualise 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 pointe des 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 ont juste besoin de documentation et ou de quelques mises au point ... d'autres ne marcheront pas)

... plus de vieilles choses que je devrais régler

En cours

Des choses vieilles de 12 ans que je devrais régler un jour (projet de visualisation)

Variés (vieux d'environ 8 ans, sortes d'œuvres ...)

Cercles

Liens

Tutoriels

Tutoriels obsolètes Les liens ci-dessus 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. Mais 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