Eléments de XML, DOM, sérialisation

QUELQUES NOTIONS D'XML, PROGRAMMATION DOM, SERIALISATION

Introduction.

XML, Extensible Markup Language, est un langage standard de balisage de document. Il permet de fournir une description précise de quasiment tout type d'information textuelle et ceci à l'aide balise. Comme il n'y a pas de balisage prédéfini, des applications XML se chargent de proposer des jeux de balise orientées pour un contexte particulier. On peut ainsi dire que le langage HTML (même s'il est antérieur à XML) est une application XML pour la présentation de documents sur le web. Mais il exste bien d'autres applications XML : pour les échanges d'informations financières, astronomiques, mathématiques etc...

Etude d'un exemple.

<?xml version="1.0" encoding="ISO-8859-1"?>
<voitures>
     <voiture>
          <immatriculation>1245TR93</immatriculation>
          <modele>Laguna</modele>
          <couleur>blanche</couleur>
          <nbKm>125000</nbKm>
          <dateSortie>12/12/1996</dateSortie>
      </voiture>
      <voiture>
           <immatriculation>4578GH93</immatriculation>
           <modele>Golf</modele>
           <couleur>bleue</couleur>
           <nbKm>12450</nbKm>
           <dateSortie>01/12/2004</dateSortie>
       </voiture>
</voitures>

Ceci est donc un document xml, il est constitué d'éléments, par exemple on trouve à deux reprises l'élément dont le type est couleur. Un élément est constitué de deux balises, d'ouverture et de fermeture, encadrant une chaîne de caractères qui représente la donnée textuelle.

Quelques règles :

  • Le document doit avoir un élément de plus haut niveau (ici voitures) : c'est l'élément racine.
  • Les élément doivent être correctement imbriqués.
  • Chaque élément doit avoir un marqueur de début et un marqueur de fin (balises) dont les noms doivent très exactement correspondre.
  • Les noms d'éléments sont sensible à la casse.

Chaque élément a un élément parent (et un seul), sauf l'élément racine qui n'a pas de parent. Notons aussi que les éléments voitures et voiture ne contiennent pas de données textuelles mais seulement des éléments enfants.

Si l'on affiche le fichier .xml dans un navigateur :

fig 1 sortie XML dans un navigateur

Les signes moins (-) qui apparaissent dans le navigateur indique le premier niveau hiérachique. On peut plier un élément en cliquant sur ce signe :

fig 2 document plié sur un niveau

Notons que si on regarde dans le navigateur la source du document, on est bien en présence du seul document textuel.

Capture des erreurs.

Dans le cas où le document n'est pas bien formé, le navigateur indique une erreur :

fig 3 erreur décelée par le navigateur

Les attributs.

Il est possible de faire figurer au sein d'un élément des attributs, couples nom/valeur, ainsi voici le même exemple utilisant un attribut immatriculation à la place d'un élément :

<?xml version="1.0" encoding="ISO-8859-1"?>
<voitures>
     <voiture immatriculation="1245TR93">
         <modele>Laguna</modele>
         <couleur>blanche</couleur>
         <nbKm>125000</nbKm>
         <dateSortie>12/12/1996</dateSortie>
      </voiture>
      <voiture immatriculation="4578GH93">
         <modele>Golf</modele>
         <couleur>bleue</couleur>
         <nbKm>12450</nbKm>
         <dateSortie>01/12/2004</dateSortie>
       </voiture>
</voitures>

Ce qui donne dans le navigateur :

fig 4 utilisation de l'attribut immatriculation

On pourrait ainsi faire de même pour tous les éléments enfants de l'élément voiture :

<?xml version="1.0" encoding="ISO-8859-1"?>
<voitures>
    <voiture immatriculation="1245TR93" modele="Laguna" couleur="blanche" nbKm="125000" dateSortie="12/12/1996" />
     <voiture immatriculation="4578GH93" modele="Golf" couleur="bleue" nbKm="12450" dateSortie="01/12/2004" />
</voitures>

Notez que l'élément voiture est dans une unique balise. Ce qui donne dans un navigateur :

fig 5 utilisation d'attributs

Remarque.

On peut se demander dans quel cas utiliser des éléments enfants ou des attributs. Ceci est l'objet de débats contradictoires. Disons qu'une structure basée sur des éléments est plus souple et extensible. On peut réserver l'attribut pour caractériser une valeur de grande stabilité (identifiant par exemple).

