Flux RSS d'opikanoba.org

Sur l’interopérabilité, l’architecture et les systèmes d’informations de santé


Billets de la catégorie ‘articles’

XInclude, l’inclusion XML

L’inclusion de fichier est un problème classique, existant depuis les premiers programmes informatiques, quelle que soit la technologie. Curieusement, les spécifications XML (1.0 et 1.1) n’ont pas abordé cette question. Heureusement cinq années après XML 1.0, XInclude vient combler ce manque. Magazine Login: n°129, juin 2005 - Frédéric Laurent

Les longs fichiers monolithiques ont toujours posé des problèmes. A l’inverse, les fragments de fichier permettent de gagner en robustesse, en simplicité de gestion, en factorisation, en partage d’informations, etc. Si c’est vrai pour les programmes (import java, include en C…), ça l’est également pour les documents. Cinq documents de vingt pages sont nettement plus aisés à maintenir et à faire évoluer qu’un long document de cent pages (surtout dans les environnements collaboratifs).

XML n’est pas épargné par ce problème. En ajoutant des balises permettant d’exprimer la structure d’un document, XML enrichit l’information utile, mais la surcharge. Le document est plus gros et plus long. Pouvoir le découper permet de mieux le maîtriser, de réutiliser et de partager les sous-documents et d’assurer une cohérence qui ne peut l’être en dupliquant la même information dans de multiples fichiers. Les exemples d’utilisation sont légion. Un entête et une information sur des droits d’auteur répétés dans chaque page web XHTML, un livre découpé en plusieurs chapitres, des paramètres généraux inclus dans des fichiers de configuration, etc…

Inclusion d'un fichier python dans un document XHTML
Figure 1 : Inclusion d’un fichier
python dans un document XHTML

Cependant, ce sujet semble avoir été oublié par les spécifications et ne fait donc pas partie du coeur de XML. Plusieurs solutions ont donc été imaginées pour faire des inclusions : par exemple, l’utilisation des instructions de traitement ou des entités externes.

Si les instructions de traitement ont l’avantage de pouvoir être placées à tout endroit du document et de ne pas être intrusives pour la DTD ou le schéma (aucune déclaration nécessaire), elles présentent un inconvénient de taille. Elles demandent un traitement spécifique de la part de l’application qui doit reconnaître l’instruction, la traiter et ainsi gérer l’inclusion du fichier. De plus, le processus de gestion des erreurs est totalement déporté dans l’application, voire inexistant.

Une autre solution, l’utilisation des entités externes, s’est plus largement répandue. Elle se base sur un mécanisme défini par les DTD. Il s’agit de déclarer une entité qui fait référence à un fichier externe, désigné par son URI (voir listing 1). Lors du traitement du fichier XML, le processeur remplacera chaque appel d’entité par son contenu. Le processeur lira donc le contenu du fichier externe désigné et procédera au remplacement de l’appel d’entité.

Cependant, ce système d’inclusion est confronté à de nombreuses limitations. Le document inclus ne peut pas être un document XML bien formé de façon globale, car les déclarations <!DOCTYPE> et <?xml ?>ne peuvent pas être présentes et le document importé peut contenir deux racines. Par ailleurs, il n’est pas possible d’importer un document qui n’a pas une syntaxe XML. L’inclusion d’un fichier java devant figurer dans un document XHTML technique est impossible. Si un problème survient lors du chargement du fichier désigné par l’entité, le processeur provoque une erreur fatale. Aucune gestion des erreurs n’est donc possible. De plus, seule la totalité du document peut être importée. Une inclusion plus fine, c’est-à-dire, une inclusion d’un fragment de document est impossible. Enfin, les entités nécessitent d’être déclarées dans le sous-ensemble interne (voir le listing 1) ou dans la DTD. C’est un processus intrusif qui peut avoir des répercutions désagréables.

<?xml version="1.0"?>
<!DOCTYPE html [
  <!ENTITY haut SYSTEM "head.xml">
  <!ENTITY bas  SYSTEM "tail.xml">
]> 

<html>
  <head><title>test entite</title></head>
  <body>
    &haut;
    <p>texte du document</p>
    &bas;
  </body>
</html>

Listing 1 : inclusion à l’aide d’entités externes

L’inclusion avec XInclude

XInclude permet de faire l’inclusion d’un document XML entier, d’un fragment de document XML ou d’un fichier non XML. Le mécanisme est très simple. Il se compose de deux éléments : include et fallback. Ces deux éléments sont présents dans un espace de noms qu’il faut déclarer. L’espace de noms http://www.w3.org/2003/XInclude est souvent associé au préfixe «xi», bien que le préfixe ne soit pas important puisque seul l’URI compte. Le listing 2 montre un exemple simple d’inclusion.

<!DOCTYPE html PUBLIC
   "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr"
   lang="fr" xmlns:xi="http://www.w3.org/2001/XInclude">
  <head><title>test xinclude</title></head>
  <body>
    <xi:include href="head.xml" mce_href="head.xml"/>
    <p>texte du document</p>
    <xi:include href="tail.xml" mce_href="tail.xml"/>
</body>
</html>

Listing 2 : inclusion grâce à XInclude

L’élément XInclude

Les attributs de cet élément en détail… (lire la suite)

L’élément xi:includedéfinit six attributs : href, parse, xpointer, encoding, accept et accept-language (voir encadré 2). Ils permettent de fournir l’information nécessaire en terme de localisation (du fichier et de la zone réelle à inclure qui peut être une partie du fichier), d’encodage et de négociation de contenu. L’inclusion est récursive. Chaque fragment inclus est analysé à son tour et les inclusions qu’il définit sont traitées. Les inclusions circulaires sont détectées et ne peuvent pas aboutir à un processus infini. XInclude permet d’inclure des données, XML ou non, en les considérant comme non-analysables. Cette fonctionnalité, forte intéressante, permet alors d’intégrer des documents Java, C#, python ou même XML dans un autre document (figure 1). Dans ce traitement spécial, les caractères de balisage sont échappés. Le caractère ‘<’, par exemple, est transformé en < ;

