Vous êtes sur la page 1sur 51

Tutoriel DOM et JDOM

Présenté par Cyril Vidal


cyril@planetexml.com

Menu
SI vous consultez ce document en ligne, vous pouvez cliquer sur n'importe que lien pour aller à la section correspondante

1. Introduction 2
2. L'API Document Object Model 3
3. L'API JDOM 39

Tutoriel DOM et JDOM Page 1


Présenté par Cyril Vidal cyril@planetexml.com

Section 1. Introduction

Introduction
Ce tutoriel se donne pour objectif de donner un aperÇu des API DOM (Document
Object Model) et JDOM , dont la finalité est de lire et de manipuler des fichiers XML.

Il présuppose un minimum de connaissances à propos du XML et surtout du langage


JAVA, exlusivement utilisé ici. J'avoue ici humblement mon énorme dette vis-à-vis de
Brett McLaughlin (co-inventeur de JDOM), dont la lecture attentive de l'excellent
ouvrage JAVA & XML, publié aux éditions O'Reilly, m'a fortement inspiré pour la
rédaction de ce petit tutoriel. Si un point vous paraît obscur voire même inexact, je
serais heureux d'en prendre connaissance par mail à l'adresse cyril@planetexml.com

Bonne navigation!

Tutoriel DOM et JDOM Page 2


Présenté par Cyril Vidal cyril@planetexml.com

Section 2. L'API Document Object Model

