Tutoriel DOM et JDOM Présenté par Cyril Vidal
[email protected]
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
[email protected]
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
[email protected] Bonne navigation!
Tutoriel DOM et JDOM
Page 2
Présenté par Cyril Vidal
[email protected]
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
[email protected]
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
[email protected]
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:
La généalogie de la morale Friedrich Nietzsche <édition>folio essaisédition> 2-07-032327-7 Réflexions sur la poésie Paul Claudel <édition>folio essaisédition> 2-07-032746-9
DOM le représente ainsi:
Tutoriel DOM et JDOM
Page 5
Présenté par Cyril Vidal
[email protected]
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
[email protected]
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
: 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 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
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
[email protected]
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
[email protected]
"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 ), 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 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 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 NodeList livres = catalogue.getElementsByTagName("livre"); //récupération de tous les éléments inclus dans System.out.println("Les caractéristiques des livres du catalogue sont:"); for( int i=0; i
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
[email protected]
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 donnant quelques précisions sur l'auteur de tel ou tel livre, comme ceci: Réflexions sur la poésie Paul Claudel n'est pas seulement grand écrivain, mais aussi grand critiquePaul Claudel <édition>folio essaisédition> 2-07-032746-9
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
[email protected]
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
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
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: La généalogie de la morale ... Réflexions sur la poésie ...
Tutoriel DOM et JDOM
Page 11
Présenté par Cyril Vidal
[email protected]
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
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 ) 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
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
[email protected]
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: Saisie/Mise à jour d'un livre Saisie/Mise à jour d'un livre
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
[email protected]
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: UpDate MiseAJourServlet UpDate /update
Le premier élément associe un nom, ici UpDate à la classe de servlet MiseAJourServlet, tandis que le deuxième élément 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:
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
[email protected]
/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
[email protected]
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 ou , 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 , 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 de la hiérarchie Tutoriel DOM et JDOM
Page 16
Présenté par Cyril Vidal
[email protected]
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
[email protected]
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
[email protected]
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
[email protected]
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
[email protected]
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
[email protected]
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(""); writer.write(sautLigne); // boucle sur chaque enfant NodeList noeuds = noeud.getChildNodes(); if (noeuds != null) { for (int i=0; i
On remarque ici que les éléments fils du document racine (dans notre cas, il s'agit de l'unique élément 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
[email protected]
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"); // 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"); 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
[email protected]
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(""); break; case Node.COMMENT_NODE: writer.write(niveauIndentation + ""); 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
[email protected]
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(""); 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
[email protected]
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
[email protected]
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
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
[email protected]
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
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
[email protected]
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
[email protected]
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 , ainsi qu'un et un . 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
[email protected]
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
[email protected]
"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 et dans l'espace de nom préfixé par "livre" et l'élément 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
[email protected]
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
[email protected]
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 , lequel contient lui-même quelques mots-clés, signalisés par une balise de type : 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 : 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
[email protected]
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 , 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
[email protected]
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
[email protected]
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 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
[email protected]
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 , 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
[email protected]
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
[email protected]
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 , puis un élément , 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 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 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
[email protected]
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
[email protected]
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 : 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
[email protected]
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
[email protected]
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 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
[email protected]
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
[email protected]
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
[email protected]
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 import import import import
org.jdom.output.XMLOutputter; org.jdom.output.DOMOutputter; org.jdom.input.SAXBuilder; org.jdom.JDOMException; 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
[email protected]
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
[email protected]
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 //TrAX import import //JDOM import import import import import
java.io.*; javax.xml.transform.*; javax.xml.transform.stream.StreamSource; org.jdom.transform.JDOMResult; org.jdom.transform.JDOMSource; org.jdom.output.XMLOutputter; org.jdom.input.SAXBuilder; 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
[email protected]
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: CATALOGUE
LIVRE N°
TITRE | |
AUTEUR | |
EDITION | |
ISBN | |
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
[email protected]
tous les noeuds blancs au sein de catalogue.xml via l'instruction , 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