Négociation de contenu

L’inclusion de fichier peut faire intervenir des serveurs HTTP locaux ou distants. Certains offrent une gestion de contenu fine, c’est-à-dire prenant en compte la valeur des paramètres accept et accept-language des requêtes HTTP, et ne se contentent pas de renvoyer la même page quelles que soient ces valeurs. Il s’agit de la négociation de contenu. Selon la valeur de ces paramètres, fixée par le client par le biais de ses préférences, le serveur cherche la représentation de la ressource qui correspond la mieux.

La négociation de contenu permet d'obtenir des versions différentes d'une même ressource
Figure 4 : La négociation de contenu
permet d’obtenir des versions différentes
d’une même ressource

XInclude fait l’analogie avec les en-têtes HTTP, puisque l’expression des préférences en matière d’inclusion se fait par l’ajout des attributs accept et accept-language. Le premier définit le format souhaité de la ressource demandée, c’est-à-dire html, text, xml, etc. Le second permet d’exprimer la préférence quant à la langue, il s’agit d’une valeur ayant la forme, fr-Fr, fr, en-us, etc. Une valeur “fr-fr, en” signifie : je préfère une représentation en langue française utilisée en France, par opposition au français utilisé au Canada par exemple, mais j’accepte aussi toute forme de ressource en langue anglaise. Ainsi, il est possible de demander une version française et html d’une page web ou cette même page dans un format XML et en anglais. Par exemple, le serveur web qui héberge la documentation du serveur apache permet d’utiliser la gestion de contenu. Il délivre un URL différent selon la langue choisie. Dans l’exemple suivant, la page française est demandée:

<xi:include href="http://httpd.apache.org/docs-2.0" mce_href="http://httpd.apache.org/docs-2.0"
        accept-language="fr"  accept="text/html"/>

La figure 4 montre un exemple plus détaillée d’utilisation de la négociation de contenu avec ce site.

Gestion d’erreur

L’inclusion de document externe est sujette à tous les problèmes de défaillances informatiques classiques. Le fichier peut être absent, il peut être inaccessible en raison d’un échec du réseau, d’un blocage de requête par un proxy, d’une suppression de la ressource par un tiers, ainsi de suite… La possibilité de définir un comportement en cas d’échec de l’inclusion constitue un apport non négligeable de Xinclude. Cette possibilité était une carence de l’inclusion faite avec les entités externes. L’élément xi:fallback permet de spécifier ce comportement. C’est un élément fils de l’élément xi:include. Son contenu sera inséré à la place de l’élément xi:include en cas d’erreur. Le contenu de xi:fallback est libre. Il peut s’agir d’un simple texte, d’un fragment de document ou d’une nouvelle instruction xi:include, comme nous pouvons le voir dans le listing 3.

<body>
  <xi:include href="head.xml" mce_href="head.xml">
      <xi:fallback>
        <xi:include href="errHead.xml" mce_href="errHead.xml"/>
      </xi:fallback>
  </xi:include>
  <p>texte du document</p>
  <xi:include href="tail.xml" mce_href="tail.xml">
      <xi:fallback>Probleme d'inclusion</xi:fallback>
  </xi:include>
</body>

Listing 3 : gestion des erreurs

L’inclusion conditionnée par la détection d’erreur est intéressante à plus d’un titre. Imaginons l’inclusion d’un document externe EnteteComplexe.xml (listing 4).

<xi:include href="EnteteComplexe.xml" mce_href="EnteteComplexe.xml">
   <xi:fallback>
      <xi:include href="EnteteSimple.xml" mce_href="EnteteSimple.xml">
         <xi:fallback>erreur d'inclusion du fichier
         externe et du fichier alternatif </xi:fallback>
      </xi:include>
   </xi:fallback>
</xi:include>

Listing 4 : inclusion alternative en cas d’erreur

Si celui-ci n’est pas atteignable, un document alternatif, EnteteSimple.xml, sera inclus. Mais s’il ne peut l’être, suite à une nouvelle erreur de localisation du fichier par exemple, on peut imaginer un simple texte laconique, qui sera présent dans le document original et sera donc toujours disponible. Dans tous les cas, la gestion d’erreur est traitée correctement.

L’inclusion partielle

Xinclude permet de faire des inclusions partielles de document. La technologie XPointer permet d’y parvenir. Elle est utilisée à travers l’attribut xpointer de l’élément xi:include. Les processeurs ne sont pas obligés de fournir un support complet de XPointer, seuls le schéma element() et les pointeurs abrégés sont obligatoires. Par ailleurs, certains processeurs, comme celui de libxml ou XInclude.net, offrent la possibilité d’utiliser les schémas xpointer() et xmlns(). Par exemple, l’inclusion ci-dessous permet d’avoir la liste de l’ensemble des documents techniques (Technical Reports ou TR) du W3C, dont le sujet contient RDF :

<xi:include href="http://www.w3.org/TR" mce_href="http://www.w3.org/TR"
  xpointer="xmlns(html=http://www.w3.org/1999/xhtml)
  xpointer(//html:dt[@class='TR']/html:a[contains(
  text(),'RDF')])”>

Inclusion d'un fragment de document distant en utilisant XPointer
Figure 2 : Inclusion d’un fragment de
document distant en utilisant XPointer