Introduction au DOM
Les principales caractéristiques du modèle DOM sont les suivantes:
1. Le modèle DOM (contrairement sur ce point à une autre API fameuse: SAX),
représente une spécification qui puise ses origines dans le consortium w3C
(elle jouit donc sur ce point d'un niveau de 'respectabilité' égal à la spécification
XML elle-même).
2. Le modèle DOM est non seulement une spécification multi-plateformes, mais
aussi multi-langages: il existe des liaisons avec Java, Javascript, CORBA et
d'autres langages encore...
3. Le modèle DOM est organisé en niveaux plutôt qu'en versions.
* DOM niveau 1 représente une Recommandation acceptée dont la
spécification complète est disponible à l'adresse
http://www.w3.org/TR/REC-DOM-Level-1 (sa traduction franÇaise est
quant à elle disponible à l'adresse
http://xmlfr.org/w3c/TR/REC-DOM-Level-1/ ). Celle-ci détaille dans une
première partie un ensemble minimum d'objets et d'interfaces
fondamentales pour accéder et manipuler des objets documentaires (XML
mais aussi HTML), ainsi qu'un ensemble d'interfaces étendues spécifiques à
la manipulation de documents XML (traitant par exemple les CDATA ou les
Processing Instructions): cette première partie s'appelle le noyau DOM (core
DOM). Dans une seconde partie, la spécification DOM niveau 1 s'attache à
décrire les objets et les méthodes spécifiques aux documents HTML, qui
n'ont pas été définis dans le noyau.
* DOM niveau 2 , finalisé en novembre 2000, disponible à l'adresse
http://www.w3.org/TR/DOM-Level-2-Core/ étend le niveau 1 en proposant
un certain nombre d'interfaces supplémentaires. En ce qui concerne le
traitement de documents XML, DOM niveau 2 supporte en plus, par
exemple, les espaces de noms (on peut ainsi créer ou retrouver un élément
ou un attribut grâce non seulement à son nom local, mais aussi via son
espace de nom (ce sont par exemple et parmi de nombreuses autres
méthodes les méthodes attachées à l'interface Document
createElementNS(), createAttributeNS() et
getElementsByTagNameNS() qui permettent de faire cela).
* DOM niveau 3, finalisé le 22 octobre 2002, disponible à l'adresse
http://www.w3.org/TR/2002/WD-DOM-Level-3-Core-20021022/DOM3-Core.html ,
propose un certain nombre d'interfaces et de méthodes supplémentaires,
parmi lesquelles la possibilité de retrouver les informations relatives à la
déclaration XML (version, encodage), la comparaison de noeuds au
sein d'un document (via les méthodes isEqualNode() et
isSameNode(), la comparaison de la position entre deux noeuds au
sein d'un même document via la méthode
compareDocumentPosition(), et beaucoup d'autres choses encore...

Dans ce tutoriel, nous aborderons DOM Niveau 1 et 2, mais laisserons DOM


Niveau 3 de côté, pour lui consacrer plus tard un tutoriel spécifique, lorque les
parseurs le supporteront.

Tutoriel DOM et JDOM Page 3


Présenté par Cyril Vidal cyril@planetexml.com

Liaison avec les langages


La spécification DOM laisse le choix du langage à utiliser pour implémenter le modèle
DOM lui-même. Il faut donc développer des liaisons avec les langages afin de
représenter la structure conceptuelle de DOM et l'utiliser avec tel langage déterminé.
Ici, nous nous intéresserons à la liaison avec le langage Java. Celle-ci (version
supportant DOM Level 2) peut-être librement téléchargée à l'adresse
http://www.w3.org/TR/DOM-Level-2/java-binding.html Cependant, si l'analyseur et le
processeur XSLT que vous utilisez est assez récent, les paquetages DOM sont
désormais systématiquement inclus dans ces produits. Pour mieux comprendre les
rapports liant le modèle DOM de son implémentation concrète dans le lange JAVA,
considérons en premier lieu la définition IDL de l'interface Document:
interface Document :
Node {
readonly attribute DocumentType doctype;
readonly attribute DOMImplementation implementation;
readonly attribute Element documentElement;
Element createElement(in DOMString tagName) provoque (DOMException);
DocumentFragment createDocumentFragment();
Text createTextNode(in DOMString data);
Comment createComment(in DOMString data);
CDATASection createCDATASection(in DOMString data) provoque (DOMException);
ProcessingInstruction createProcessingInstruction(in DOMString target, in DOMString dat
Attr createAttribute(in DOMString Name) provoque (DOMException);
entityReference createEntityReference(in DOMString name) provoque (DOMException);
NodeList getElementsByTagName(in DOMString tagName); };

A fin de comparaison, l'implémentation Java d'une telle interface sera celle-ci:


package org.w3c.dom;
public interface Document extends Node {
public DocumentType getDoctype();
public DOMImplementation getImplementation();
public Element getDocumentElement();
public Element createElement(String tagName)
throws DOMException;
public DocumentFragment createDocumentFragment();
public Text createTextNode(String data);
public Comment createComment(String data);
public CDATASection createCDATASection(String data)
throws DOMException;
public ProcessingInstruction createProcessingInstruction(String target,
String data)
throws DOMException;
public Attr createAttribute(String name)
throws DOMException;
public EntityReference createEntityReference(String name)
throws DOMException;
public NodeList getElementsByTagName(String tagname);
public Node importNode(Node importedNode,
boolean deep)
throws DOMException;
public Element createElementNS(String namespaceURI,
String qualifiedName)
throws DOMException;
public Attr createAttributeNS(String namespaceURI,
String qualifiedName)

Tutoriel DOM et JDOM Page 4


Présenté par Cyril Vidal cyril@planetexml.com

throws DOMException;
public NodeList getElementsByTagNameNS(String namespaceURI,
String localName);
public Element getElementById(String elementId);
}

Comment DOM travaille


Comme il a déjà été dit précédemment, à l'aide du Modèle Objet de Document, les
programmeurs peuvent construire des documents, naviguer dans leur structure, et
ajouter, modifier, ou supprimer soit des éléments soit du contenu.

Par chance, cette API ressemble très étroitement à la structure des documents qu'elle
modélise. Par exemple, si l'on considère le document XML suivant:
<?xml version="1.0" encoding="iso-8859-1?">
<catalogue>
<livre>
<titre>La généalogie de la morale</titre>
<auteur>Friedrich Nietzsche</auteur>
<édition>folio essais</édition>
<ISBN>2-07-032327-7</ISBN>
</livre>
<livre>
<titre>Réflexions sur la poésie</titre>
<auteur>Paul Claudel</auteur>
<édition>folio essais</édition>
<ISBN>2-07-032746-9</ISBN>
</livre>
</catalogue>

DOM le représente ainsi:

Tutoriel DOM et JDOM Page 5


Présenté par Cyril Vidal cyril@planetexml.com

DOM présente les documents sous forme d'une hiérarchie d'objets


org.w3C.dom.Node, à partir desquels d'autres interfaces plus spécialisées sont
elles-mêmes implémentées: Document, Element, Attr, Text,... Grâce à ce modèle,
on peut traiter tous les composants DOM soit par leur type générique, Node, soit par
leur type spécifique (Element, Attr): de nombreuses méthodes de navigation, par
exemple getChildNodes() ou getLastChild() sont disponibles dans l'interface
Node de base, et permettent ainsi une navigation dans l'arborescence sans avoir à
s'inquiéter du type spécifique de composant traité.

Récupération de noeuds
Après cette brève présentation, utilisons DOM afin de parser et de récupérer des
données de notre fichier catalogue.xml.
La première chose à faire de créer un objet de type org.w3c.dom.Document. Tant
que l'intégralité du document n'a pas été analysée et ajoutée dans la structure
arborescente à ce niveau supérieur par rapport à l'élément racine du document XML,
les données du fichier d'entrée ne se trouvent pas dans un état utilisable. En fait,
comme le standard DOM ne spécifie pas de méthode pour obtenir l'objet Document, il
existe plusieurs méthodes à cette fin. Puisque nous nous focalisons sur le parseur
Xerces, la méthodologie à suivre est la suivante:
1. D'abord, ne pas oublier d'importer le parseur concerné, ici Xerces, via
l'instruction import org.apache.xerces.parsers.DOMParser
2. Ensuite, instancier un objet de la classe Parseur, via DOMParser parseur =
new DOMParser();
3. Enfin, utiliser la méthode getDocument() du parseur ainsi obtenu afin d'obtenir

Tutoriel DOM et JDOM Page 6


Présenté par Cyril Vidal cyril@planetexml.com

l'objet Document issu de l'analyse XML.

Voici le code correspondant:


//Importation de l'analyseur xerces
import org.apache.xerces.parsers.DOMParser;
public class TestDOM
{
public static void main( String [] args ) throws Exception
{
DOMParser parser = new DOMParser();
parser.parse("catalogue.xml");
Document document = parser.getDocument();
}
}

Nous verrons plus loin dans ce tutoriel qu'il existe une autre faÇon de procéder via
l'API JAXP afin de standardiser l'accès à une arborescence DOM depuis une
implémentation quelconque d'un analyseur.

Tentons à présent de récupérer les titres des livres composant le catalogue: Pour ce
faire, nous récupérons d'abord l'élément racine du document catalogue.xml, via la
méthode getDocumentElement() appliquée au noeud de type Document document
précédemment défini. Ensuite, nous définissons un noeud de type NodeList,
équivalent à une Collection Java, qui regroupe tous les éléments dont le type est titre,
via la méthode getElementsByTagName("titre"). Enfin, nous itérons sur cette
NodeList afin de récupérer la valeur de chacun des noeuds de type Text, fils des
éléments <titre>: en effet la structure hiérarchique du DOM impose de devoir
récupérer le contenu textuel d'un élément, non à partir de l'élément lui-même
(comme cela se fait avec JDOM ainsi que nous le verrons plus tard grâce à la
méthode getText()), mais à partir du noeud Text fils de l'élément. La première
étape (récupération du noeud Text fils de l'élément <titre> se fait grâce à la
méthode getFirstChild(), tandis que la récupération de la valeur textuelle se fait
en utilisant la méthode getNodeValue() (cette denière méthode s'applique
également aux noeuds CDATA, comment et processing instructions).

Voici le code correspondant auxx étapes que l'on vient de décrire:


//DOM
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
...
Element catalogue = document.getDocumentElement();
NodeList titres = catalogue.getElementsByTagName("titre");
System.out.println("Les titres des livres du catalogue sont: ");
for (int i=0; i<titres.getLength(); i++) {
System.out.println(titres.item(i).getFirstChild().getNodeValue());

Lorsque nous exécutons en ligne de commande ce fichier (dont la source est


disponible ici), nous obtenons l'écran suivant:

Tutoriel DOM et JDOM Page 7


Présenté par Cyril Vidal cyril@planetexml.com

Si nous voulons récupérer l'ensemble des informations afférentes à chacun des livres,
on risque de se livrer à un travail assez fastidieux. DOM est en effet un langage
bavard. Peut-être est-il plus judicieux de créer des méthodes réutilisables pour
chacune des quatre éléments caractérisant le livre (auteur, titre, edition, ISBN).

Récupération de noeuds (suite)


Nous allons donc créer dans fichier annexe, que nous appelerons AnnexeDOM.java,
différentes méthodes permettant de systématiser notre recherche d'éléments dans le
document xml à notre disposition.

On commence pour ce faire par définir une première méthode générique


trouveTexte(), prenant comme arguments à la fois un Element et une chaîne de
caractères String, et qui accomplit les deux actions suivantes:
1. D'abord, récupère l'élément de nom indiqué par le paramètre via la méthode
trouvePremierElement().
2. Ensuite, récupère le contenu textuel d'un tel élément via la méthode de même
nom que la méthode appelante, i.e. trouveTexte(), mais surchargée avec un
seul paramètre. (On rappelle que Java permet de définir dans une classe
plusieurs méthodes portant le même nom, tant que chaque méthode possède un
ensemble unique de paramètres. C'est ce que l'on appelle la surcharge de
méthode)

public static String trouveTexte( Element element, String nom )


{
Element elementNom = trouvePremierElement( element, nom );
return trouveTexte(elementNom );
}

La méthode trouvePremierElement est très simple: A partir d'une liste de noeuds


précédemment définie (en l'occurence, la liste des éléments livre contenus dans
l'élément catalogue), on définit une autre NodeList, comportant tous les noeuds
ayant le nom passé en argument. Si il n'y a aucun élément répondant à ce nom, un
message d'erreur est renvoyé. Sinon, le premier élement de la liste est renvoyé en
utilisant la méthode item() de la classe NodeList et en lui passant la valeur 0
comme argument (les index commencent tous par zéro). Le code de cette méthode est
donc le suivant:
public static Element trouvePremierElement( Element element, String nom ) {
NodeList nl = element.getElementsByTagName( nom );
if ( nl.getLength() < 1 )
throw new NullPointerException(

Tutoriel DOM et JDOM Page 8


Présenté par Cyril Vidal cyril@planetexml.com

"Element: "+element+" ne contient pas: "+nom);


return (Element)nl.item(0);
}

Enfin, la troisième et dernière méthode dont nous avons besoin,


trouveTexte(Element element) récupère la valeur textuelle de l'élément
passé en paramètre. Exactement de la même faÇon que vue dans le panneau
précédent, en descendant dans la hiérachie au niveau du noeud Text et en en
récupérant la valeur via la méthode getNodeValue()
public static String trouveTexte( Element element)
{
return element.getFirstChild().getNodeValue();
}

Le fichier source AnnexeDOM.java est disponible ici

Dès lors, la récupération de toutes les informations concernant chacun de ces livres
est très simplifiée: Après avoir repris les quelques étapes du panneau précédent
(instanciation d'un parseur, création du noeud Document, récupération de l'élément
racine du document, récupération de tous les éléments <titre>), on appelle pour
chaque caractéristique du livre définie dans catalogue.xml, la méthode de classe
trouveTexte() définie dans la classe AnnexeDOM, avec comme arguments un
noeud <livre> extrait de la NodeList sur laquelle on itère, et la chaîne de caractères
correspondant au nom de l'élément enfant de ce noeud <livre>que l'on veut
récupérer. Le code est le suivant:
//DOM
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
//Importation de l'analyseur xerces
import org.apache.xerces.parsers.DOMParser;
public class TestDOM1
{
public static void main( String [] args ) throws Exception
{
DOMParser parser = new DOMParser(); //instanciation parseur
parser.parse("catalogue.xml"); //analyse du fichier catalogue.xml
Document document = parser.getDocument(); //récupération du document englobant to
données analysées
Element catalogue = document.getDocumentElement();
//récupération de l'élément racine du document, ici <catalogue>
NodeList livres = catalogue.getElementsByTagName("livre");
//récupération de tous les éléments <livre> inclus dans <catalogue>
System.out.println("Les caractéristiques des livres du catalogue sont:");
for( int i=0; i<livres.getLength(); i++ ) {
String titre = AnnexeDOM.trouveTexte( (Element)livres.item(i),"titre" );
String auteur = AnnexeDOM.trouveTexte( (Element)livres.item(i),"auteur" );
String édition = AnnexeDOM.trouveTexte(
(Element)livres.item(i),"édition" );
String ISBN = AnnexeDOM.trouveTexte( (Element)livres.item(i),"ISBN" );
System.out.println("\ntitre: "+ titre+"\nauteur:
"+auteur+"\nédition: "+ édition +"\nISBN: "+ ISBN);
}
}
}

Lorqu'on exécute ce programme (dont le fichier source est disponible ici), on obtient

Tutoriel DOM et JDOM Page 9


Présenté par Cyril Vidal cyril@planetexml.com

cran suivant:

Test du type de noeuds


On peut améliorer certains points du code précédent. Par exemple, lorsqu'on veut
récupérer la valeur textuelle d'un noeud, on a vu que l'on utilisait la méthode
getFirstChild().getNodeValue(). Mais que se passe-t-il si par hasard on
devait modifier le document catalogue.xml pour ajouter un élément
<commentaire> donnant quelques précisions sur l'auteur de tel ou tel livre, comme
ceci:
<livre>
<titre>Réflexions sur la poésie</titre>
<auteur><commentaires>Paul Claudel n'est pas seulement grand
écrivain, mais aussi grand critique</commentaires>Paul
Claudel</auteur>
<édition>folio essais</édition>
<ISBN>2-07-032746-9</ISBN>
</livre>

Le code source correspondant au fichier catalogue1.xml est disponible ici.

En lanÇant TestDOM1 sur catalogue1.xml, on obtient alors le résultat suivant:

Bien évidemment, au niveau du livre Réfléxions sur la poésie, nous obtenons une
référence null pour son auteur. Simplement parce que le premier élément fils n'est
pas de type Text mais Element, ce pourquoi nous ne pouvons lui appliquer la
méthode getNodeValue().

Afin de résoudre cette difficulté, il nous faut donc changer la méthode


trouveText(Element element) de la classe AnnexeDOM, afin de tester le type

Tutoriel DOM et JDOM Page 10


Présenté par Cyril Vidal cyril@planetexml.com

de noeud concerné. Il existe pour cela deux méthodes:


1. Mot-clé java: instance of On commence par créer une NodeList qui stocke
tous les noeuds fils de l'élément passé en argument. Nous testons ensuite
chacun de ces noeuds, afin de savoir si son type est Text ou non, via l'instruction
if ( numéro instanceof Text ). Si c'est le cas, nous l'ajoutons au buffer.
Enfin, nous créons un objet String à partir de l'objet StringBuffer en utilisant
la méthode toSring() afin que le résultat puisse être affiché sur l'écran.
StringBuffer buffer = new StringBuffer();
NodeList fils = element.getChildNodes();
for(int i=0; i<fils.getLength(); i++) {
Node numéro = fils.item(i);
if ( numéro instanceof Text )
buffer.append( numéro.getNodeValue() );
}
return buffer.toString();

Le fichier source du fichier AnnexeDOMbuf.java est disponible ici

2. Méthode de l'interface Node: getNodeType() L'interface Node définit pour


accomplir la même tâche une méthode utilitaire, getNodeType(), qui retourne
une valeur entière. On peut comparer cette valeur avec un ensemble de
constantes également définies dans l'interface Node , qui sont, entre autres:
Node.DOCUMENT_NODE, Node.ELEMENT_NODE, Node.TEXT_NODE,
Node.CDATA_SECTION_NODE, Node.COMMENT_NODE,
Node.PROCESSING_INSTRUCTION_NODE, Node.DOCUMENT_TYPE_NODE. Ici,
puisque nous voulons tester si le noeud est de type Text, nous utilisons donc
l'instruction if (numéro.getNodeType() == Node.TEXT_NODE), ce qui donne:
StringBuffer buffer = new StringBuffer();
NodeList fils = element.getChildNodes();
for(int i=0; i<fils.getLength(); i++) {
Node numéro = fils.item(i);
if ( numéro.getNodeType() == Node.TEXT_NODE )
buffer.append( numéro.getNodeValue() );
}
return buffer.toString();

Récupération d'attributs
Voyons à présent comment récupérer un genre particuliers de noeuds que
représentent les attributs. Supposons que dans notre fichier catalogue.xml, nous
rajoutions des attributs, spécifiant par exemple la langue utilisée:
<catalogue>
<livre>
<titre langue="fr">La généalogie de la morale</titre>
...
</livre>
<livre>
<titre langue="fr">Réflexions sur la poésie</titre>
...
</livre>
</catalogue>

Tutoriel DOM et JDOM Page 11


Présenté par Cyril Vidal cyril@planetexml.com

et que nous voulions les récupérer afin d'afficher le nom et la valeur de ces attributs.

Il nous faut rajouter une méthode trouveAttribut() dans notre fichier


AnnexeDOM.java, qui puisse faire cela:
public static void trouveAttribut( Element element, String nom )
{
Element elementNom = trouvePremierElement( element, nom );
NamedNodeMap attributs = elementNom.getAttributes();
for(int i=0; i<attributs.getLength(); i++) {
Node numéro = attributs.item(i);
String nomAttribut = numéro.getNodeName();
String valeurAttribut = numéro.getNodeValue();
System.out.print(nomAttribut + " =\"" + valeurAttribut + "\"");
}
}

Le fonctionnement d'une telle méthode est le suivant: En premier lieu, nous réutilisons
la méthode trouvePremierElement déjà définie dans la classe afin de récupérer
chaque premier élément d'un nom donné au sein d'un élément donné (ici, comme on
l'a vu, il s'agit d'un élément <livre>) Ensuite, nous récupérons les attributs de cet
élément grâce à la méthode getAttributes (proposée par l'interface Node) ,
laquelle retourne un objet NamedNodeMap. Cet objet partage avec NodeList la
propriété d'être itérable. C'est donc ce que nous faisons, en récupérant à chaque fois
à la fois le nom de l'attribut via getNodeName() ainsi que sa valeur, via
getNodeValue()

Pour appeler cette méthode à partir de la classe TestDOM, il suffit alors d'insérer entre
deux instructions d'impression écran, l'appel à notre méthode trouveAttribut(),
comme ceci:
System.out.println("Les caractéristiques des livres du catalogue sont:");
for( int i=0; i<livres.getLength(); i++ ) {
String titre = AnnexeDOMbuf1.trouveTexte( (Element)livres.item(i),"titre" )
String auteur = AnnexeDOMbuf1.trouveTexte( (Element)livres.item(i),"auteur"
String édition = AnnexeDOMbuf1.trouveTexte(
(Element)livres.item(i),"édition" );
String ISBN = AnnexeDOMbuf1.trouveTexte( (Element)livres.item(i),"ISBN"
System.out.println("\ntitre: "+ titre);
AnnexeDOMbuf1.trouveAttribut((Element)livres.item(i),"titre");
System.out.print("\nauteur: " + auteur + "\nédition: " + édition + "\nISBN: "+ ISB

En exécutant la classe ainsi définie, nous obtenons l'écran suivant:

Les fichiers sources sont: catalogue2.xml, TestDOM2.java, et enfin


AnnexeDOMbuf1.java.

Tutoriel DOM et JDOM Page 12


Présenté par Cyril Vidal cyril@planetexml.com

Création et Modification d'une arboresence DOM (via


servlet)
Nous allons voir dans cet exemple un petit peu plus sophistiqué (car utilisant une
servlet) la faÇon de modifier une arborescence DOM. Le but est le suivant: on propose
un formulaire à l'utilisateur dans lequel celui-ci peut indiquer le n°, le titre, l'auteur, ainsi
qu'une courte description d'un livre. A partir de là, de deux choses l'une: soit il existe
déjà un livre de même numéro et alors il faut mettre à jour le détail du livre, soit le livre
n'existe pas, et il faut alors créer le fichier XML correspondant. Le code HTML du
formulaire de départ pourrait être le suivant:
<html>
<head><title>Saisie/Mise à jour d'un livre</title></head>
<body>
<h1 align='center'>Saisie/Mise à jour d'un livre</h1>
<p align='center'>
<form method='POST' action='/tuto/update'>
Numéro du livre: <br/>
<input name='no' type='text' maxLength='10' /><br/><br/>
Titre du livre: <br />
<input name='titre' type='text' maxLength='50' /><br /><br />
Auteur du livre: <br />
<input name='auteur' type='text' maxLength='50' />
<br /><br />
Description du livre: <br />
<textarea name='description' rows='10' cols='30' wrap='wrap'
>
</textarea><br /><br />
<input type='reset' value='Recommencer' />
<input type='submit' value='Ajouter/Mettre à jour' />
</form>
</p>
</body>
</html>

On remarque que la cible de ce formulaire est comme dit précédemment une servlet,
et que le fait d'appuyer sur le bouton de valeur Ajouter/Mettre à jour lancera la
méthode doPost() de la servlet située à l'URL
http://localhost:8080/tuto/update. Nous utilisons ici le moteur de servlet
Tomcat4.0.1 dont nous avons déjà décrit l'installation dans un précédent tutoriel, à
l'adresse http://www.planetexml.com/base_xml/base_xml-4-3.html . La seule chose à
préciser ici est la manière d'accéder à la servlet. Nous avons créé l'arborescence
webapps->tuto->WEB-INF->classes ->MiseAJourServlet.class, ainsi que le
montre la fenêtre d'Explorateur Windows suivante:

Tutoriel DOM et JDOM Page 13


Présenté par Cyril Vidal cyril@planetexml.com

Ceci étant mis en place, il suffit juste d'éditer un fichier web.xml dans le dossier
WEB-INF et d'y insérer les quelques lignes de code suivantes:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>UpDate</servlet-name>
<servlet-class>MiseAJourServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UpDate</servlet-name>
<url-pattern>/update</url-pattern>
</servlet-mapping>
</web-app>

Le premier élément <servlet> associe un nom, ici UpDate à la classe de servlet


MiseAJourServlet, tandis que le deuxième élément <servlet-mapping> fait
correspondre un tel nom UpDate à une URL, ici /update, valable à partir du contexte
de la servlet, ici tuto (le sous-dossier contenant une telle servlet et directement enfant
du dossier webapps). Notre servlet sera donc ainsi accessible en pointant sur l'URL
http://localhost:8080/tuto/update.

De plus, si l'on veut éviter de redémarrer Tomcat à chaque fois que l'on modifie
une classe dans notre répertoire de travail, il convient également de modifier le
fichier server.xml situé dans le répertoire conf de Tomcat de la faÇon suivante:
<!--Contexte Repertoire tuto -->
<Context path="/tuto" docBase="tuto" debug="0"
reloadable="true"/>

L'attribut reloadable="true" permet de pouvoir reloader les classes


automatiquement dès que des changements sont détectés. Si l'on voulait changer
l'adresse URL de notre servlet, par exemple en

Tutoriel DOM et JDOM Page 14


Présenté par Cyril Vidal cyril@planetexml.com

/update, il suffirait de changer l'attribut path de la faÇon suivante:


path="/cyril"

. Regardons à présent à quoi pourrait ressembler notre méthode doPost():


//importations nécessaires
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.xml.sax.SAXException;
// DOM
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
// parseur
import org.apache.xerces.dom.DOMImplementationImpl;
import org.apache.xerces.parsers.DOMParser;
//Recherche le fichier nommé et soit le crée, soit le remplace
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// Récupère les valeurs de paramètres
String no = req.getParameterValues("no")[0];
String titre = req.getParameterValues("titre")[0];
String auteur = req.getParameterValues("auteur")[0];
String description = req.getParameterValues("description")[0];
// Contrôle si le fichier existe
Document doc = null;
File fichierXML = new File(REPERTOIRE + "livre-" + no + ".xml");
if (!fichierXML.exists()) {
// Creer une nouvelle arborescence DOM
DOMImplementation domImpl = new DOMImplementationImpl();
doc = domImpl.createDocument(null, "livre", null);
Element root = doc.getDocumentElement();
// no du livre comme attribut
root.setAttribute("no", no);
//Titre du livre
Element elementTitre = doc.createElement("titre");
Text texteTitre = doc.createTextNode(titre);
elementTitre.appendChild(texteTitre);
root.appendChild(elementTitre);
// Auteur du livre
Element elementAuteur = doc.createElement("auteur");
Text texteAuteur = doc.createTextNode(auteur);
elementAuteur.appendChild(texteAuteur);
root.appendChild(elementAuteur);
// Description du livre
Element elementDescription = doc.createElement("description");
Text texteDescription = doc.createTextNode(description);
elementDescription.appendChild(texteDescription);
root.appendChild(elementDescription);

La méthode ci-dessus exposée commmence par récupérer les valeurs des quatre
paramètres no, titre, auteur et description. Une fois cela fait, elle crée un
nouvel arbre DOM via la méthode createDocument() (introduite dans DOM

Tutoriel DOM et JDOM Page 15


Présenté par Cyril Vidal cyril@planetexml.com

Niveau 2), appliquée à la classe DOMImplementation, classe de base de tout travail


de création de DOM. Ici, nous utilisons l'implémentation de xerces,
org.apache.xerces.dom.DOMImplementationImpl, aucune manière neutre en
terme de vendeur n'étant pour l'instant disponible, même si DOM Niveau 3 et l'API
JAXP lancent quelques pistes en ce sens. La méthode createDocument prend
comme premier argument l'espace de nommage de l'élément racine du document
(pour l'instant, nous n'en utilisons aucun et nous indiquons donc la valeur null). Le
second argument de cette seconde méthode représente le nom de l'élément racine
lui-même (ici livre) et le troisième élément représente une instance de DocType
associée à ce document (ici, nous n'en utilisons aucune, et nous spécifions donc
encore la valeur null). Une fois l'arbre DOM crée, nous récupérons l'élément racine
via la méthode getDocumentElement(), auquel nous ajoutons dans un premier
temps un attribut no via setAttribute("no",no), puis dans un deuxième temps
les éléments titre, auteur et description. Dans ce dernier cas, la méthode est
toujours la même: on commence par créer l'élément lui-même via la méthode
createElement( String nomElement), puis le contenu textuel de cet élément
via createTextNode(String texte), que nous finissons par attacher à l'élément
racine via la méthode appendChild(Element parent)

Sinon, si le fichier spécifié existe déjà, il nous faut alors le modifier à l'aide des
nouveaux paramètres renseignés par l' utilisateur. Le code afin de parvenir à un tel but
pourrait être le suivant:
DOMParser parser = new DOMParser();
parser.parse(fichierXML.toURL().toString());
doc = parser.getDocument();
Element root = doc.getDocumentElement();
// Titre du livre
NodeList elementsTitre = root.getElementsByTagName("titre");
Element elementTitre = (Element)elementsTitre.item(0);
Text texteTitre = (Text)elementTitre.getFirstChild();
texteTitre.setData(titre);
// Auteur du livre
NodeList elementsAuteur = root.getElementsByTagName("auteur");
Element elementAuteur = (Element)elementsAuteur.item(0);
Text texteAuteur = (Text)elementAuteur.getFirstChild();
texteAuteur.setData(auteur);
// Description du livre
NodeList elementsDescription =
root.getElementsByTagName("description");
Element elementDescription = (Element)elementsDescription.item(0);
// Supprime et recrée la description
root.removeChild(elementDescription);
elementDescription = doc.createElement("description");
Text texteDescription = doc.createTextNode(description);
elementDescription.appendChild(texteDescription);
root.appendChild(elementDescription);

On commence par parser le document existant via la méthode parse déjà vue, puis
on récupère son élément racine, toujous avec la méthode getDocumentElement().
A partir de là, on récupère chacun des premiers noeuds de nom <auteur> ou
<titre>, puis on leur transfère un contenu contextuel correspondant au paramètre
de même nom via la méthode setData(). Pour l'élément <description>, on utilise
une approche légèrement différente car un tel élément peut contenir de nombreux
éléments imbriqués (par exemple des élément HTML) qui empêchent la récupération
du premier élément textuel à remplacer par la valeur du paramètre description. Le
plus simple ici est d'enlever directement l'élement <description> de la hiérarchie

Tutoriel DOM et JDOM Page 16


Présenté par Cyril Vidal cyril@planetexml.com

via la méthode removeChild() et de le remplacer par un nouveau auquel on affecte


la valeur textuelle égale à la valeur du paramètre.

Enfin, on sérialise l'arbre DOM (via la classe DOMSerialiseur.java que nous détaillons
au prochain chapitre), puis on écrit un message de bonne exécution de la requête via
le code suivant:
// Serialise l'arborescence DOM
DOMSerialiseur serializer = new DOMSerialiseur();
serializer.serialise(doc, fichierXML);
// Confirmation écrite du traitement
PrintWriter out = res.getWriter();
res.setContentType("text/html");
out.println("Merci pour votre requête: " +
"Celle-ci a bien été traitée.");
out.close();

Tant qu'à faire, onpeut également mettre le code HTML du formulaire au sein de la
servlet elle-même, par exemple à travers la méthode doGet(). Code source complet
de la servlet MiseAJourServlet.java Lorqu'on pointe le navigateur sur l'adresse
http://localhost:8080/tuto/update, on obtient alors l'écran suivant:

Si l'on renseigne le formulaire de la manière suivante:

Tutoriel DOM et JDOM Page 17


Présenté par Cyril Vidal cyril@planetexml.com

l'on obtient l'écran suivant après avoir cliqué sur le bouton de confirmation de requêtes,
si tout se passe bien:

et l'on peut vérifier la présence du fichier livre-1.xml dans le dossier


C:\DOM\tuto, qui sera le suivant:

Si l'on remplit à nouveau le formulaire de la faÇon suivante:

Tutoriel DOM et JDOM Page 18


Présenté par Cyril Vidal cyril@planetexml.com

on vérifie que l'on obtient à présent le fichier livre-1.xml suivant:

Sérialisation
Jusqu'à présent, nous avons parsé le document catalogue.xml ou variantes, en
construisant grâce à Xerces une arborescence DOM de ce document. A partir de là,
nous avons récupéré tel ou tel élément, ou bien encore tel ou tel attribut de cet arbre
DOM. Cependant, l'une des quesiotns les plus courantes concernant l'utilisation de
DOM concerne la sérialisation des arborescences DOM, autrement dit, la faÇon dont
celles-ci peuvent être enregistrées dans un fichier. En fait, les Niveaux 1 et 2 de
DOM ne proposent aucune manière de faire cela, et cela reviendra à la charge de
DOM Niveau 3. En attendant, et avant de voir une autre faÇon de contourner le
problème via JAXP1.1 et l'API TrAX, voyons comment procéder à une telle

Tutoriel DOM et JDOM Page 19


Présenté par Cyril Vidal cyril@planetexml.com

rialisation via des moyens ordinaires (et un peu lourds, il faut le reconnaître...). Etant
donné que nous avons plusieurs versions du fichier catalogue.xml, une bonne idée
consiste peut-être à indiquer le nom du fichier à sérialiser en paramètre, afin de ne pas
avoir à changer celui-ci dans le code et de recompiler à chaque fois. Pour cela, nous
écrivons le code suivant:
import java.io.File;
import org.w3c.dom.Document;
import org.apache.xerces.parsers.DOMParser;
public class Serialiseur {
public void serialise(String documentXML, String nomFichierSortie)
throws Exception {
File fichierSortie = new File(nomFichierSortie);
DOMParser parseur = new DOMParser();
parseur.parse(documentXML);
Document document = parseur.getDocument();
//Sérialise
}
public static void main(String[] args) {
if (args.length !=2) {
System.out.println(
"Usage: java Serialiseur [document XML à lire] "
+ "[nom du fichier de sortie]");
System.exit(0);
}
try {
Serialiseur serialiseur = new Serialiseur();
serialiseur.serialise(args[0], args[1]);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Il n'y a rien de particulier à dire jusqu'ici: nous définissons la classe Serialiseur


avec une méthode serialise() qui prend comme arguments respectivement le
document XML à sérialiser et le fichier de sortie. Si un nombre différent d'arguments
est passé, on obtient alors un message d'information sur la procédure à suivre. La
méthode principale utilise elle-même la méthode printStackTrace() de l'objet
Exception, qui fournit un message et la trace de la pile au flux de sortie d'erreur
standard, ici l'écran.

Sérialisation (suite 1)
Dans le code défini dans le panneau précédent, il nous manque bien sûr l'essentiel,
c'est-à-dire la définition d'une classe effective de sérialisation. Nous allons ici
commenter pas à pas une classe de sérialisation codée par Brett McLaughlin, un des
gourous JAVA/XML, et qu'il propose dans le chapitre consacré à DOM dans son livre
JAVA&XML publié aux éditions O'REILLY. Cette classe de sérialisation reste
imparfaite, notamment en termes d'indentations et de retours chariots, mais elle donne
un très bon aperÇu de la faÇon dont on peut travailler en DOM. Dans un premier
temps, nous configurons notre classe DOMSerialiseur de manière à la rendre la
plus générique possible. Cette configuration concerne:
1. la mise en forme du flot de sortie d'une part, via la définition de deux variables
d'instance privées indentation et sautLigne, ainsi que la définition de deux

Tutoriel DOM et JDOM Page 20


Présenté par Cyril Vidal cyril@planetexml.com

accesseurs en modification de ces deux variables, respectivement


setIndentation() et setSautLigne(). (On rappelle que par convention, le
nom de l'accesseur, qu'il soit en modification ou en consultation, doit être préfixé
respectivement par le mot set ou get, suivi du nom de la variable qu'il modifie en
passant la première lettre de la variable en majuscule mais en conservant la
casse pour le reste des lettres). D'autre part, pour éviter toute ambiguîté, lorque le
nom du paramètre d'une méthode est identique au nom d'un membre de données
de la classe, on doit utiliser le nom this pour faire référence au membre de
données de la classe.
2. Ensuite, nous nous occupons de considérer les principaux formats de sortie du
flot XML, lesquels concernent principalement le format File ainsi que
OutputStream, et qui peuvent tous deux se ramener grâce aux classes Java
E/S java.io.OutputStreamWriter et java.io.FileWriter à une même
classe de base: la classe abstraite java.io.Writer.

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class DOMSerialiseur {
// Indentation à utiliser
private String indentation;
// Saut de ligne à utiliser
private String sautLigne;
// Constructeur initialisant les membres de données
public DOMSerialiseur() {
indentation = "";
sautLigne = "\n";
}
// Accesseur en modification définissant l'indentation à utiliser
public void setIndentation(String indentation) {
this.indentation = indentation;
}
// Accesseur en modification définissant le saut de ligne à utiliser
public void setSautLigne(String sautLigne) {
this.sautLigne = sautLigne;
}
//Sérialisation de l'arborescence DOM en l'OutputStream de sortie
mentionné
public void serialise(Document doc, OutputStream out)
throws IOException {
Writer writer = new OutputStreamWriter(out);
serialise(doc, writer);
}
//Sérialisation de l'arborescence DOM vers le fichier de sortie
mentionné
public void serialise(Document doc, File fichier)
throws IOException {
Writer writer = new FileWriter(fichier);
serialise(doc, writer);
}
//Sérialisation de l'arborescence DOM vers le Writer mentionné
public void serialise(Document doc, Writer writer)

Tutoriel DOM et JDOM Page 21


Présenté par Cyril Vidal cyril@planetexml.com

throws IOException {
// Exécution de la sérialisation via la méthode
serialiseNoeud() définie dans
le panneau suivant.
//On s'assure finalement que tout le flot soit vidé
writer.flush();
}

Sérialisation (suite 2)
Venons en enfin à notre méthode de sérialisation, serialiseNoeud(). Un des
grands avantages du Modèle Objet Document consiste en ce que toutes les structures
qu'il définit étendent l'interface Node, et donc qu'une seule et même méthode, à
condition de différencier chacune de ces structures dans ce qu'elle a de spécifique (par
exemple via une commande switch) permet de définir et de gérer le processus
consistant à traverser l'arborescence DOM.

On commence ainsi par tester via la méthode getNodeType() le cas d'un noeud de
type Document: si la condition est remplie, on commence par écrire la déclaration
XML (non prise en charge par DOM Niveau 2: DOM Niveau 3 devrait remédier à
cela), puis on saute une ou plusieurs lignes suivant la faÇon dont on a défini notre
variable d'instance sautLigne, et enfin on boucle sur chacun des noeuds fils de
Document, récupérés par l'intermédiaire de la méthode getChildNodes(), en
rappelant de manière récursive la même méthode serialiseNoeud().
public void serialiseNoeud(Node noeud, Writer writer,
String niveauIndentation)
throws IOException {
// Determine l'action à accomplir en fonction du type de noeud
switch (noeud.getNodeType()) {
case Node.DOCUMENT_NODE:
writer.write("<?xml version='1.0'
encoding='iso-8859-1'?>");
writer.write(sautLigne);
// boucle sur chaque enfant
NodeList noeuds = noeud.getChildNodes();
if (noeuds != null) {
for (int i=0; i<noeuds.getLength(); i++) {
serialiseNoeud(noeuds.item(i), writer, "");
}
}
break;
...
}
}

On remarque ici que les éléments fils du document racine (dans notre cas, il s'agit de
l'unique élément <catalogue> seront écrits en sortie juste en dessous de la déclaration
XML, sans indentation par rapport à celle-ci. Si l'on veut modifier cela, il suffit
simplement de rajouter des espaces blancs au sein des guillemets dans l'instruction
serialiseNoeud(noeuds.item(i), writer, "");. On définit ainsi une
indentation par défaut à laquelle on ajoute à chaque niveau hiérarchique de
l'arborescence l'indentation définie par la variable d'instance SautLigne. Sans
surprise, l'action à accomplir relativement à un Element consiste à afficher son nom,

Tutoriel DOM et JDOM Page 22


Présenté par Cyril Vidal cyril@planetexml.com

ses attributs, sa valeur, puis à s'occuper de ses fils. Cela se fait sans problèmes via
respectivement les méthodes getNodeName(), getAttributes() et en rappelant
de manière récursive toujours la méthode serialiseNoeud() pour le reste. Ce qui
donne le code suivant, à mettre à la suite du précédent:
case Node.ELEMENT_NODE:
String nom = noeud.getNodeName();
writer.write(niveauIndentation + "<" + nom);
NamedNodeMap attributs = noeud.getAttributes();
for (int i=0; i<attributs.getLength(); i++) {
Node courant = attributs.item(i);
writer.write(" " + courant.getNodeName() +
"=\"" + courant.getNodeValue() +
"\"");
}
writer.write(">");
// boucle sur chaque enfant
NodeList enfants = noeud.getChildNodes();
if (enfants != null) {
if ((enfants.item(0) != null) &&
(enfants.item(0).getNodeType() ==
Node.ELEMENT_NODE)) {
writer.write(sautLigne);
}
for (int i=0; i<enfants.getLength(); i++) {
serialiseNoeud(enfants.item(i), writer,
niveauIndentation + indentation);
}
if ((enfants.item(0) != null) &&
(enfants.item(enfants.getLength()-1)
.getNodeType() ==
Node.ELEMENT_NODE)) {
writer.write(niveauIndentation);
}
}
writer.write("</" + nom + ">");
writer.write(sautLigne);
break;

Il est à remarquer qu'on teste en début et en fin de boucle sur les enfants le type du
premier et du dernier fils afin de rajouter soit un saut de ligne (dans le cas du premier
fils élément), soit la même indentation que la balise ouvrante de l'élément père affectée
à la balise fermante. Si, au lieu de :
if ((enfants.item(0) != null) &&
(enfants.item(enfants.getLength()-1)
.getNodeType() ==
Node.ELEMENT_NODE)) {
writer.write(niveauIndentation);

on écrivait simplement ceci:


writer.write(niveauIndentation)

on obtiendrait alors des espaces blancs (autant qu'en comporte l'indentation courante)
à la fin de chaque contenu textuel d'élément. En effet, comme il a déjà été dit, le
contenu textuel d'un élément est considéré comme noeud fils de cet élément. Aussi, un
élément ne contenant que du contenu textuel obéit à la condition if (enfants !=
null) et nous oblige à tester une condition supplémentaire (le fait que le dernier fils
de l'élément considéré soit de type Element) avant de définir l'indentation de la balise

Tutoriel DOM et JDOM Page 23


Présenté par Cyril Vidal cyril@planetexml.com

de fermeture.

Il nous faut tester ensuite les noeuds de type Text, CDATA, COMMENT, PROCESSING
INSTRUCTION, , ENTITY REFERENCE, principalement via la méthode
getNodeValue().
case Node.TEXT_NODE:
writer.write(noeud.getNodeValue());
break;
case Node.CDATA_SECTION_NODE:
writer.write("<![CDATA[" +
noeud.getNodeValue() + "]]>");
break;
case Node.COMMENT_NODE:
writer.write(niveauIndentation + "<!-- " +
noeud.getNodeValue() + " -->");
writer.write(sautLigne);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
writer.write("<?" + noeud.getNodeName() +
" " + noeud.getNodeValue() +
"?>");
writer.write(sautLigne);
break;
case Node.ENTITY_REFERENCE_NODE:
writer.write("&" + noeud.getNodeName() + ";");
break;

Il faut noter que le noeud de type Processing Instruction est un peu particulier,
puisqu'il requiert à la fois les méthodes getNodeName() et getNodeValue() pour
être correctement affiché en sortie. Si nous n'utilisons que getNodeName(), nous
obtenons l'écran suivant en sortie:

alors que si nous n'utilisons que getNodeValue(), nous obtenons dans ce cas le
résultat suivant:

Tutoriel DOM et JDOM Page 24


Présenté par Cyril Vidal cyril@planetexml.com

Enfin, il nous faut considérer le cas des noeuds de type DocumentType, qui
représentent des déclarations DOCTYPE. Comme on peut y trouver des données
spécifiques, il faut veiller à transtyper l'instance de Node vers l'interface
DocumentType afin d'accéder à ces données supplémentaires. Les méthodes à
utiliser sont alors getName(), getPublicId() pour récupérer l'identifiant public (s'il
existe), et getSystemId() pour l'ID système de la DTD référencée. on obtient alors
le code suivant:
case Node.DOCUMENT_TYPE_NODE:
DocumentType docType = (DocumentType)noeud;
writer.write("<!DOCTYPE " + docType.getName());
if (docType.getPublicId() != null) {
System.out.print(" PUBLIC \"" +
docType.getPublicId() + "\" ");
} else {
writer.write(" SYSTEM ");
}
writer.write("\"" + docType.getSystemId() + "\">");
writer.write(sautLigne);
break;

Il ne reste plus dès lors qu'à compléter notre fichier DOMSerialiseur.java et d'y
placer l'instruction suivante: serialiseur.serialise(document,
fichierSortie); à la place du commentaire //Sérialise précédemment mis par
défaut (voir le panneau Sérialisation on page 19 ).

Si l'on veut d'autre part changer la valeur des variables privées indentation et
sautLigne, il suffit d'ajouter à la suite par exemple le code suivant:
serialiseur.setIndentation(" ");
serialiseur.setSautLigne("\n\n");

En exécutant la ligne suivante dans la fenêtre DOS:


java Serialiseur catalogue.xml sortie.xml

, nous obtenons alors le fichier sortie.xml suivant:

Tutoriel DOM et JDOM Page 25


Présenté par Cyril Vidal cyril@planetexml.com

Les fichiers source sont Serialiseur.java et DOMSerialiseur.java

DOM et JAXP 1.1 (parsing)


Regardons à présent ce que l'API de Sun JAXP (JAVA API for XML Parsing)peut nous
apporter par rapport aux tâches que nous avons déjà vues. (Toutes les versions
récentes des parseurs prennent en charge cet API, il n'est donc pas nécessaire de la
télécharger séparément: toutefois, JAXP est disponible en téléchargement sur le site
de Sun ou de la fondation apache sous forme de l'archive jaxp.jar). Avant de rentrer
dans le détail du code, il est important de mentionner le fait que JAXP se situe à un
niveau supérieur par rapport à l'API DOM (ou SAX ou JDOM que nous verrons
prochainement), et que, de ce fait, elle n'offre aucune manière d'analyser du code
XML, tâche qui reste dévolue aux trois API mentionnées. JAXP permet seulement de
rendre beaucoup plus accessibles et manipulables certaines fonctionnalités
dont DOM ou les autres API standard s'acquittent plutôt difficilement.

Premièrement, parser un document XML se réalise avec plus de simplicité: avant, on


s'en souvient (panneau Récupération de noeuds on page 6 ), il nous fallait avant tout
créer une instance de DOMParser en utilisant l'implémentation d'un analyseur
spécifique à un vendeur (nous avions ainsi utilisé Xerces de la fondation
apache.org), opération qui nous oblige à changer le code et le recompiler en cas de
modification de parseur. L'API JAXP de Sun propose de ce point de vue une bien
meilleure alternative, puisqu'elle permet d'utiliser la classe d'analyse d'un vendeur
sous la forme d'une propriété système Java: ainsi, JAXP fournit un mode d'analyse

Tutoriel DOM et JDOM Page 26


Présenté par Cyril Vidal cyril@planetexml.com

neutre par rapport aux vendeurs.

On se rappelle le code de notre programme récupérant les titres des livres du


catalogue:
//DOM
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
//Importation de l'analyseur xerces
import org.apache.xerces.parsers.DOMParser;
public class TestDOM
{
public static void main( String [] args ) throws Exception
{
DOMParser parser = new DOMParser();
parser.parse("catalogue.xml");
Document document = parser.getDocument();
Element catalogue = document.getDocumentElement();
NodeList titres = catalogue.getElementsByTagName("titre");
System.out.println("Les titres des livres du catalogue sont: ");
for (int i=0; i<titres.getLength(); i++) {
System.out.println(titres.item(i).getFirstChild().getNodeValue());
}
}
}

Les trois lignes surlignées en gras font appel aux classes spécifiques de Xerces: en
fait, comme il a déjà été dit, la spécificaton DOM ne fournit pas de standard pour
obtenir un noeud de type Document. C'est à ce niveau précis qu'intervient JAXP, à
travers l'utilisation des classes javax.xml.parsers et
javax.xml.parsers.DocumentBuilder. L'approche de base est la suivante:
* Utilisation de la méthode de construction
DocumentBuilderFactory.newInstance() afin de retourner un objet
DocumentBuilderFactory
* Utilisation de la méthode newDocumentBuilder() de cet objet
DocumentBuilderFactory afin de retourner une instance (spécifique d'un
vendeur) de la classe abstraite DocumentBuilder
* Uitlisation d'une des méthodes parse() de DocumentBuilder afin de lire le
document XML et retourner un objet org.w3c.dom.Document.

La version JAXP du code précédent donne alors ceci:


//DOM
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
public class TestDOMJAXP
{
public static void main( String [] args ) throws Exception
{
try{
//Récupère une instance de la classe de fabrication
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//Récupére une instance de la classe DocumentBuilder
(spécifique vendeur)

Tutoriel DOM et JDOM Page 27


Présenté par Cyril Vidal cyril@planetexml.com

DocumentBuilder parser = factory.newDocumentBuilder();


//effectue le parsing avec récupération du noeud DOM Document
Document document = parser.parse("catalogue.xml");
//code identique au précédent
Element catalogue = document.getDocumentElement();
NodeList titres = catalogue.getElementsByTagName("titre");
System.out.println("Les titres des livres du catalogue sont: ");
for (int i=0; i<titres.getLength(); i++) {
System.out.println(titres.item(i).getFirstChild().getNodeValue());
}
}
catch (FactoryConfigurationError e) {
System.out.println("Impossible de localiser une classe de construction");
}
catch (ParserConfigurationException e) {
System.out.println("Impossible de localiser un parseur JAXP");
}
}
}

En exécuant TestDOMJAXP disponible ici, on vérifie que l'on obtient bien le même
résultat:

Il faut noter que la classe DocumentBuilderFactory possède un certain nombre


d'options de configuration. Parmi les plus importantes, on compte la prise en
charge des espaces de noms par le parseur via la méthode public boolean
isNamespaceAware() qu'on rend effective par le code suivant:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);

La validation d'un document XML par rapport à une DTD peut également être prise en
charge par le parseur produit par une factory via la méthode public boolean
isValidating() mise en oeuvre de la faÇon suivante:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);

Il existe enfin des otions concernant la possibilité ou non d'ignorer les


commentaires situés dans le document d'entrée (public boolean
isIgnoringComments(), la possibilité ou non de résoudre les références
d'entités (public boolean isExpandEntityReference), , etc.

Enfin, pour terminer ce petit tour d'horizon sur l'analyse de documents XML via JAXP, il
convient de revenir sur le point important suivant: comment JAXP choisit-il son
parseur?

En fait, JAXP utilise le parseur que référence la classe indiquée par la propriété
système javax.xml.parsers.DocumentBuilderFactory. Par exemple, si l'on
veut être sûr que que nous utilisons Xerces lors de l'analyse de notre document
catalogue.xml via notre classe TestDOMJAXP, le plus simple est d'exécuter cette

Tutoriel DOM et JDOM Page 28


Présenté par Cyril Vidal cyril@planetexml.com

dernière de la faÇon suivante: java


-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilde
TestDOMJAXP, comme indiqué par l'écran ci-dessous:

Si l'on veut une fois pour toutes que JAXP utilise tel parseur plutôt que tel autre sans
avoir à le spécifier à chaque fois en ligne de commande comme nous venons de le
faire, il suffit de créer dans le répertoire lib de votre installation Java un fichier
nommé jaxp.properties, qui contiendrait les informations suivantes:
javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.DocumentBuilderFactory(xerces
=org.apache.crimson.jaxp.DocumentBuilderBuilderFactory(crimson)

Si auncun renseignement n'est fourni d'une faÇon ou d'une autre, alors ce sera
l'implémentation de JAXP (par exemple Sun ou Apache) qui déterminera
l'analyseur par défaut (respectivement Crimson ou Xerces).

DOM et JAXP 1.1 (sérialisation)


Bien que le P de JAXP corresponde à Parsing (indiquant par là que JAXP ne
s'occuperait que de l'analyse), il vaut la peine de noter que l'une des grandes
nouveautés apportées par JAXP1.1 par rapport à l'API JAXP1.0 est que cette nouvelle
version rend désormais possible des transformations XML neutres en termes de
vendeurs, via l'API TrAX contenue dans le paquetage javax.xml.transform.

Du point de vue qui nous oocupe (ici celui de la sérialisation), il faut avouer que JAXP
ne dipose pas en tant que telle d'une classe de sérilaisation, mais l'astuce consiste à
utiliser une transformation à vide à partir du document XML initial pour arriver
exactement au même résultat. Regardons cela d'un peu plus près: L'API JAXP jouit
d'une grande cohérence et les instructions à fournir pour exécuter une transformation
obéissent exactement à la même dynamique que celles exécutées dans le cas de
l'analyse, à savoir :
1. Utilisation dela méthode de fabrication
TransformerFactory.newInstance() retournant un objet
javax.xml.transform.TransformerFactory
2. Utilisation de la méthode newTransformer() de cet objet
TransformerFactory afin de retourner une instance (spécifique du vendeur)
de la classe abstraite javax.xml.transform.Transformer
3. Réalisation des opérations de transformation

Illustrons cela par un exemple simple: nous parsons une des versions du fichier
catalogue.xml, le modifions par quelques manipulations DOM afin de lui rajouter un
livre dans la liste du catalogue, et finalement le sérialisons vers l'écran ou un fichier de
sortie.

Pour cela, la première chose à faire d'importer les classes de JAXP relatives aux

Tutoriel DOM et JDOM Page 29


Présenté par Cyril Vidal cyril@planetexml.com

transformations (ces classes concernent plus spécifiquement l'API TrAX):


//importation des classes TrAX
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.Source;
import javax.xml.transform.Result;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.TransformerException;

En plus des classes de constructions (à mettre en parallèle avec les classes d'analyse)
et d'exceptions, on doit rajouter ici les classes relatives aux entrées et aux sorties de la
transformation TrAX. Nous fournissons dans notre exemple une arborescence DOM
en entrée de transformation, ce qui explique l'importation de la classe
javax.xml.transform.dom.DOMSource. (Celle-ci constitue, ainsi que les deux
autre classes d'entrée javax.xml.transform.sax.SAXSource et
javax.xml.transform.stream.StreamSource, une implémentation concrète de
l'interface javax.xml.transform.Source). Comme nous envisageons d'effectuer
la sortie sur l'écran système ou dans un fichier, nous utilisons la classe de sortie
javax.xml.transform.stream.StreamResult qui prend comme arguments soit
un OutputStream (comme System.out), soit un Writer. Cette classe de sortie
constitue, ainsi que les classes javax.xml.transform.dom.DOMResult et
javax.xml.transform.sax.SAXResult, une implémentation concrète de
l'interface javax.xml.transform.Result

Ensuite, nous modifions notre document d'origine en rajoutant un élément <livre>,


ainsi qu'un <titre> et un <auteur>. Il n'y a rien de difficile, simplement bien veiller à
créer chaque nouveau Node à partir du document DOM lui-même via la méthode
createElement() et ne placer qu'ensuite le noeud ainsi crée dans l'arborescence
DOM.
Element catalogue = document.getDocumentElement();
//création d'un élément livre
Element livre = document.createElement("livre");
//création d'un élément titre
Element titre = document.createElement("titre");
//création d'un élément auteur
Element auteur = document.createElement("auteur");
//ajout d'un contenu textuel à l'élément titre crée
titre.appendChild(document.createTextNode("Le mythe de Sisyphe"));
//ajout d'un contenu textuel à l'élément auteur crée
auteur.appendChild(document.createTextNode("Albert Camus"));
//ajout de l'élément titre ainsi crée à l'élément livre
livre.appendChild(titre);
//ajout de l'élément auteur ainsi crée à l'élément livre
livre.appendChild(auteur);
//ajout de l'élément livre à l'élément catalogue
catalogue.appendChild(livre);

Nous passons ensuite à la sérialisation proprement dite, dont on a déjà dit qu'elle
consistait en fait via JAXP à une transformation sans feuille de style. Les étapes sont
les suivantes:
1. Obtenir une classe fabriquant des instances de la classe Transformer via la
classe TransformerFactory
2. Récupérer une instance de la classe Transfomer
3. Réaliser les opérations de transformation, en définissant les types de document

Tutoriel DOM et JDOM Page 30


Présenté par Cyril Vidal cyril@planetexml.com

d'entrée et de sortie, et en utilisant ceux-ci avec la classe transform()

//Création d'un objet TransformerFactory fabriquant des instances de la classe


TransformerFactory tfactory = TransformerFactory.newInstance();
//Création d'un objet Transformer
Transformer transformeur = tfactory.newTransformer();
//Définition du document d'entrée comme arborescence DOM
Source entrée = new DOMSource(document);
//Définition du document de sortie vers l'écran
Result sortie = new StreamResult(System.out);
transformeur.transform(entrée, sortie);

L'exécution de la classe JAXPSerialise produit le résultat suivant:

Si l'on veut effectuer la sortie vers un fichier sortie.xml, il suffit de changer la ligne
de définition de sortie: Result sortie = new StreamResult(new
FileOutputStream("sortie.xml");

DOM Niveau 2: les espaces de nom


DOM Niveau 2 apporte comme complément susbantiel par rapport au niveau
précédent la prise en charge des espaces de nom. On a déjà indiqué dans le
panneau Création et Modification d'une arboresence DOM (via servlet) on page 13
l'utilisation de la méthode createDocument() introduite par DOM Niveau 2, et qui
prend comme premier argument l'URI de l'espace de nommaage de l'élément racine et
comme second argument le nom qualifié (qualified name)d'un tel nom: on rappelle
que le nom qualifié représente la concaténation du nom local de l'élément et du
préfixe associé à l'URI de l'espace de nommage. Parallèlement à cette méthode,
DOM niveau 2 met à notre disposition les méthodes de création
createElementNS(URI, nom qualifié) et createAttributeNS(URI, nom
qualifie). Ainsi, si l'on souhaite utiliser l'URI d'espace de nom
http://schema-livre et le préfixe livre avec un élément nommé titre, on
devra invoquer la méthode createElementNS("http://schema-livre",

Tutoriel DOM et JDOM Page 31


Présenté par Cyril Vidal cyril@planetexml.com

"livre:titre"). On peut également utiliser la méthode getPrefix() pour


récupérer le préfixe du nom qualifié (ici, livre) ainsi que la méthode
getNamespaceURI() pour obtenir l'espace de nom correspondant. Si l'élément se
trouve dans un espace de nom par défaut ou qu'il n'est dans aucun espace de
nommage, la valeur retournée est alors null. Pour illustrer cela, reprenons notre
exemple de création/modification de description de livre via la servlet du pannneau
Création et Modification d'une arboresence DOM (via servlet) on page 13 . On veut
créer un document xml avec un espace de nommage par défaut
http://www.catalogue.com. Pour cela, le code à utiliser est le suivant:
...
String docNS = "http://www.catalogue.com";
if (!fichierXML.exists()) {
// Creer une nouvelle arborescence DOM
DOMImplementation domImpl = new DOMImplementationImpl();
doc = domImpl.createDocument(docNS, "livre", null);
Element racine = doc.getDocumentElement();
...

Comme il a été expliqué plus haut, cela aurait normalement pour effet de créer un
élément racine du document avec un espace de nom par défaut référant à l'URI
http://www.catalogue.com. Mais si vous compilez et exécutez le code, vous vous
apercevez que votre document xml n'a subi aucun changement. C'est parce qu'il faut
ajouter à la main l'attribut xmlns à l'arboresence DOM, laquelle API ne prend pas
en charge par elle-même un tel ajout. i faut donc rajouter:
racine.setAttribute("xmlns",docNS)

Si nous voulons rajouter un autre espace de nom préfixé "livre" à certains des
éléments du document, il faudra donc également commencer par déclarer un tel
espace de nom sur l'élément racine via la ligne de code suivante:
String NS ="http://schema-livre";
racine.setAttribute("xmlns:livre", NS);

Ensuite, on place les éléments <titre> et <auteur> dans l'espace de nom préfixé
par "livre" et l'élément <description> dans l'espace de nom défini par défaut de la
manière suivante:
//Titre du livre
Element elementTitre = doc.createElementNS(NS, "livre:titre");
Text texteTitre = doc.createTextNode(titre);
elementTitre.appendChild(texteTitre);
racine.appendChild(elementTitre);
// Auteur du livre
Element elementAuteur = doc.createElementNS(NS,"livre:auteur");
Text texteAuteur = doc.createTextNode(auteur);
elementAuteur.appendChild(texteAuteur);
racine.appendChild(elementAuteur);
// Description du livre
Element elementDescription = doc.createElementNS(docNS,"description");
Text texteDescription = doc.createTextNode(description);
elementDescription.appendChild(texteDescription);
racine.appendChild(elementDescription);

Attention, dans la deuxième partie de la servlet concernant la modification d'une


arborescence DOM, on utilise les méthodes DOM Niveau 2
getElementsByTagNameNS(URI,nom local). Le deuxième argument de cette

Tutoriel DOM et JDOM Page 32


Présenté par Cyril Vidal cyril@planetexml.com

thode n'est pas, contrairement à la méthode createElementNS(URI, nom


qualifié) un nom qualifié (incluant donc un préfixe) mais un nom local ( sans
préfixe). Donc le code ressemblera à cela:
else {
// Charge le document
try {
DOMParser parser = new DOMParser();
parser.parse(fichierXML.toURL().toString());
doc = parser.getDocument();
Element racine = doc.getDocumentElement();
// Titre du livre
NodeList elementsTitre = racine.getElementsByTagNameNS(NS,"titre");
Element elementTitre = (Element)elementsTitre.item(0);
Text texteTitre = (Text)elementTitre.getFirstChild();
texteTitre.setData(titre);
// Auteur du livre
NodeList elementsAuteur = racine.getElementsByTagNameNS(NS,"auteur");
Element elementAuteur = (Element)elementsAuteur.item(0);
Text texteAuteur = (Text)elementAuteur.getFirstChild();
texteAuteur.setData(auteur);
// Description du livre
NodeList elementsDescription =
racine.getElementsByTagNameNS(docNS,"description");
Element elementDescription = (Element)elementsDescription.item(0);
// Supprime et recrée la description
racine.removeChild(elementDescription);
elementDescription = doc.createElementNS(docNS,"description");
Text texteDescription = doc.createTextNode(description);
elementDescription.appendChild(texteDescription);
racine.appendChild(elementDescription);
....

En compilant et en exécutant cette servlet MiseAJourServletNS.java et en pointant sur


l'adresse http://localhost:8080/updateNS (nécessitant la modification du
fichier web.xml), et en remplissant le formulaire de la faÇon suivante:

Tutoriel DOM et JDOM Page 33


Présenté par Cyril Vidal cyril@planetexml.com

on obtient alors le fichier livre-01.xml suivant, situé dans le dossier C:\DOM\tuto:

Module Traversal de DOM Niveau 2: NodeIterator


La spécification DOM Niveau 2 regroupe six modules destinés à étendre les
fonctionnalités de DOM Niveau 1. Parmi ces derniers, en plus de ce qu'offre le noyau
de DOM Niveau 2, on retrouve le module Traversée, facilitant, comme son nom
l'indique, la "traversée" ou la navigation au sein d'un arbre DOM. Cette extension DOM
s'avère particulièrement utile lorque la structure du document DOM à analyser nous
reste à peu près inconnue, et qu'il nous faut récupérer tout de même des noeuds ou
modifier le contenu d'un tel document. Les classes qui constituent le module DOM
Traversal se trouvent toutes dans le package org.w3c.dom.traversal , et sont au
nombre de quatre. La classe de base à partir de laquelle tout est construit se nomme
DocumentTraversal(un peu de la même manière que dans le noyau DOM, tout
commence avec une inteface Document), les trois autres sont NodeIterator,
TreeWalker et NodeFilter laquelle classe est utilisée pour personnaliser la nature
des noeuds retournés lors du parcours de l'arbre DOM. Voyons comment cela marche:

Soit le document catalogueTraversal.xml, pour lequel, à chaque livre, on ajoute un


élément <description>, lequel contient lui-même quelques mots-clés, signalisés
par une balise de type <mot-clé>: le but est de récupérer le plus facilement et
directement possible de tels mot-clés. La première chose à faire est de créer une
instance de DocumentTraversal:en général, la classe qui implémente l'interface
org.w3c.dom.Document est celle qui implémente également DocumentTraversal
//Accéder à l'implémentation org.w3c.dom.Document du parseur (ici xerces)
Document document = new org.apache.xerces.dom.DocumentImpl();
//Récupérer une instance de DocumentTraversal par cast de type
DocumentTraversal traversal = (DocumentTraversal)document

Ensuite, il faut créer une implémentation de l'interface


org.w3c.dom.traversal.NodeFilter en implémentant son unique méthode
public short acceptNode(Node n). Cette méthode accepte un Node comme
seul argument: elle traite ce noeud et retourne un constante Java short, indiquant si
le noeud rencontré doit être retourné au NodeIterator courant ou non.
L'implémentation suivante de NodeFilter accepte uniquement les noeuds à
l'intérieur des éléments <mot-clé>:
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeFilter;
class NodeFilterPerso implements NodeFilter {
public short accept(Node n) {

Tutoriel DOM et JDOM Page 34


Présenté par Cyril Vidal cyril@planetexml.com

if (n.getNodeType() == Node.TEXT_NODE) {
Node parent = n.getParentNode();
if (parent.getNodeName().equalsIgnoreCase("mot-clé")) {
return FILTER_ACCEPT;
}
}
//Si nous arrivons là, nous ne sommes pas intéréssés
return FILTER_SKIP;
}
}

Fichier source de NodeFilterPerso.java. Ici, nous utilisons un code DOM habituel: nous
ne nous intéressons qu'aux noeuds textuels, et souhaitons récupérer le contenu textuel
des éléments <mot-clé>, non aux éléments eux-mêmes. Pour ce faire, nous utilisons
la méthode getNodeName() appliquée au parent du noeud textuel rencontré (il est
raisonnable de présupposer que le parent d'un noeud textuel est un élément). Si le
nom de l'élément parent est mot-clé, alors le code renvoie la valeur
FILTER_ACCEPT. Sinon, il renvoie la valeur FILTER_SKIP qui évite le noeud examiné
mais continue à itérer sur ses fils. (Il existe une troisième valeur de retour,
FILTER_REJECT, qui rejette le noeud examiné ainsi que tous ses fils, et qui n'est
applicable qu'à TreeWalker que nous verrons plus loin).

Une fois ce filtre crée, il reste à créer un NodeIterator utilisant ce filtre. Il y a


plusieurs informations à fournir dans le cadre d'une telle création: d'abord l'élément sur
lequel démarrer l'itération: ici, nous choisissons par défaut l'élément racine du
document xml. Ensuite, il faut indiquer la nature des noeuds que l'itérateur doit
afficher:: il peut s'agir soit de tous noeuds (NodeFilter.SHOW_ALL), soit uniquement
les éléments (NodeFilter.SHOW_ELEMENT), soit encore les valeurs textuelles
(NodeFilter.SHOW_TEXT). Enfin, on spécifie l'implémentation du NodeFilter qui
nous convient, et l'on rend possible l'expansion ds références d'entité, via la valeur
booléeene true.
// Parse into a DOM tree
DOMParser parser = new DOMParser();
parser.parse(nomFichier);
Document document = parser.getDocument();
// Indique le noeud à partir duquel commencer l'itération
Element racine = document.getDocumentElement();
// Crée un NodeItertor
NodeIterator i = ((DocumentTraversal)document)
.createNodeIterator(racine, NodeFilter.SHOW_ALL,
new NodeFilterPerso(), true);
Node n;
while ((n = i.nextNode()) != null) {
System.out.println("mot-clé trouvé: '" + n.getNodeValue() + "'");

La caractéristique vraiment intéressante à remarquer à propos de l'utilisation de


NodeIteraor est que l'on récupère tous les noeuds formant la descendance de
l'élément racine, même sur plusieurs niveaux de profondeur...Ce qui s'avère
extrêmement utile lorque l'on ne connaît pas l'arborescence XML! Lorque l'on compile
puis exécute le code de RechercheMot.java, via la ligne de commande java
RechercheMot catalogueTranversal.xml, on obtient alors l'écran suivant:

Tutoriel DOM et JDOM Page 35


Présenté par Cyril Vidal cyril@planetexml.com

N.B: Il vaut la peine de remarquer qu'il peut y avoir des "interférences" entre l'utilisation
de la constante fournie à la méthode createNodeIterator() et l'implémentation du
NodeFilter. Dans l'exemple ci-dessus, on a utilisé une constante
NodeFilter.SHOW_ALL alors que l'on retournait des éléments textuels, ce qui est
concordant. Par contre, si l'on tente de changer la constante en
NodeFilter.SHOW.ELEMENT à la méthode createNodeIterator(), on ne reÇoit
aucun mot-clé en réponse: l'explication vient du fait que l'implémentation du
NodeIterator() reÇoit des noeuds de type contenu textuel et qu'il n'est censé
afficher que des éléments. Une faÇon correcte de faire serait ici de passer la constante
NodeFilter.SHOW_TEXT

Module Traversal de DOM Niveau 2: TreeWalker


L'interface TreeWalker est essentiellement semblable à l'interface NodeIterator
précédemment vue: la seule différence consiste en ce que l'on récupère ici une vue
arborescente plutôt qu'une vue sous forme de liste. Il s'git là encore de traiter une
arborescence DOM standard en la filtrant pour n'obtenir qu'une certaine vue de cette
arborescence, dépouillée de tel ou tel type d'élément, ou bien dénué des
commentaires, etc. La faÇon de créer une instance de TreeWalker est équivalente à
celle précédemment vue:on utilise simplement la méthode createTreeWalker en
lieu et place de la méthode createNodeIterator()
// Parse un fichier en une arborescence DOM
File file = new File(nomFichier);
DOMParser parser = new DOMParser();
parser.parse(nomFichier);
Document document = parser.getDocument();
// Indique le noeud à partir duquel commencer l'itération
Element racine = document.getDocumentElement();
// Crée un TreeWalker
TreeWalker i = ((DocumentTraversal)document)
.createTreeWalker(racine, NodeFilter.SHOW_ALL,
null, true);

Ici, nous n'avons spécifié aucune implémentation personnalisée de NodeFilter. On


peut simplement naviguer à travers l'arbre DOM en renvoyant à chaque noeud
rencontré son nom ainsi que sa valeur. On admire la facilité de la mise en place d'une
telle navigation comparée à un code DOM classique pour lequel il faudrait opérer des
récursions successives à chaque niveau de profondeur.
Node n;
while ((n = i.nextNode()) != null) {
System.out.println( n.getNodeName() + ":" + n.getNodeValue());
}

Tutoriel DOM et JDOM Page 36


Présenté par Cyril Vidal cyril@planetexml.com

Lorque l'on exécute RechercheTreeWalker.java via la ligne de commande java


RechercheTreeWalker catalogueTraversal.xml, on obtient alors l'écran
suivant:

On vérifie ainsi qu'un élément ne possède jamais en tant que tel de contenu textue, ce
qui nous est indiqué par la valeur null retournée par la méthode getNodeValue().

Module Range de DOM Niveau 2


Le module Range (Portée) de DOM Niveau 2, dont la spécification se trouve à
l'adresse suivante http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html ,
est très pratique pour sélectionner le contenu délimité par un point initial et un
point final d'un document DOM dont on ignore par ailleurs à peu près tout de la
structure. Une fois qu'il est sélectionné, on peut alors tout à faire avec ce fragment de
contenu: y insérer des données, le copier ou en supprimer des parties, etc.
org.w3c.dom.ranges constitue le paquetage afférent à ce module, et inclut
l'interface principale org.w3c.ranges.DocumentRange, l'équivalent de l'interface
org.w3c.dom.traversal.DocumentTraversal pour le paquetage
org.w3c.traversal. Si l'on reprend notre exemple de servlet du panneau Création
et Modification d'une arboresence DOM (via servlet) on page 13 . Nous avions alors
décidé de supprimer complètement l'élément <description> via la méthode
removeChild() et de le remplacer par un nouveau élément de même nom contenant
le texte passé en paramètre. Le module de portée de DOM Niveau 2 permet de faire
une opération équivalente de faÇon tout à fait simple. On commmence par les
traditionnelles instructions d'importation des classes DocumentRange et Range
situées dans le paquetage org.w3c.dom.ranges.
// Module Range
import org.w3c.dom.ranges.DocumentRange;
import org.w3c.dom.ranges.Range;

Ensuite, il faut créer notre portée, exactement de la même faÇon que nous avions
défini un NodeIterator ou un TreeWalker, , c'est-à-dire par transtypage et

Tutoriel DOM et JDOM Page 37


Présenté par Cyril Vidal cyril@planetexml.com

utilisation d'une méthode de création, ici createRange().


//Création d'une portée
Range portee = ((DocumentRange)doc).createRange();

Une fois la portée créée, il faut à présent définir les points d'arrivée et de départ.
Comme on veut supprimer tout le contenu de l'élément <description>, on choisit
comme départ ce qui précède le premier fils via la méthode setStartBefore(), et
comme point d'arrivée ce qui succède au dernier fils, via setEndAfter().
//Défintion du point de départ
portee.setStartBefore(elementDescription.getFirstChild());
//Définition du point d'arrivée
portee.setEndAfter(elementDescription.getLastChild());

Une fois ce fragment encadré, il ne reste plus qu'à invoquer la méthode


deleteContents pour le supprimer.
//Suppression du contenu
portee.deleteContents();

Dès lors, il ne reste plus qu'à créer le nouveau contenu textuel et à l'ajouter à
l'arborescence via la méthode classique appendChild()
Text texteDescription = doc.createTextNode(description);
elementDescription.appendChild(texteDescription);

Enfin, on peut libérer les ressources associées à la portée en appelant la méthode


detach(), comme suit:
portee.detach();

ON voit l'avantage d'utiliser un module Range dans ce cas-là: si on avait en effet voulu
garder en DOM Niveau 1 l'élément description et simplement changé son contenu
textuel comme on vient de le faire, il aurait fallu crée une NodeList comprenant tous
les noeuds fils de l'élément, puis dans un second temps, il aurait fallu itérer sur chacun
d'eux pour les supprimer un à un. A l'évidence, le module Portée est beaucoup plus
pratique à utiliser. Voir le code source de RangeMiseAJourServletNS.java

Tutoriel DOM et JDOM Page 38


Présenté par Cyril Vidal cyril@planetexml.com

Section 3. L'API JDOM

Introduction
JDOM présente de grandes similitudes avec le DOM en ce sens qu'il représente un
document XML via une structure arborescente. Cependant, il s'en distingue parce
que JDOM est spécifiquement conÇu pour JAVA et que du point de vue du
développeur JAVA, il s'avère beaucoup plus pratique à utiliser (A ce propos, il
convient de noter que contrairement à ce qui est parfois écrit, le J de JDOM ne renvoie
pas à Java, et que JDOM suit la nomenclature NAA - not an abreviation - de Sun:
JDOM veut ainsi dire JDOM et rien d'autre). Nous utilisons ici JDOM1.0 beta 8
disponible à l'adresse http://www.jdom.org. La configuration est très simple: les
opérations à effectuer sont spécifiées dans le fichier README.txt de la distribution et
sont principalement au nombre de deux:
1. D'abord vérifier que la variable d'environnement JAVA_HOME est définie
correctement en spécifiant bien le dossier du JDK comprenant la JVM devant être
utilisée.
2. Ensuite, veiller à être dans le dossier dans lequel est situé le fichier build.xml
puis taper ./build.sh (unix) ou .\build.bat (windows). Cela a pour
effet de créer via l'outil Ant l'archive JDOM.jar contenant toutes les classes de
l'API. Ensuite, il faut inclure cette archive JDOM.jar, ainsi que xerces.jar,
jaxp.jar et xalan.jar fournis dans la distribution dans votre classpath.

Création d'un document XML


Nous allons commencer par un exemple très simple, permettant de voir la faÇon dont
fonctionne l'API JDOM. Pour commencer, nous allons créer le document
catalogue.xml de la première section de notre tutoriel.
import org.jdom.*;
public class tutoJDOM1
{
public static void main(String[] args)
{
Element racine = new Element("catalogue");
Document document = new Document(racine);
System.out.println(document);
}
}

On commence par importer le package jdom. Ensuite, nous instantions un objet de


type Element (classe org.jdom.Element) à l'aide du constructeur Element(). La
classe Element possède plusieurs constructeurs. Nous utilisons ici le plus simple, qui
prend un String comme paramètre, lequel devient le nom de l'élément (nous verrons
plus tard le constructeur qui prend en charge les espaces de nom). Après avoir crée un
Element, un objet Document est instantié, avec l'élément crée passé comme
argument (sachant qu'un document XML ne peut exister sans élément racine, une
instance de la classe Element est requise par le constructeur de la classe
Document). Finalement, le code sort le Document sur l'écran. En compilant et en
exécutant ce code (tutoJDOM1.java), on obtient le résultat suivant:

Tutoriel DOM et JDOM Page 39


Présenté par Cyril Vidal cyril@planetexml.com

Le résultat ne ressemble pas vraiment à du XML. Utilisé avec la méthode


System.out.println, on sait que le message toString est envoyé à l'objet
concerné. Ici, la documentation de JDOM stipule qu'une telle méthode ne doit être
utilisée que pour le debugging:"toString: This returns a String representation of the
Document, suitable for debugging." On verra dans le prochain panneau Sérialisation
JDOM on page 41qu'une sérialisation digne de ce nom doit utiliser l'objet
XMLOutputter.

En attendant, il nous faut créer la suite de notre document catalogue.xml. Cela se


fait de la manière suivante:
Element livre1 = new Element("livre");
Element titre1 = new Element("titre");
titre1.setText("La Généalogie de la morale");
livre1.addContent(titre1);
racine.addContent(livre1);

On commence par créer un élément <livre>, puis un élément <titre>, auquel on


ajoute un contenu textuel via la méthode setText(), puis on attache chacun des
deux éléments ainsi crées à leur noeud parent respectif via la méthode
addContent(): on remarque la simplicité de la mise en oeuvre par rapport au DOM,
car la création d'un noeud (élément ou texte) ne nécessite pas le recours à l'objet
Document. Il reste que la création de chacun des sous-éléments d'un élément
<livre> peut rester quand même un peu pénible, et le mieux est peut-être de créer
une méthode AjoutElement() pour systématiser l'ajout d'éléments et de leur
contenu textuel à chaque élément <livre>
public void ajoutElement(Element titre, String element, String texte) {
Element elementAjoute = new Element(element);
elementAjoute.setText(texte);
titre.addContent(elementAjoute);
}

On a plus alors qu'à instancier un objet de la classe et de lui appliquer la méthode


autant de fois que nécessaire:
tutoJDOM2 tuto = new tutoJDOM2();
Element racine = new Element("catalogue");
Document document = new Document(racine);
Element livre1 = new Element("livre");
Element titre1 = new Element("titre");
titre1.setText("La Généalogie de la morale");
livre1.addContent(titre1);
racine.addContent(livre1);
tuto.ajoutElement(livre1, "auteur", "Friedrich Nietzsche");
tuto.ajoutElement (livre1, "édition", "Folio essais");
tuto.ajoutElement (livre1, "ISBN", "2-07-032327-7");
Element livre2 = new Element("livre");
racine.addContent(livre2);
tuto.ajoutElement(livre2, "titre", "Réflexions sur la poésie");

Tutoriel DOM et JDOM Page 40


Présenté par Cyril Vidal cyril@planetexml.com

tuto.ajoutElement(livre2, "auteur", "Paul Claudel");


tuto.ajoutElement (livre2, "édition", "Folio essais");
tuto.ajoutElement (livre2, "ISBN", "2-07-032327-7");

Sérialisation JDOM
Une fois notre strcture créée, il serait bien de pouvoir la visualiser sur l'écran ou de
l'envoyer en sortie vers un fichier. L'API JDOM utilise la classe XMLOutputter pour
envoyer le code XML vers un flux encapsulant une connection réseau, un fichier ou
toute autre structure dans laquelle on souhaite placer du code XML. La classe
XMLOutputter s'utilise de la manière suivante:
XMLOutputter sortie = new XMLOutputter(" ", true);
sortie.output(document, System.out);

Sans argument, la classe XMLOutputter réalise une sortie directe, généralement


sans indentation ni saut de ligne. Le document résultat se trouve donc sur une seule
ligne, à l'exception de la déclaration XML. Il exsite plusieurs constructeurs plus
sophistiqués permettant de gérer cela, parmi lesquelles public XMLOutputter
(String indent, boolean newlines); et public XMLOutputter (String
indent, boolean newlines, String encoding). Le paramètre indent
permet de spécifier le nombre d'espaces à utiliser pour l'indentation, la valeur
booléenne newlines détermine s'il y a lieu d'utiliser les sauts de ligne et enfin, si
nécessaire, on peut encore vouloir spécifier un paramètre relatif à l'encodage, qui
deviendra alors la valeur de l'attribut encoding dans la déclaration XML de début de
documet. La méthode output prend comme arguments soit une instance de
OutputStream, soit une instance de Writer (ic, nous effectuons une sortie vers
l'écran via System.out). Enfin, il convient de noter que la classe XMLOutputter doit
être utilisée accompagnée d'un bloc de capture d'exception E/S pour être
vraiment effective. On obtient donc:
try {
XMLOutputter sortie = new XMLOutputter(" ", true);
sortie.output(document, System.out);
}
catch (java.io.IOException e){
e.printStackTrace();
}

En compilant puis exécutant l'ensemble du code tutoJDOM2.java, on obtient la fenêtre


DOS suivante:

Tutoriel DOM et JDOM Page 41


Présenté par Cyril Vidal cyril@planetexml.com

Si l'on veut envoyer les données XML vers un fichier, il faudrait utiliser la classe
FileOutputStream, par exemple de la faÇon suivante:
XMLOutputter outputter = new XMLOutputter(" ", true);
FileOutputStream sortie = new FileOutputStream("sortie.xml");
outputter.output(document,sortie);
sortie.flush();
sortie.close();

JDOM et les attributs


Après avoir vu comment créer et insérer des éléments et des contenus textuels,
voyons à présent comment ajouter des attributs à notre XML. On utilise pour cela le
constructeur new Atribute avec comme paramètres le nom de l'attribut et sa valeur.
Dans un deuxième temps, on affecte l'attribut à l'élément correspondant à l'aide de la
méthode setAttribute() prenant comme paramètre l'attribut que l'on désire
attacher. Dans notre cas, il faudra donc écrire le code suivant pour ajouter les attributs
langue="fr" aux deux éléments <livre>:
Attribute langue1 = new Attribute("langue", "fr");
Attribute langue2 = new Attribute("langue","fr");
titre1.setAttribute(langue1);
Element titre2 = livre2.getChild("titre");
titre2.setAttribute(langue2);

On commence par créer l'attribut de nom langue en lui affectant la valeur fr, puis on
l'affecte à l'élément titre1 déjà défini plus haut dans le code. Ensuite, on crée un
nouvel attribut que l'on affecte au titre du deuxième livre: celui-ci n'avait pas été
explicitement défini dans le code précédent, il faut donc d'abord le récupérer avec la
méthode getChild(). En compilant et exécutant le code ainsi obtenu, on obtient le
résultat suivant:

Tutoriel DOM et JDOM Page 42


Présenté par Cyril Vidal cyril@planetexml.com

Le code source de tutoJDOM3.java est disponible ici.

JDOM et les espaces de nom


JDOM prend en charge les espaces de nom à l'aide de l'utilisation de l'un des trois
constructeurs prévus à cet effet. Les deux premiers n'utilisant qu'un String passé en
paramètre, comme ceci: Element livre1 = new Element ("livre",
"www.catalogue-schema.com"); Element livre2 = new Element
("livre", "ctg", "www.catalogue-schema.com"); Le premier élément est
crée en passant en paramètre le nom de l'élément, ainsi que l'URI de l'espace de nom
auquel il appartient, tandis que le second incluera le préfixe de l'espace de nom, en
plus du nom de l'élément et de l'URI auquel l'espace de nom renvoie. Le résultat que
l'on obtient en compilant et exécutant tutoJDOM4.java est le suivant:

La troisième faÇon de créer un élément avec un espace de nom consiste à utiliser la


classe Namespace. On crée d'abord un objet Namespace à l'aide de la méthode
getNamespace(), puis on utilise un tel objet pour créer le nouvel élément. Il existe
tout comme précédemment une version de Namespace pour créer des espaces de
noms sans préfixe (les espaces de nom par défaut), et une version pour créer des
espaces de nom avec préfixes. Le code à utiliser pour produire le même résultat que
précédemment serait alors le suivant:
//Crée un espace de nom sans préfixe

Tutoriel DOM et JDOM Page 43


Présenté par Cyril Vidal cyril@planetexml.com

Namespace espaceNom = Namespace.getNamespace("www.catalogue-schema.com");


Element livre1 = new Element ("livre", espaceNom);
//Crée un espace de nom avec préfixe
Namespace espaceNomctg = Namespace.getNamespace("ctg", "www.catalogue-schema.com");
Element livre2 = new Element ("livre", espaceNomctg);

La méthode getChildren(Element element, Namespace espaceNom) est


également utilisée pour la recherche d'éléments associés à tel ou tel espace de
nom. Par exemple, pour récupérer l'ensemble des éléments <livre> associés à
l'espace de nom www.catalogue-schema.com, il suffit d'invoquer le code suivant:
List selection = racine.getChildren("livre", espaceNom);
System.out.println("nombre d'éléments: " + selection.size());
for (Iterator i= selection.iterator(); i.hasNext();) {
Element courant= (Element)i.next();
String nom = courant.getName();
System.out.println("Element: " + nom + " titre: " + courant.getChild("titre").getT

La méthode getChildren() renvoie une List Java (et non une NodeList
spécifique DOM), sur laquelle il est possible de naviguer via un Iterator Java
classique. Ici, deux choses sont à noter:
1. Premièrement, bien que l'on fasse la recherche uniquement sur l'espace de nom
espaceNom (espace de nom par défaut), le résultat de la requête renvoie
également l'élément associé au préfixe ctg. En effet, avec JDOM, la
comparaison entre les espaces de nom se base uniquement sur les URI.
Autrement dit, deux objets Namespace sont considérés comme égaux si leur URI
le sont aussi, indépendamment des préfixes. Cette approche est tout à fait
conforme à la spécification des espaces de noms XML, laquelle indique que deux
éléments se trouvent dans le même espace de nom si leur URI est identique, et
ce indépendamment du nom du préfixe auquel cet URI renvoie
2. Deuxièmement, on récupère le contenu textuel d'un élément directement à partir
de l'élément (et non en redescendant d'un niveau comme en DOM) via la
méthode getTextTrim(), qui est équivalente à la méthode getText(), sauf
qu'elle retourne le contenu textuel d'un élément sans les espaces blancs qui
l'entourent ni les espaces superflus entre les mots qu'elle comprimera en espace
blanc unique.

Lorqu'on exécute le code tutoJDOM5.java, on obtient le résultat suivant:

Tutoriel DOM et JDOM Page 44


Présenté par Cyril Vidal cyril@planetexml.com

JDOM et DTD
Il se peut que vous vouliez ajouter une DTD à votre document. Cela est très facile via
JDOM. Par exemple, si on voulait ajouter une DTD catalogue.dtd à notre fichier
catalogue.xml, il nous faudrait juste écrire le code suivant:
DocType type = new DocType("catalogue", "catalogue.dtd");
Document document = new Document (racine, type);

En exécutant le code de tutoJDOM6.java, on obtient le résultat suivant:

On remarque immédiatement quelque chose d'assez surprenant: bien que nous


n'ayons jamais crée de fichier catalogue.dtd, JDOM n'a émis aucun message
d'erreur. La raison en est que JDOM ne valide pas lui-même le XML lorsqu'il est
construit. Afin de valider un document, on a besoin d'utiliser un parseur SAX ou DOM
sous-jacent. Regardons à présent comment cela marche à l'aide du fragment de code
suivant: (On présuppose que l'on a modifié le code de sortie du panneau précédent en:
try {
XMLOutputter sortie = new XMLOutputter(" ", true, "iso-8859-1");
sortie.output(document, new FileOutputStream("catalogueDTD.xml"));
}
catch (java.io.IOException e){
e.printStackTrace();
}

afin de générer le fichier catalogueDTD.xml).


import org.jdom.input.*;
//Instanciation d'une classe de fabrication SAX
SAXBuilder builder = new SAXBuilder(true);
//Chargement de la classe et analyse du document catalogueDTD.xml
try{
Document document = builder.build(new FileInputStream(catalogueDTD.xml));
}
catch {JDOMException e){
System.out.println("Erreur de chargement XML" + e.getMessage());
}

En compilant et en exécutant le fichier tutoJDOM7.java, voici l'écran qu'on obtient:

Tutoriel DOM et JDOM Page 45


Présenté par Cyril Vidal cyril@planetexml.com

Ici, il s'avère que notre document XML à valider est un fichier enregistré sur le disque
et que la meilleure faÇon de construire une représentation JDOM dans ce cas se fait à
partir d'un ensemble d'événements SAX. Une solution alternative peut consister à
utiliser l'autre classe de construction DOMBuilder mise à notre disposition pour
construire l'arborescence JDOM, mais ici, c'est une très mauvaise idée pour la bonne
et simple raison que DOMBuilder utilise SAX pour construire une arobrescence DOM,
avant que cette arborescence DOM ne soit convertie en JDOM. Quand on le peut, en
fait toutes les fois où le document à tranformer en JDOM ne consiste pas en une
arborescence DOM, le mieux est de se servir de la classe SAXBuilder.

Changer d'API: de DOM à JDOM


Même s'il est indéniable que la manipulation JDOM est plus aisée pour un développeur
JAVA, il peut arriver, il arrive même fréquemment qu'un tel développeur ait à sa
ressource uniquement des arborescences DOM en entrée, auquel cas il lui faut un
moyen de convertir ce DOM en JDOM. En fait, cela se fait très facilement en passant
un objet DOMDocument au builder JDOM, qui renvoie lui-même un Document
JDOM. Regardons cela de plus près à travers un petit exemple: on récupère dans un
premier temps l'arborescence DOM de notre fichier catalogue.xml, puis on le
convertit en document JDOM par la suite. Le code est le suivant:
//Création de l'arborscence DOM à partir du fichier catalogue.xml pris par
défaut
org.w3c.dom.Document documentDOM = null;
String fichierXML = "catalogue.xml";
if (args.length == 1) {
fichierXML = args[0];
}
DOMParser parseur = new DOMParser();
try {
parseur.parse(new InputSource(fichierXML));
documentDOM = parseur.getDocument();
}
catch(Exception e) {
e.printStackTrace();
}

Il n'y a rien de spécial à dire ici: on crée simplement une instance de DOMParseur
comme on l'a vu dans la première section, puis on lui envoie le message parse avec
pour argument un objet InputSource de SAX (de manière générale, il est conseillé
d'utiliser cette classe à la place d'une simple URI, car elle permet de fournir plus
d'informations à l'analyseur: elle permet notamment de résoudre les chemins d'accès
relatifs au sein d'un document). La conversion de DOM en JDOM s'effectue quant à
elle de la manière suivante:
//Conversion du document DOM en document JDOM

Tutoriel DOM et JDOM Page 46


Présenté par Cyril Vidal cyril@planetexml.com

try {
DOMBuilder builder = new DOMBuilder();
org.jdom.Document documentJDOM = builder.build(documentDOM);
XMLOutputter outputter = new XMLOutputter();
outputter.output(documentJDOM, System.out);
}
catch (java.io.IOException e) {
e.printStackTrace();
}

Fichier source de tutoJDOM8.java.

Changer d'API: de JDOM à DOM


L'opération inverse, c'est-à-dire le passage d'une structure JDOM vers une
arborescence DOM reste essentiellement identique, mais on utilise alors la classe
org.jdom.output.DOMOutputter, qui s'utilise en prenant comme paramètre une
strcuture JDOM et en renvoyant en sortie une structure DOM, comme ceci:
DOMOutputter outputter = new DOMOutputter();
org.w3c.dom.Document documentDOM = outputter.output(documentJDOM);

Afin de transformer l'arborescence JDOM de notre fichier catalogue.xml, on


pourrait par exemple utiliser le code suivant: On commence par importer les divers
classes et paquetages requis:
import org.jdom.output.XMLOutputter;
import org.jdom.output.DOMOutputter;
import org.jdom.input.SAXBuilder;
import org.jdom.JDOMException;
import java.io.*;

Ensuite, après avoir déclaré deux variables initialisant deux objets


org.jdom.Document et org.w3c.dom.Document, nous construisons
l'arborescence documentJDOM via la méthode lectureFichier, en utilisant la
classe SAXBuilder déjà vue.
org.jdom.Document documentJDOM = null;
org.w3c.dom.Document documentDOM = null;
documentJDOM = lectureFichier(fichierXML);
private static org.jdom.Document lectureFichier(String nom)
throws JDOMException
{
SAXBuilder sxb = new SAXBuilder();
return sxb.build(new File(nom));
}

Enfin, on tranforme l'arborescence JDOM en arborescence DOM via un objet


DOMOutputter auquel on envoie un message output
DOMOutputter domOutputter = new DOMOutputter();
documentDOM = domOutputter.output(documentJDOM);

Code source de tutoJDOM9.java

Tutoriel DOM et JDOM Page 47


Présenté par Cyril Vidal cyril@planetexml.com

JDOM et les classes de fabrication


JDOM permet de personnaliser ses classes de fabrication, afin de rendre la production
du code XML en Java plus flexible. Par exemple, on peut vouloir créer une sous-classe
de la classe par défaut org.jdom.Element afin d'associer à chaque élément crée un
espace de nom bien défini une fois pour toutes. Le code pour cela serait le suivant
(disponible ici).
import org.jdom.Element;
import org.jdom.Namespace;
public class Elementctg extends Element {
private static final Namespace Espace_ctg = Namespace.getNamespace("ctg", "www.cata
public Elementctg(String nom) {
super(nom, Espace_ctg);
}
}

On commence par définir un espace de nom via la méthode getNamespace() vue au


panneau JDOM et les espaces de nom on page 43 . Puis on appelle le constructeur de
la classe de base Element, prenant pour paramètre le nom de l'élément à définir.

Une fois en possession de cette sous-classe, il faut maintenant l'utiliser. Cela se fait
simplement en sous-classant l'interface org.jdom.input.DefaultJDOMFactory
qui retourne par défaut toutes les classes essentielles de JDOM. Cela se fait grâce par
exemple au code suivant, disponible ici:
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.DefaultJDOMFactory;
class JDOMFactoryPerso extends DefaultJDOMFactory {
public Element element(String nom) {
return new Elementctg(nom);
}
}

L'interface sous-classée JDOMFactoryPerso redéfinit la méthode element de


l'interface de base en renvoyant un objet Elementctg précédemment défini.

Enfin, troisième étape,lorque l'on a défini une implémentation correcte de l'interface


DefaultJDOMFactory, il reste à faire appel à la méthode setFactory() afin
d'indiquer aux deux classes de construction JDOM: SAXBuilder et DOMBuilder,
qu'elles doivent l'utiliser. Cela se fait de la faÇon suivante:
SAXBuilder builder = new SAXBuilder();
JDOMFactory factory = new JDOMFactoryPerso();
builder.setFactory(factory);
Document document = builder.build(documentXML);

On applique à la classe de construction SAXBuilder notre classe de fabrication


personnalisée JDOMFactoryPerso via la méthode setFactory(). Les reste est
identique à nos codes de construction de strctures JDOM précédents. Utilisation
successive des méthodes build() et output().

En exécutant le code de tutoJDOM10.java à l'aide de la ligne de commande suivante


java tutoJDOM10 catalogue.xml out.xml, on obtient l'écran suivant:

Tutoriel DOM et JDOM Page 48


Présenté par Cyril Vidal cyril@planetexml.com

JDOM et XSLT
On a vu dans DOM et JAXP 1.1 (sérialisation) on page 29 qu'une bonne faÇon
d'effectuer des transformations XSLT avec des arborescences DOM en entrée ou en
sortie consistait à utiliser l'API JAXP. Eh bien, il en de même avec JDOM. Les classes
JDOM à utiliser en entrée et sortie sont respectivement
org.jdom.transform.JDOMSource(org.jdom.Document
documentJDOMEntree) et org.jdom.transform.JDOMResult(). Soit le code
suivant créant tout d'abord une arborescence JDOM à partir du fichier
catalogue.xml, opérant une transformation XSLT via JAXP à partir d'une telle
arborescence, laquelle produit une autre autre structure JDOM en sortie.

On commence par impoter les classes nécessaires:


//IO
import java.io.*;
//TrAX
import javax.xml.transform.*;
import javax.xml.transform.stream.StreamSource;
//JDOM
import org.jdom.transform.JDOMResult;
import org.jdom.transform.JDOMSource;
import org.jdom.output.XMLOutputter;
import org.jdom.input.SAXBuilder;
import org.jdom.JDOMException;

Puis on définit les quelques variables (document JDOM d'entrée, documentJDOM de


sortie, et String fichierXML valant 'catalogue.xml' par défaut et le premier argument
passé en ligne de commande sinon), dont on aura besoin dans la suite:
org.jdom.Document documentJDOMEntree = null;

Tutoriel DOM et JDOM Page 49


Présenté par Cyril Vidal cyril@planetexml.com

org.jdom.transform.JDOMResult documentJDOMSortie = null;


String fichierXML = "catalogue.xml";
if (args.length == 1) {
fichierXML = args[0];
}

On utilise ensuite la méthode lectureFichier déjà vue, afin de créer une


arborescence JDOM, appelée documentJDOMEntree
documentJDOMEntree = lectureFichier(fichierXML);

On crée ensuite une instance de Transformer en mentionnant la feuille de styles à


utiliser par le biais d'un StreamSource approprié, puis on opère la transformation
XSLT proprement dite avec les deux arguments que sont
org.jdom.transform.JDOMSource(org.jdom.Document document)
(sous-classe de javax.xml.transform.sax.SAXSource) et
org.jdom.transform.JDOMResult (sous-classe de
javax.xml.transform.sax.SAXResult). Pour récupérer le document JDOM issu
de cette transformation, il faut utiliser la méthode getDocument() appliquée à l'objet
JDOMResult issu de la transformation TrAX.
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource("catalogue.xsl"
transformer.transform(new org.jdom.transform.JDOMSource(documentJDOMEntree), docu
org.jdom.Document resultat = documentJDOMSortie.getDocument();

Enfin, on effecue la sérialisation de notre structure JDOM, via XMLOutputter:


XMLOutputter outputter = new XMLOutputter(" ",true);
outputter.output(resultat, new FileOutputStream("resultat.xml"));

En utilsant la feuille de style catalogue.xsl suivante:


<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="catalogue"/>
<xsl:template match="catalogue">
<html>
<h1>CATALOGUE</h1>
<br/>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="livre">
<h2>LIVRE N° <xsl:value-of select="position()"/></h2>
<table border="1" cellpadding="3">
<tr><td><b>TITRE</b></td><td><xsl:value-of select="titre"/></td></tr>
<tr><td><b>AUTEUR</b></td><td><xsl:value-of
select="auteur"/></td></tr>
<tr><td><b>EDITION</b></td><td><xsl:value-of
select="édition"/></td></tr>
<tr><td><b>ISBN</b></td><td><xsl:value-of select="ISBN"/></td></tr>
</table>
</xsl:template>
</xsl:stylesheet>

qui produit un tableau pour chacun des livres du catalogue, en indiquant l'ordre
d'apparition via la fonction position() (on remarque également que l'on a supprimé

Tutoriel DOM et JDOM Page 50


Présenté par Cyril Vidal cyril@planetexml.com

tous les noeuds blancs au sein de catalogue.xml via l'instruction


<xsl:strip-space elements="catalogue"/>, autrement l'on obtiendrait les
livres 2 et 4), on obtient le résultat suivant en exécutant le code précédent (source
disponible ici:

Colophon
This tutorial was written entirely in XML, using the developerWorks Toot-O-Matic tutorial
generator. The Toot-O-Matic tool is an XSLT stylesheet and several XSLT extension
functions that convert an XML file into a number of HTML pages, a zip file, JPEG heading
graphics, and two PDF files. Our ability to generate multiple text and binary formats from a
single source file illustrates the power and flexibility of XML. (It also saves our production
team a great deal of time and effort.)

Tutoriel DOM et JDOM Page 51

Vous aimerez peut-être aussi