Règles de nommage.

Pour nommer les éléments ou les attributs il faut respecter un certain nombre de règles :

  • Contenir des lettres, chiffres ou caracères _, -, .
  • Ne pas commencer par un chiffre

Certains caractères sont interdits dans la partie donnée :

  • < remplacé par &lt;
  • & remplacé par &amp
  • > remplacé par &gt
  • " remplacé par &quot
  • ' remplacé par &apos

Mais il est possible de déclarer des sections de donnée dans lesquelles tout caractère sera admis (sauf ]]>) ; pour cela on utilise le mot réservé XML CDATA (en majuscule) :

<![CDATA[ on peut écrire ici n'importe quoi < ou > ou " ou tout autre chose...]]>

Insertion de commentaires.

Les commentaires sont ceux valides en HTML <!-- ceci est un commentaire -->

Définition de type de document, laDTD

Il est possible et recommandé de définir la structure d'un document XML. Ceci permet de garantir qu'un document donné respecte effectivement les règles définies dans la DTD. Une DTD est exprimée dans une syntaxe formelle utilisant des mots réservés du langage ainsi qu'un certain nombre de spécificateurs. Un document XML possèdant une DTD et bien formé est dit valide.

Application.

La DTD associée à l'exemple plus haut serait :

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE voitures
[
<!ELEMENT voiture (immatriculation, modele,couleur,nbKm,dateSortie)>
<!ELEMENT immatriculation (#PCDATA)>
<!ELEMENT modele (#PCDATA)>
<!ELEMENT couleur (#PCDATA)>
<!ELEMENT nbKm (#PCDATA)>
<!ELEMENT dateSortie (#PCDATA)>
]
>

Cette définition peut figurer dans le fichier XML lui-même au début ou bien dans un fichier séparé. Dans ce dernier cas le fichier contenant la DTD sera :

<!ELEMENT voitures (voiture)*>
<!ELEMENT voiture (immatriculation, modele,couleur,nbKm,dateSortie)>
<!ELEMENT immatriculation (#PCDATA)>
<!ELEMENT modele (#PCDATA)>
<!ELEMENT couleur (#PCDATA)>
<!ELEMENT nbKm (#PCDATA)>
<!ELEMENT dateSortie (#PCDATA)>

Le fichier XML doit maintenant référencer sa DTD :

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE voitures SYSTEM "voitures.dtd">

Remarques.

  • voitures(voiture)* indiquent que l'élément voitures contient de 0 à n éléments voiture.
  • voiture (immatriculation, modele,couleur,nbKm,dateSortie) idique qu'un élément voiture est composé d'une immatriculation, d'un modèle etc..
  • modele (#PCDATA) indique que l'élément modele contient des données.

Une déclaration d'élément a le format suivant :

<!ELEMENT nom_elément (modèle_contenu)>

Exemples :

  • <!ELEMENT personne( nom, prenom, adresse*)> décrit un élément personne composé d'éléments nom, prénom et éventuellement 0 ou n éléments adresse
  • <!ELEMENT personne( nom, prenom?, adresse)> décrit un élément personne composé d'éléments nom, éventuellement (0 ou 1) prénom et adresse
  • <!ELEMENT personne( nom, prenom?, adresse+)>décrit un élément personne composé d'éléments nom, éventuellement (0 ou 1) prénom et au moins une adresse

Quelques éléments de syntaxe.

  • Choix <!ELEMENT adresse ( tel | fax | mail ) l'élément adresse contient soit un sous-élément tel ou fax ou mail (ou excusif)
  • Elément vide :
    <!ELEMENT voiture EMPTY>
    La déclaration suivante est conforme
    <voiture immatriculation="1245TR93" modele="Laguna" couleur="blanche" nbKm="125000" dateSortie="12/12/1996"></voiture>

Déclaration d'attributs.

Pour déclarer des attributs associés à des éléments, on utilise le format suivant :

<!ATTLIST voiture immatriculation CDATA déclaration_par_défaut >

où ici :

  • voiture représente le nom de l'élément dans lequel figurera l'attribut
  • immatriculation est le nom de l'attribut
  • déclaration_par_défaut est un mot réservé XML, les plus courants sont #REQUIRED et #IMPLIED

Exemple

<!ELEMENT voiture(couleur, nbKm, dateSortie)>
<!ATTLIST voiture immatriculation CDATA #REQUIRED marque CDATA #IMPLIED>  déclare un élément voiture avec un attribut immatriculation obligatoire et un attribut marque optionnel 
              
Contraintes sur les attributs.

  • un attribut de type NMTOKEN prend des valeurs qui doivent respecter les règles de nommage XML , alphanumérique, ne commençant pas par un chiffre...
  • Il est possible de fournir une liste de valeurs possibles : <!ATTLIST question reponse(oui | non)>
  • Identifiant, il possible de poser une contrainte d'unicité sur un attribut :
    <!ATTLIST voiture immatriculation ID #REQUIRED>
  • Référence d'identifiant : il est possible de
  • fixer ce qui peut ressembler à une contrainte d'intégrité référentielle
    <!ATTLIST conducteur voitImmatriculation IDREF #REQUIRED> dans ce cas la valeur de l'attribut voitImmatriculation doit référer une valeur d'un attribut défini ailleurs comme ID, mais pas forcément un numéro d'immatriculation !!

Exercice

Ecrire une DTD qui rend le document XML suivant valide :

fig 6 exercice

Solution

Les schémas XML

Les DTD permettent de décrire les structures de fichier XML, mais ils souffrent de plusieurs défauts si l'on veut décrire objets plus complexes, notamment ceux utilisés dans le monde de la programmation. Aussi une nouvelle norme tend à se substituer aux DTD, ce sont les schémas XML qui offrent des fonctionalités supplémentaires :

  • Les schémas sont écrits en XML (élément, attribut...) ce qui va permettre de traiter ces fichiers de déclaration en utilisant les bibliothèques de classes dédiés à XML (SAX et DOM, nous y reviendrons)
  • Un grand nombre de types est intégré (booleen, numériques) ainsi que la possibilité de définir des type personnalisés
  • Le concept d'héritage est supporté

Ceci va se faire au prix d'une complexite accrue. Aussi nous n'allons pas nous attacher à décrire les mécanismes des schémas.

Un exemple.

La plupart du temps, nous n'avons pas à nous soucier des schémas, ceci sont générés automatiquement par les environnements de développement. Regardons ainsi le schéma généré par Visual Studio pour l'exemple de la voiture :

fig 7 schéma XML du cas voiture

Commentaires :

  • Le schéma est écrit en XML. Remarquez la première ligne. Les éléments possèdent ainsi des attributs
  • Les valeurs des éléments sont typés
  • Les contraintes (unicité de clé) sont plus explicites
  • Ce schéma validera le même fichier XML que celui présenté en début de cours

Développement avec XML.

XML est présent aujourd'hui dans de nombreux domaines de l'informatique. Le traitement des fichiers XML ou des schémas est donc une préoccupation du développeur. Les outils logiciels permettant de lire (ou écrire) des fichiers XML sont appelés analyseur ou parseur. Il existe deux grandes approches très différentes : les api DOM et SAX.

L'Api DOM.

Le terme DOM (Document Object Model) est employé pour décrire deux choses, d'une part une spécification qui décrit un document à partir d'une hiérarchie d'objets :

fig 8 hiérarchie DOM

Ceci permet par exemple à un langage comme Javascript d'atteindre les composant d'un document.

Cette organisation permettra d'écrire :

document.forms[0].elements[0].value="bonjour"; // l'objet Window est implicite

  • forms représente ici la collection de d'objet form
  • elements représente la collection d'éléments du formulaire

D'autre part DOM est le nom donné à une bibliothèque (Api) de classes implémentée dans la plupart des langages et qui permet de traiter les fichiers XML en utilisant son organisation hiérarchique basée sur le modèle DOM.

L'Api DOM interprète un document XML comme une hiérarchie de noeuds. Le document est un noeud, tout comme un élément ou un atttribut

Exemple d'utilisation en C#.

Affichons les numéros d'immatriculation des voitures du fichier XML suivant :

fig 9 extraction des numéros d'immatriculation

Le code en C# sera :

     XmlDocument docXml = new XmlDocument();                          // ligne 1
     docXml.Load("voitures.xml");                                                    // ligne 2
     XmlNodeList desNoeuds;                                                         // ligne 3 
     desNoeuds = docXml.GetElementsByTagName("voiture");        // ligne 4
     foreach(XmlNode unNoeud in desNoeuds)                               // ligne 5
     {
           string s=unNoeud.FirstChild.InnerText;                                // ligne 6 
           lst.Items.Add(s);                                                                  // ligne 7
      }

Commentaires :

  • ligne 1 : on crée un objet XMLDocument
  • ligne 2 : chargement en mémoire
  • ligne 3 : déclaration d'une liste de noeuds
  • ligne 4 : récupération de la liste des noeuds de nom "voiture"
  • ligne 5 : parcours dans une boucle
  • ligne 6 : récupération du premier noeud enfant du noeud courant (l'immatriculation donc)
  • ligne 7 : ajout dans la liste

L'Api SAX.

Comme le montre l'exemple précédent, le fichier est complètement chargé en mémoire, ce qui peut être pénalisant sur des gros volumes, aussi une autre norme existe.

L'approche de SAX (Simple Api for Xml) est diffférente, contrairement à DOM, elle est basée sur une approche événementielle, et une lecture séquentielle du document XML :

• On trouve des notions de début et fin de document, d'élément, d'attribut,

• Permet de récupérer les informations lues par le parseur XML.

SAX, contrairement à DOM, n'implémente pas d'arbre en mémoire, donc orienté traitement de gros document XML.

Différents langages implémentent l'Api SAX, notamment Java. Le principe est toujours le même.

Une classe (le parseur) se charge d'instancier un objet pointant sur le fichier XML. Une autre classe, appelée gestionnaire (Handler) va implémenter les événements standards utiles pour l'application (startDocument, startElement, endDocument,...). Un objet de cette classe sera fournie au parseur. Il suffira ensuite d'appeler une méthode parse du parseur. Le code implémentant chaque événement s'exécutera automatiquement.

Application générant des objets de classe à partir de fichier XML

Soit la classe Voiture, définie ainsi :

public class Voiture
{
        public Voiture(string imma,string modele,string couleur,int nbKms,string dateSortie)
        {
                  this._immatriculation=imma;
                  this._modele=modele;
                  this._couleur=couleur;
                  this._nbKm=nbKms;
                  this._dateSortie=Convert.ToDateTime(dateSortie);
         }
         public string GetImma()
         {
                  return this._immatriculation;
          }
          public int GetNbKm()
         {
                  return this._nbKm;
          }
          public void Roule(int kms)
         {
                this._nbKm+=kms;
          }
private string _immatriculation;
private string _modele;
private string _couleur;
private int _nbKm;
private DateTime _dateSortie;
}

Instencions une ArrayList à partir du fichier XML :

XmlDocument docXml = new XmlDocument();
docXml.Load("voitures.xml");
XmlNodeList desNoeuds;
desNoeuds = docXml.GetElementsByTagName("voiture");
ArrayList lesVoitures = new ArrayList();
foreach(XmlNode unNoeud in desNoeuds)
{
       XmlNodeList noeudsEnfants = unNoeud.ChildNodes;
       string imma=noeudsEnfants[0].InnerText;
       string modele=noeudsEnfants[1].InnerText;
        string couleur=noeudsEnfants[2].InnerText;
        int km = Convert.ToInt32(noeudsEnfants[3].InnerText);
        string date=noeudsEnfants[4].InnerText;
        lesVoitures.Add(new Voiture(imma,modele,couleur,km,date));
}

Utilisons un objet Voiture :

Voiture v=(Voiture)lesVoitures[1];
v.Roule(100);
int n=v.GetNbKm();

La sérialisation

La sérialisation est un mécanisme qui permet de donner une représentation "à plat" d'un objet en mémoire. La désérialisation est le mécanisme inverse qui reconstruit en mémoire un objet à partir d'une représentation "à plat". Dans l'exercice précédente nous avons désérialisé un fichier XML afin de générer des objets Voiture. La sérialisation permet de conserver l'état d'un objet pour une manipulation ultérieure. Nous allons présenter sommairement ces mécanismes. DotNet offre trois possibilités.

A) La sérialisation SOAP

La sérialisation SOAP crée un fichier XML en utilisant "l'enveloppe" SOAP. SOAP (Simple Object Access Protocol) est un protocole au dessus de HTTP qui propose un format de messagerie pour la communication de machine à machine. Ce protocole est utilisé dans les services Web de communication et d'activation d'objets distants. L'avantage de ce protocole est qu'il est standard, ce qui rompt avec les protocoles ou technologies "propriétaires", Corba ou COM.

Pour cela, il nous faut une classe "conteneur" de voitures, la classe Garage jouera ce rôle :

using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;// ajouter la référence

[System.Serializable]
public class Garage
{
       public Garage()
       {
               _mesVoitures = new ArrayList();
        }
        public void AddVoiture(Voiture v)
        {
                _mesVoitures.Add(v);
         }
         public Voiture GetVoiture(int n)
        {
                  return (Voiture)_mesVoitures[n];
         }
                  private ArrayList _mesVoitures;
  }

Remarque : la déclaration [System.Serializable] indique au framework que la classe peut disposer du mécanisme de sérialisation. Il faudra ajouter cet attribut également pour la classe Voiture.

Une petite interface permet de sérialiser et désérialiser :

fig 10 mise en oeuvre de la sérialisation SOAP

A.1 Sérialisation SOAP.

Le code du click sur le bouton "SérialiseSoap" est :

Voiture v= new Voiture("1234YT93","laguna","bleue",1250,"12/1/2005");
Voiture v1= new Voiture("4444GT93","twingo","verte",10400,"1/11/2004");
Garage g = new Garage();
g.AddVoiture(v);
g.AddVoiture(v1);
FileStream fichier = new FileStream("garage.sr",FileMode.Create);
SoapFormatter sf = new SoapFormatter();
sf.Serialize(fichier,g);
fichier.Close();

Commentaire :

La classe SoapFormateur propose les objets permettant la génération du flux, ici un simple fichier texte. La méthode Serialize prend deux arguments, le fichier et l'objet.

Pour voir le fichier généré.

A.2 Désérialisation SOAP.

C'est l'opération inverse, nous allons lire le fichier et "reconstruire" le garage.

Le code du click sur le bouton "DésérialiseSoap" est :

FileStream fichier = new FileStream("garage.sr",FileMode.Open);
SoapFormatter sf = new SoapFormatter();
Garage g=(Garage)sf.Deserialize(fichier);
txt.Text=g.GetVoiture(1).GetImma();
fichier.Close();

B) La sérialisation binaire

Très proche de la technologie précédente dans ses fonctionnalités, le flux de sérialisation est ici de type binaire.

fig 11 interface pour la sérialisation binaire

B.1 Sérialisation binaire.

Le formateur est ici un BinaryFormatter. Le code est identique que pour le SoapFormatter.

using System.Runtime.Serialization.Formatters.Binary;

Voiture v= new Voiture("1234YT93","laguna","bleue",1250,"12/1/2005");
Voiture v1= new Voiture("4444GT93","twingo","verte",10400,"1/11/2004");
Garage g = new Garage();
g.AddVoiture(v);
g.AddVoiture(v1);
FileStream fichier = new FileStream("garageBinaire.txt",FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fichier,g);
fichier.Close();

Si vous voulez voir le fichier généré (simple curiosité...)

B.2 Désérialisation Binaire

FileStream fichier = new FileStream("garageBinaire.txt",FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
Garage g=(Garage)bf.Deserialize(fichier);
txt.Text=g.GetVoiture(1).GetImma();
fichier.Close();

C) La Sérialisation XML

L'interface est simple ici aussi :

fig 11 sérialisation XML

Une classe XMLSerializer est utilisée :

C.1 Sérialisation XML

using System.Runtime.Serialization;
using System.Xml.Serialization;

Voiture v= new Voiture("121GFT93","Twingo","verte",1235,"12/12/2004");
XmlSerializer serializer = new XmlSerializer(typeof(Voiture));
FileStream fichier = new FileStream("voitureXML.xml",FileMode.Open);
serializer.Serialize(fichier, v);
fichier.Close();

Par contre au niveau des classes sérialisée, un certain nombre de contrainte sont à respecter :

  • Un constructeur par défaut pour la classe Voiture
  • Il faut explicitement que les attributs aient des propriétés avec accesseur et modificateur. Voir le nouveau code de la classe
  • Le mécanisme fonctionne facilement pour des classes simples, si l'on désire sérialiser des objets complexes comme la classe Garage il faut utiliser un mécanisme plus complexe (attributs)

Le fichier sérialisé montre bien que les éléments correspondent bien aux propriétés et non aux attributs.

C.2 Désérialisation XML.

Le code est très proche des précédents, le formateur est seulement différent :

FileStream fichier = new FileStream("voitureXML.xml",FileMode.Open);
XmlSerializer serializer = new XmlSerializer(typeof(Voiture));
Voiture v= (Voiture)serializer.Deserialize(fichier);
txt.Text=v.immatriculation;
fichier.Close();