Dans l’attribut xpointer, le schémaxmlns() permet de déclarer l’espace de noms du document référencé. Cet espace de noms sera utilisé pour localiser l’information dans le schéma xpointer(). Le contenu du schéma xpointer est une expression XPath, dont une version française pourrait être : “toutes les balises <a> qui ont pour élément père une balise <dt>, dont l’attribut class vaut TR, et dont le contenu textuel (de la balise <a>) contient le texte RDF” (figure 2).

Support de xml:base

XInclude utilise la spécification du W3C xml:base. Le processeur XInclude positionne l’attribut xml:base sur la balise racine du fragment inclus, de sorte que chaque lien relatif, contenu dans le fragment, soit correct. Ainsi deux liens vers doc.xml ne pourront pas être confondus car ils seront relatifs à l’URL spécifié par leur attribut parent respectif. Par exemple, l’incorporation de la liste des technologies proposées par le W3C ne pose pas de problème relatifs aux liens hypertextes, grâce au positionnement d’un attribut xml:base. Les liens ne sont pas modifiés (<a href="/amaya/" mce_href="/amaya/">Amaya</a>) et l’URL est correct car il est chapoté par une déclaration xml:base permettant de construire le lien cible (voir figure 3).

Le processeur insère un attribut xml:base rendant les URL consistants
Figure 3 : Le processeur insère un attribut
xml:base rendant les URL consistants

xml:base peut également être utilisé par le document faisant l’inclusion. Les inclusions seront alors spécifiées relativement à cette base, évitant de ressaisir les URI complets. Cette facilité apporte non seulement de la lisibilité mais aussi de la robustesse car le risque d’erreur dans les URI est diminué.

<doc xml:base="http://www.site.org/mondocument">
  <xi:include href="chapitre1.xml" mce_href="chapitre1.xml" />
  <xi:include href="chapitre2.xml" mce_href="chapitre2.xml" />
  <xi:include href="chapitre3.xml" mce_href="chapitre3.xml" />
</doc>

Quelques limitations

Malgré ses nombreux atouts, XInclude présente quelques désagréments. Cette spécification a pris pas de moins de cinq années pour voir le jour. Ce temps extrêment long dans le domaine des formats XML, a vu éclore nombre de spécifications ne prenant pas en compte XInculde, jugé dans un état instable. Ainsi les définitions de schémas (DTD, XML Schémas, schémas RelaxNG, etc) ont fait l’impasse sur XInclude et cela vient perturber la validation. Il faut en effet déclarer l’espace de noms XInclude pour permettre au processeur de traiter les balises d’inclusion, mais cette déclaration reste dans le document après la transformation. Une spécification XHTML, même récente, ne s’attend pas à trouver une déclaration xmlns:xi="http://www.w3.org/2003/XInclude" sur sa racine.

Support de XInclude

De nombreux processeurs mettent déjà en œuvre XInclude mais de façon très inégale (lire la suite)

Le document, après inclusion sera donc invalide. L’ajout par le processeur de l’attribut xml:base pose le même type de problème. Plusieurs solutions sont envisageables. On peut imaginer de ne pas valider le document résultant, de modifier les grammaires pour prendre en compte ce genre d’information ou encore de définir au niveau même de XML, que les attributs xmlns ou xml:* peuvent être présents à tout endroit dans un document et ne doivent pas être pris en compte par la validation. Quoi qu’il en soit, cet inconvénient risque de prendre du temps avant qu’il ne soit totalement résolu.

Une option à explorer

XInclude vient remplacer l’utilisation délicate des entités externes. Cette nouvelle spécification comble ainsi un manque important dans l’espace XML et formalise un mécanisme d’inclusion robuste, qui utilise les concepts XML fonctionnant par ailleurs (élément, URI, espace de noms, xml :base). Même si certains questions restent posées, notamment sur la post-validation, et que la mise en oeuvre de cette spécification par les processeurs est aujourd’hui disparate, XInclude repésente une avancée vraiment intéressante pour les documents orientés données. Le développement rapide de cette technologie permet d’envisager à terme un support natif par les navigateurs Internet, fournissant alors la possibilité de faire des inclusions dans les pages XHTML.

Les sources de l’article

Statistiques en SVG avec Python

XML a apporté son lot de nouvelles applications. SVG est l’une d’entre elles. SVG permet de faire du dessin vectoriel de façon très simple et sans librairie particulière puisqu’il s’agit d’un format XML. Nous allons voir comment utiliser la puissance de python pour collecter des informations sur la taille des répertoires d’un disque et générer un graphique à base d’histogrammes représentant les résultats.
(Login: n°121, novembre 2004 - Frédéric Laurent)

La figure 1 offre un aperçu du diagramme SVG d’occupation du disque que nous obtiendrons avec notre court exemple Python. Pour un répertoire et une profondeur donnés (ici le répertoire c:\Python et une profondeur 2), le graphique permettra de comprendre quels sont les répartitions en termes de pourcentage des différents répertoires contenus dans le chemin initial.

[fichier SVG]

statistiques
Figure 1: Statistiques de l’occupation de la
distribution python

Pour construire un tel graphique, le programme se divise en deux parties. La première permet d’explorer le système de fichiers et de collecter les informations de tailles des répertoires. La seconde partie met en forme ces données. Pour arriver à nos fins simplement, rien de tel qu’un papier quadrillé et un crayon. Il va nous servir à construire très simplement le repère et l’enchaînement des différents rectangles représentant les valeurs en pourcentage de l’espace occupé sur le disque.

Histoire de repères

Le système de coordonnées de SVG est assez classique en informatique. Le point (0,0) se trouve en haut à gauche. L’axe des abscisses croît de gauche à droite et l’axe des ordonnées croît de haut en bas. La figure 2 illustre ce système.

[fichier SVG]

Système de coordonnées SVG
Figure 2: Système
de coordonnées SVG

S’il est courant de rencontrer cette orientation, elle rentre cependant en opposition avec les habitudes prises sur les bancs de l’école. Ainsi, nos souvenirs de mathématiques nous conduisent à réfléchir avec un système un peu différent. Inventé au 17ème siècle par René Descartes, le système cartésien situe l’origine vers le bas de l’écran et si l’orientation de l’axe des abscisses ne changent pas, celui des ordonnées se retrouve inversé, comme l’illustre la figure 3.

[fichier SVG]

inversion
Figure 3: Système de
coordonnées cartésien

Alors comment passer de l’un à l’autre ? Comment réfléchir avec un repère cartésien et avoir un graphique correct ? Deux solutions sont envisageables. La première consiste à concevoir une fonction au niveau du langage de programmation qui fera la conversion des points exprimés dans le système cartésien en des points corrects dans le système de coordonnées de SVG. La seconde utilise les coordonnées cartésiennes directement dans le document SVG et définit les transformations à appliquer sur les éléments graphiques pour obtenir le résultat attendu. Nous allons opter pour la seconde solution. La première transformation fera donc une translation (fonction translate) de tous les points vers le bas. Nous utilisons une translation (30,130) qui correspond à une première translation de (30,30) pour éloigner le graphique du bord du document, à laquelle s’ajoute une translation de (0,100). 100 étant la valeur maximale des ordonnées.

Ensuite, il faut inverser l’axe des ordonnées en l’orientant de bas en haut. La fonction scale qui permet d’appliquer à chaque point un facteur multiplicatif. scale(1,-1) appliqué à chaque point permet de conserver l’abscisse et inverse l’ordonnée. Au niveau du document SVG, un simple bloc englobant permet d’appliquer les transformations à tous les composants graphiques situés à l’intérieur de ce bloc :

<g transform="translate(30,130) scale(1,-1)">
  <rect .../>
  <line .../>
</g>

 

Scalable Vector Graphics

Scalable Vector Graphics (SVG) est un langage XML, né en 1998 peu après la recommandation XML 1.0. Il est issu d’un groupe de travail du W3C. Il permet de …(lire la suite)

L’attribut transform définit la séquence des transformations. Elles sont définies les unes à la suite des autres en les séparant par un espace.

Simple, mais…

Cette transformation globale a l’avantage d’être simple, mais elle inverse tous les points, y compris ceux des caractères ! Toutes les lettres se retrouvent donc à l’envers, voir figure 4.

[fichier SVG]

Inversion des caractères
Figure 4: Inversion des
ordonnées et des caractères

Il faut donc soit les sortir du bloc de transformation et calculer leurs nouvelles coordonnées, soit leur appliquer de nouvelles transformations : scale(1,-1) permet de remettre à l’endroit les caractères. Mais elle fausse par la même occasion le calcul de coordonnées (x,y). Un caractère positionné sur le point P(20,60) se trouve après la première translation en un point P'(50,190) (grâce à la translation de (30,130)). Or il devrait se trouver à P''(50,70) soit 60 points plus haut que l’axe des abscisses (situé à y=130).

[fichier SVG]

Remise à l'endroit des caractères
Figure 5: Remise à l’endroit
des caractères

Le point se trouve donc deux fois trop bas, voir figure 5. Une translation de -60 permet de le remonter sur l’axe des abscisses (y=0 dans le repère cartésien). Une seconde translation de -60 lui permet de trouver sa place (y=60 dans le répertoire cartésien). Il faut donc effectuer un translation pour tous les textes positionnées à (Xorig,Yorig) de (0, -2*Yorig). Maintenant que ces quelques règles sont établies, toutes les données du graphique résultat pourront être exprimées en coordonnées cartésiennes.

Collecter les informations avec python

La collecte des informations du système peut être réalisée à l’aide d’une fonction récursive. Les paramètres à lui fournir sont le nom de répertoire de base et la profondeur de parcours dans l’arbre du système de fichiers. La fonction va alors calculer le poids d’un répertoire donné et stocker dans une liste un tuple d’informations si le niveau du répertoire est compatible avec ce que veut l’utilisateur. Par exemple, l’appel de la méthode gatherSize(r"d:\monrep", 2), recueillera toutes les informations relatives à l’occupation disque du répertoire d:\monrep. Le second paramètre permet de fixer une limite en profondeur afin de synthétiser l’information. Ainsi, les données d’un répertoire d:\monrep\niv2 seront disponibles alors que les données d’un répertoire d:\monrep\niv2\niv3 ne le seront pas. Pour les avoir, il suffit de fournir le nombre adéquat à la méthode, soit 3.

L’utilisation de Python, de son support des tuples, des listes et des fonctions associées comme la somme, le filtrage ou les fonctions lambda permet d’écrire une méthode relativement courte et simple. La somme des tailles de chaque fichier contenu dans un répertoire donné. Pour cela, la fonction listdir récupère la liste de tous les éléments, fichiers et répertoires. Un filtre permet de ne garder que les éléments de type fichier (à l’aide de la fonction de test os.path.isfile). La fonction sum est utilisée pour calculer le poids de cette liste nouvellement créée.

def gatherSize(self, directory, level, deep=1):
listdir = os.listdir(directory)
filelist = filter(lambda
   f: os.path.isfile(os.path.join(directory, f)), listdir)
weight = sum([os.path.getsize(
   os.path.join(directory, name))
   for name in listdir])

 

Python et les listes

S’il y a bien un concept sur lequel Python est éclatant, c’est bien le support natif des listes. Les listes, les fonctions lambda, …(lire la suite)

Puis la même fonction gatherSize est appelée de façon récursive sur chacun des sous-répertoires. De la même façon que pour la liste des fichiers, la liste de répertoires peut être obtenue très simplement à l’aide d’un filtre. Puis, il suffit de faire la somme de toutes les tailles ainsi calculées pour obtenir le poids de la sous-arborescence. Celui-ci ajouté au poids de l’ensemble des fichiers du répertoire constitue le poids total du répertoire passé en paramètre de gatherSize.

subdir = filter(lambda f: os.path.isdir(os.path.join(directory, f)), listdir)
subdir_w = sum([self.gatherSize(os.path.join(directory,f),
                      level,deep+1) for f in subdir])
total = weight+subdir_w

Enfin, il faut déterminer si la profondeur n’est pas trop importante par rapport au besoin de l’utilisateur, et si les informations sont stockées ou non. Une fois le parcours arborescent terminé, les informations sont disponibles sous la forme d’une liste de couples (nom,taille) qui représente la liste des répertoires trouvés.

Production du SVG

Produire du SVG n’est pas compliqué en soi, une fois le système de représentation des coordonnées choisi. Comme il s’agit d’un format XML, plusieurs approches permettent de générer un tel flux. La première consiste à écrire directement le XML sans utiliser de librairie. Rapide à mettre en oeuvre, du moins au départ, la gestion des guillemets rend vite le code illisible et il faut faire attention à produire du XML bien formé. Seconde solution, utiliser les librairies XML, telle que dom, minidom (fournies en standard) ou une libraire tierce (4Dom ou ElementTree). Cette approche permet de produire un XML bien formé en utilisant un code plus clair. Enfin, il existe des API SVG pour python. Elles permettent de s’abstraire facilement de l’implémentation XML sous-jacente et de se concentrer sur les aspects SVG. Celle choisie dans cet article est SVGDraw. Ce n’est pas la plus complète ni la plus complexe, mais elle a l’avantage d’être très simple et de ne constituer qu’un seul fichier à mettre dans le répertoire site-package. Qui plus est, la version windows s’auto-installe. La création du document SVG se fait en deux étapes. La première va créer l’enveloppe globale. Une section déclarative, définie par l’élément SVG defs, contient trois motifs (éléments SVG pattern). Ils serviront à représenter la graduation des axes et la grille de fond. La figure 6 illustre le motif représentant l’axe des abscisses.

 

[fichier SVG]

Motif pour la représentation des graduations de l'axe des abscisses
Figure 6: Motif pour la représentation des
graduations de l’axe des abscisses

Ce motif, d’une largeur de 20, définit deux traits verticaux. Le plus grand représentera toutes les graduations multiples de 20, le petit permettra d’identifier les graduations multiples de 10. Ces deux traits sont définis par des éléments SVG path. Outre les informations relatives au dimensionnement du motif et aux couleurs des traits, l’attribut le plus intéressant est la définition “d” du chemin. Elle consiste en une suite de commandes séparées par une virgule. Chaque commande est construite à l’aide d’une instruction désignée par une lettre (M,V,L,C,S,Q,T,...) et d’une ou plusieurs coordonnées (absolues ou relatives).

<pattern width="20"
   patternUnits="userSpaceOnUse"
   id="axeX" height="10">
   <path stroke="black"
         stroke-width="0.25"
         d="M 0 0, L 0 10" fill="none"/>
   <path stroke="lightgray"
         stroke-width="0.25"
         d="M 10 10, V 5" fill="none"/>
</pattern>

Le premier chemin est défini comme suit : Aller (M i.e Move) au point 0,0, faire une ligne (L i.e Lineto) jusqu’aux coordonnées absolues 0,10. La seconde ligne est définie par : Aller au point 10,10 et tracer une ligne verticale (V ou Vertical Lineto) jusqu’aux coordonnées absolues x,5 (la valeur de x ne change pas puisqu’il s’agit d’une ligne verticale). La spécification d’un attribut id=”axeX” permet de nommer ce motif et il pourra ensuite être utilisé de nouveau dans le graphique SVG, en faisant simplement référence à son nom. Le dessin de l’axe des x se fait donc ainsi :

<rect y="-10" width="240"
   fill="url(#axeX)" x="0" height="10"/>

Le rectangle est positionné plus bas que l’axe (avec des coordonnées cartésiennes), est large de 240 (le motif sera donc appliqué 12 fois) et est rempli avec le motif dont le nom correspond à “axeX”. Les motifs et les dessins effectifs de l’axe des Y et de la grille de fond sont construits sur le même principe.

Les lignes des axes sont dessinées à l’aide de l’élément SVG line et les deux flèches orientant les axes sont réalisées avec l’élément SVG polygon. Enfin, on affiche les informations générales sur le résultat de la recherche : le répertoire source et la taille globale utilisée. La seconde étape de la production du graphique va analyser chaque élément de la liste constituée dans la phase de collecte (voir ci-dessus) et ajouter un rectangle pour la représentation de la barre, et du texte pour afficher les informations relatives. On commence par trier la liste des couples (nom,taille). La taille occupée par le répertoire constitue le facteur de tri. Avec Python, le tri d’une liste constituée d’éléments complexes comme des tuples, des dictionnaires ou des objets se révèle très simple. Il suffit en effet de fournir une fonction de comparaison pour assurer l’opération :

def tupleCmp(self, a,b):
  (nameA, sizeA) = a
  (nameB, sizeB) = b
  return cmp(sizeB,sizeA)# tri des elements selon la taille occupee
self.infos.sort(self.tupleCmp)

Ici seules les tailles sont importantes, elles constituent donc le facteur de comparaison. Puisque c’est l’ordre décroissant qui nous intéresse, la fonction retournera donc 1 si sizeB>sizeA. Enfin, pour chaque élément de la liste, c’est à dire chaque répertoire, une barre est générée (via l’élément SVG rect) ainsi que deux zones de texte (éléments SVG text) : le pourcentage d’occupation et les informations sur le répertoire à proprement dit. Il suffit d’incrémenter à chaque fois l’axe des X, sans oublier d’ajouter les paramètres de transformation pour que les coordonnées cartésiennes exprimées dans la boucle soient cohérentes avec le système de coordonnées de SVG. Une fois, toutes les informations générées, le document SVG en mémoire est exporté vers un fichier XML temporaire. Le module webbrowser permet de lancer une instance du navigateur Internet avec l’URL du document ainsi construit. Le navigateur affiche alors le document SVG résultat. Il est donc nécessaire qu’il dispose du plugin approprié.

En une centaine de lignes de Python, nous avons pu analyser la structure d’un système de fichiers et produire un graphique vectorielle de haute qualité. Il serait simple de le rendre encore plus attractif en ajoutant d’autres éléments SVG. Par exemple, on pourrait obtenir un effet 3D des barres, ajouter une image dans la grille de fond, travailler les fontes avec des ombres, ou ajouter une dimension dynamique au graphique. Maintenant que les bases sont posées, libre à vous de laisser courir votre imagination…

Les sources de l’article

Recherche rapide d’informations grâce à XPath

Avec l’avènement de XML, de nombreuses informations se retrouvent dans des fichiers au format texte structuré de façon arborescente, qu’il s’agisse de fichiers de configuration, de données à proprement parler ou de résultats de requête. Si la structure XML apporte lisibilité et robustesse, le problème d’accès à l’information demeure. Nous allons voir comment XPath y apporte une réponse vraiment puissante.

Magazine Login: n°120, septembre 2004 - Frédéric Laurent

De nombreuses techniques permettent de retrouver une valeur noyée au plus profond d’un arbre XML, mais elles ne sont pas toutes aisées à mettre en oeuvre. XPath, langage non XML, spécifié par le W3C, permet de déclarer de façon concise l’information recherchée. Le travail du programmeur est allégé en terme de code mais aussi de maintenance. Il peut en effet se concentrer uniquement sur la formulation de sa requête (le quoi) et non sur la façon d’y accéder (le comment).

Pour illustrer l’article, nous définissons deux types d’information à retrouver dans deux fichiers XML différents. Tout d’abord, nous souhaitons connaître la liste des servlets déclarées dans un fichier de configuration d’une application web J2EE (le fichier web.xml). Les noms de servlet sont définis par la balise <servlet-name>. Le fichier ne contient pas d’espace de noms. L’expression sera donc très simple. Le second exemple, en revanche, est plus complexe. A partir du fichier d’actualités du W3C (au format RSS 1.0, c’est à dire avec le support des espaces de noms), nous souhaitons connaître les annonces des nouveaux documents de travail. Ce qui revient à localiser le texte des balises <description> dont les balises adjacentes <title> contiennent le mot “Draft”. Cet exemple utilise donc les concepts d’axe XPath, d’espace de noms et de fonction XPath.

Différents types d’accès

Plusieurs techniques sont envisageables pour accéder à une information contenue dans un fichier XML. On peut citer la recherche de texte à l’aide des expressions régulières, l’API SAX, le modèle DOM, les librairies de correspondance (désigné par le terme anglais “binding”) XML vers des objets de n’importe quel langage de programmation, et bien sûr XPath.

Utilisation des expressions régulières

Un fichier XML est un fichier texte. Pendant longtemps, l’utilisation des expressions régulières a fait le bonheur des développeurs devant trouver des données dans ces fichiers texte. Cette technique est donc parfaitement applicable pour retrouver notre liste de servlets. En python, une première version naïve serait la suivante :

servletname="<servlet-name>(.*?)</servlet-name>"
webxml = open("WEB-INF\\web.xml").read()
print re.findall(servletname, webxml)
['invoker', 'CompressionFilterTestServlet', 'HelloWorldExample',
 'RequestInfoExample', 'RequestHeaderExample', 'RequestParamExample',
 'CookieExample', 'SessionExample', 'CompressionFilterTestServlet',
 'HelloWorldExample', 'RequestInfoExample', 'RequestHeaderExample',
 'RequestParamExample', 'CookieExample', 'SessionExample']

Pour ce cas simple, la solution semble parfaite. Cependant, il faudra penser à traiter les retours à la ligne entre la balise ouvrante et le texte à proprement dit. Le code se complexifie alors. Mais surtout comment repérer que <servlet-name>invoker</servlet-name> est déclaré ou ne l’est pas car compris entre un début et une fin de commentaire (resp. <!-- et -->) ? Complexifier encore l’expression régulière ? Ajouter du code pour contrôler cet aspect ? Faire une première lecture pour supprimer toutes les parties XML contenues dans des commentaires et travailler sur cette version épurée ? Les solutions sont multiples, mais elles présentent toutes de nombreux inconvénients. En occultant la sémantique du langage XML, le travail devient complexe et difficilement maintenable. Et que dire de notre second exemple…

Utilisation de SAX

SAX permet de répondre de façon plus intéressante que les expressions régulières car le format XML est traité de façon adaptée : les problèmes d’espaces entre balise et texte, ou de balises entre commentaires se résolvent d’eux-mêmes. Si SAX fournit un moyen rapide d’accéder à l’information puisqu’il est possible de ne prendre en compte que les données qui nous intéressent, il n’en demeure pas moins qu’il faut construire un programme pour trouver les différents noms de servlet. La classe dédiée peut être relativement simple, elle a cependant le principal inconvénient de fixer dans le programme la façon d’accéder à l’information. Si le format évolue, le programme doit être changé, recompilé, redéployé (selon le cas, et le langage).

Utilisation de DOM

L’API DOM, spécifiée par le W3C, permet également d’accéder aux informations des fichiers XML. Une fois le document lu en mémoire dans une structure de données standard, l’accès se fait par l’appel aux méthodes définies sur les classes Document, Element, Attribute… A l’image de SAX, cette solution exploite pleinement la spécificité du format XML et règle nombre de problèmes. Cependant, elle ne résout pas la dépendance entre le code du programme et le format du fichier XML.

S’il existe des alternatives à DOM, comme JDOM ou DOM4J par exemple, qui rendent le code plus concis et plus lisible, les inconvénients exposés ci-dessus persistent.

Utilisation du binding

Il s’agit d’obtenir une représentation du fichier XML sous forme de classe. L’accès est simple, cependant encore une fois, la consultation d’une information impose la génération de code (Java, Python ou autre), sa compilation éventuelle, son déploiement… Tout cela étant sujet à modification, et donc difficilement maintenable. De plus, pour accéder à la liste des noms de servlet, il faut coder explicitement l’accès par des appels à des accesseurs de la classe générée.

Utilisation de XPath

XPath : un langage pour intérroger les fichiers XML

La spécification XPath est assez simple. Peu de notions permettent de composer une expression qui localisera, dans le document XML, une simple valeur, ou un ensemble de noeuds. Une expression XPath définit …(lire la suite)

Face à tous ces problèmes de couplage fort entre le format XML et le code du programme, XPath apporte une réponse vraiment satisfaisante. La requête d’accès à l’information est la seule chose à définir. Le codage de l’affichage du résultat reste indépendant (des informations recherchées). Si la structure du fichier source XML change, seule l’expression XPath doit évoluer. Qui plus est, les instructions qui exploitent le résultat de l’évaluation sont vraiment simples. Il s’agit d’un parcours d’un ensemble de noeuds pour les résultats multiples (ensembles de noeuds, ou "node-set") ou la consultation d’une valeur. Enfin, l’utilisation de cette technologie permet également d’obtenir le résultat en tant que noeud DOM d’un document. Lui ajouter un nouveau noeud fils, un attribut ou du texte est donc une formalité. XPath ne se cantonne donc pas à la seule consultation d’information, mais fournit un véritable moyen d’accès à une partie du document XML. Libre au programmeur d’utiliser le résultat comme bon lui semble.

XPath et la gestion des espaces de noms

Lors de la formulation d’une requête XPath, chaque espace de noms doit être explicitement nommé. Chaque élément doit pouvoir être préfixé… (lire la suite)

L’apparition d’une spécification claire pour l’utilisation d’API XPath s’est faite attendre. La conséquence immédiate est le développement par chaque librairie XML de sa propre API. Ainsi pour Java, Saxon, JDOM, Xalan, DOM4j,… fournissent ce service de façon propriétaire. Le W3C progresse sur une recommandation (qui n’est encore qu’un document de travail), et les différents projets comblent peu à peu ce manque de cohérence. Nous allons voir comment accéder aux informations désirées en utilisant d’une part Saxon et d’autre part l’API standard XPath (uniquement implémentée par Xalan, à ce jour).

Le premier exemple permet de manipuler une expression XPath triviale, puisqu’il s’agit d’obtenir la valeur textuelle de tous les noeuds <servlet-name>. L’expression //servlet-name/text() convient parfaitement. Le second exemple est plus ardu. Une des expressions XPath utilisable (il est possible de fournir de nombreuses autres solutions pour accéder à la même information) est :

//dns1:description[contains(preceding-sibling::dns1:title,'Draft')]/text()

ce qui peut se traduire en français par : prendre le texte de toutes les balises <description> (dans l’espace de noms dns1) qui contiennent un noeud frère nommé <title> (dans l’espace de noms dns1) et dont le contenu contient le mot ‘Draft’.

XPath avec Saxon

Le listing 1 présente comment utiliser Saxon pour obtenir notre liste de servlets. Le programme se contente de lire un fichier XML et d’évaluer l’expression XPath. Une fois le fichier lu (depuis le système de fichiers ou depuis une URL), l’objet XPathEvaluator de l’API Saxon est créé avec la référence sur le flux XML. Il servira à évaluer les requètes. Ce service est rendu par la méthode evaluate(). Elle renvoie un objet du type Java classique [List][23] qu’il suffit de parcourir pour afficher l’ensemble des résultats. Saxon fournit pour la représentation des noeuds, une classe [NodeInfo][24], qui offre contrairement à la classe [Node][25] du W3C, une méthode getStringValue() permettant d’en avoir une représentation textuelle. L’affichage est donc grandement simplifié.

Le listing 2 correspond au second exemple. Il est plus complexe puisqu’il faut gérer les espaces de noms. Chaque couple (préfixe, URI de l’espace de noms) doit être déclaré au niveau de l’évaluateur XPath. Enfin, les espaces de noms par défaut spécifiés dans le document XML (par une déclaration xmlns="http://...") doivent également posséder un préfixe au niveau XPath. Ainsi, la constante DEFAULT_NS_PREFIX définit la valeur textuelle du préfixe à laquelle un entier, incrémenté automatiquement, est accolé. Il n’existe pas de moyen prédéfini pour collecter les espaces de noms d’un document XML. Deux solutions relativement simples sont envisageables. La première consiste à utiliser la récursivité de la structure XML et les méthodes d’accès aux informations fournies par DOM. Le parcours récursif teste tous les attributs de tous les noeuds Element afin de trouver ceux qui commencent par "xmlns". La seconde solution est basée sur l’analyse syntaxique SAX en se concentrant uniquement sur les espaces de noms. C’est cette solution qui est retenue dans notre exemple. Ainsi, avant de fournir le flux XML à l’objet [XPathEvalutor][26], il est analysé une première fois par une classe dérivant du [DefaultHandler][27] de SAX 2. Seul le signal startPrefixMapping(prefix,uri) est utilisé. Cette méthode voit passer chaque déclaration d’espace de noms, elle se charge de l’enregistrer dans un objet [StandaloneContext][28] prévu, entre autres, à cet effet dans l’API Saxon.

Utilisation de l’API XPath DOM niveau 3

Les exemples suivants se basent sur des abstractions d’API. Le but recherché est bien sûr d’être indépendant de toute implémentation, afin d’offrir une pérennité et une maintenabilité plus grandes. La première abstraction concerne la lecture du document. Les documents XML sont lus en utilisant l’API JAXP. Définie par Sun, elle permet de ne pas se préoccuper de l’analyseur syntaxique sous-jacent. L’évaluation XPath se fait en utilisant l’ensemble des classes spécifiées par le groupe de travail DOM du W3C. Ainsi, même si actuellement seule Xalan implémente ce document de travail, l’utilisation de l’évaluateur XPath est indépendante. Dès qu’une autre librairie supportera XPath DOM niveau 3, il sera trivial de remplacer Xalan. Le code de l’exemple n’aura pas besoin d’évoluer (on notera d’ailleurs que les imports utilisés ne dépendent pas de Xalan). Il reste cependant un bémol à cette indépendance. Le document est très jeune, et l’implémentation de Xalan n’est pas finalisée. Ainsi, la création de l’évaluateur XPath est un peu différente de ce qu’elle devrait être (et de ce qu’elle sera dans les mois à venir). En effet, elle est effective en faisant appel explicitement à une classe de la librairie Xalan.

XPathEvaluator evaluator = new org.apache.xpath.domapi.XPathEvaluatorImpl(this.doc);

Dans la version finale, il faudra utiliser le mécanisme d’interrogation (les méthodes isSupported() et getFeature() de l’interface Node) de l’implémentation afin de choisir la bonne façon de créer l’objet XPathEvaluator.

Interroger une implémentation indirectement…

La recommandation DOM Niveau 3 introduit un mécanisme permettant d’agir selon les réponses d’une implémentation… (lire la suite)

Enfin, l’écriture sur la sortie standard du résultat de l’évaluation est également indépendante de toute librairie, puisque c’est une transformation XSLT identité qui l’effectue (la transformation identité prend en entrée un document XML et produit en sortie le même document XML).

Le listing 3 permet d’illustrer l’évaluation d’expressions simples, sans espaces de noms. La méthode evaluate() de l’XPathEvaluator produit un résultat qui est parcouru élément par élément. Chaque élément est affiché sur la sortie standard, par un simple println dans le cas d’un noeud Texte, ou par la transformation identité s’il s’agit d’un fragment de document.

Le listing 4 se charge de traiter les espaces de noms. L’analyse des déclarations d’espace de noms se fait de façon similaire à celle présente dans le listing 2. Par contre, la résolution des préfixes se fait de façon différente. Lors de la lecture du document source, un nouveau document DOM est créé. Son seul but est de porter l’ensemble des déclarations d’espace de noms. On peut se demander pourquoi créer un nouveau document, alors que le document RSS du W3C porte déjà toutes les déclarations sur son noeud racine (<rdf:RDF>). Pourquoi alors ne pas l’utiliser directement ? La raison est simple et double: le fichier RSS n’est qu’un exemple et il s’agit donc d’un cas particulier qui ne fonctionne que dans la mesure ou toutes les déclarations sont faites au niveau le plus haut. Si un nouvel espace de noms (par défaut ou préfixé) est déclaré dans les profondeurs de l’arbre, il ne sera pas pris en compte. De plus, cette méthode permet d’associer, de façon claire, un préfixe pour chaque espace de noms par défaut.

Un évaluateur XPathNSResolver est créé par la méthode createNSResolver(). Il sera consulté à chaque fois que le moteur d’évaluation rencontrera un préfixe et sera chargé de fournir l’URI correspondante. Cet objet est construit à partir du document portant toutes les déclarations, mais une autre méthode pourrait être employée. Elle est sans doute plus simple, mais la jeunesse de l’implémentation la rend indisponible pour l’instant. Le programmeur pourra fournir lui-même une classe implémentant XPathNSResolver. Elle ne rendra qu’un seul service (lookupNamespaceURI(String prefix)): donner l’URI exacte correspondant au préfixe qu’elle reçoit en paramètre. Pour autant, la phase de collecte des espaces de noms ne sera pas supprimée, mais la création du document transitoire pourra être évitée puisque cette classe fournira le renseignement.

Pour conclure…

L’utilisation de XPath n’est certes pas encore complètement standard, mais l’arrivée de la recommandation du W3C risque d’accélérer encore le processus. Cela dit, il ne s’agit là que de questions d’abstraction. Si l’indépendance vis-à-vis d’une librairie particulière n’est pas rédhibitoire, vous avez un large panel d’outils déjà disponibles vous permettant d’exploiter pleinement XPath dans vos programmes. L’intégrer sera d’autant plus intéressant que le nombre de requêtes sera important. Avec une classe utilitaire (helper class), l’accès aux informations de vos fichiers XML gagne en efficacité, maintenance, compréhension. Plus de code à maintenir mais simplement des chaînes de caractères décrivant les requêtes. Voila tout l’intérêt d’utiliser XPath dans vos programmes.

Les sources de l’article