42. SAX (Simple API for XML) 44. XSLT (Extensible Stylesheet Language Transformations) Imprimer Sommaire Consulter avec table des matières
Développons en Java   v 2.10  
Copyright (C) 1999-2016 .  

 

43. DOM (Document Object Model)

 

chapitre 4 3

 

Niveau : niveau 3 Intermédiaire 

 

DOM est l'acronyme de Document Object Model. C'est une spécification du W3C pour proposer une API qui permet de modéliser, de parcourir et de manipuler un document XML.

Le principal rôle de DOM est de fournir une représentation mémoire d'un document XML sous la forme d'un arbre d'objets et d'en permettre la manipulation (parcours, recherche et mise à jour)

A partir de cette représentation (le modèle), DOM propose de parcourir le document mais aussi de pouvoir le modifier. Ce dernier aspect est l'un des aspects les plus intéressants de DOM.

DOM est défini pour être indépendant du langage dans lequel il sera implémenté. DOM n'est qu'une spécification qui pour être utilisée doit être implémentée par un éditeur tiers.

Il existe plusieurs versions de DOM nommées «niveaux» :

Arbre DOM

Chaque élément qui compose l'arbre possède un type. Selon ce type, l'élément peut avoir certains éléments fils comme le montre le schéma ci-dessus. Le premier élément est le document encapsulé dans l'interface Document

Toutes les classes et interfaces sont regroupées dans le package org.w3c.dom

Ce chapitre contient plusieurs sections :

 

43.1. Les interfaces du DOM

Chaque type d'entité qui compose l'arbre est défini dans une interface. L'interface de base est l'interface Node dont plusieurs autres interfaces héritent.

 

43.1.1. L'interface Node

Chaque élément de l'arbre est un noeud encapsulé dans l'interface org.w3c.dom.Node ou dans une de ses interfaces filles.

L'interface définit plusieurs méthodes :

Méthode

Rôle

short getNodeType()

Renvoyer le type du noeud

String getNodeName()

Renvoyer le nom du noeud

String getNodeValue()

Renvoyer la valeur du noeud

NamedNodeList getAttributes()

Renvoyer la liste des attributs ou null

void setNodeValue(String)

Mettre à jour la valeur du noeud

boolean hasChildNodes()

Renvoyer un booléen qui indique si le noeud a au moins un noeud fils

Node getFirstChild()

Renvoyer le premier noeud fils du noeud ou null

Node getLastChild()

Renvoyer le dernier noeud fils du noeud ou null

NodeList getChildNodes()

Renvoyer une liste des noeuds fils du noeud ou null

Node getParentNode()

Renvoyer le noeud parent du noeud ou null

Node getPreviousSibling()

Renvoyer le noeud frère précédent

Node getNextSibling()

Renvoyer le noeud frère suivant

Document getOwnerDocument()

Renvoyer le document dans lequel le noeud est inclus

Node insertBefore(Node, Node)

Insèrer le premier noeud fourni en paramètre avant le second noeud

Node replaceNode(Node, Node)

Remplacer le second noeud fourni en paramètre par le premier

Node removeNode(Node)

Supprimer le noeud fourni en paramètre

Node appendChild(Node)

Ajouter le noeud fourni en paramètre aux noeuds enfants du noeud courant

Node cloneNode(boolean)

Renvoyer une copie du noeud. Le booléen fourni en paramètre indique si la copie doit inclure les noeuds enfants


Tous les différents noeuds qui composent l'arbre héritent de cette interface. La méthode getNodeType() permet de connaître le type du noeud. Le type est très important car il permet de savoir ce que contient le noeud.

Le type de noeud peut être :

Constante

Valeur

Rôle

ELEMENT_NODE

1

Elément

ATTRIBUTE_NODE

2

Attribut

TEXT_NODE

3

Texte

CDATA_SECTION_NODE

4

Section de type CData

ENTITY_REFERENCE_NODE

5

Réference d'entité

ENTITY_NODE

6

Entité

PROCESSING_INSTRUCTION_NODE

7

Instruction de traitement

COMMENT_NODE

8

Commentaire

DOCUMENT_NODE

9

Racine du document

DOCUMENT_TYPE_NODE

10

Document

DOCUMENT_FRAGMENT_NODE

11

Fragment de document

NOTATION_NODE

12

Notation

 

43.1.2. L'interface NodeList

Cette interface définit une liste ordonnée de noeuds suivant l'ordre du document XML. Elle définit deux méthodes :

Méthode

Rôle

int getLength()

Renvoie le nombre de noeuds contenus dans la liste

Node item(int)

Renvoie le noeud dont l'index est fourni en paramètre

 

43.1.3. L'interface Document

Cette interface définit les caractéristiques pour un objet qui sera la racine de l'arbre DOM. Elle hérite de l'interface Node. Un objet de type Document possède toujours un type de noeud DOCUMENT_NODE.

Méthode

Rôle

DocumentType getDocType()

Renvoyer les informations sur le type de document

Element getDocumentElement()

Renvoyer l'élément racine du document

NodeList getElementsByTagNames(String)

Renvoyer une liste des éléments dont le nom est fourni en paramètre

Attr createAttributes(String)

Créer un attribut dont le nom est fourni en paramètre

CDATASection createCDATASection(String)

Créer un noeud de type CDATA

Comment createComment(String)

Créer un noeud de type commentaire

Element createElement(string)

Créer un noeud de type élément dont le nom est fourni en paramètre

 

43.1.4. L'interface Element

Cette interface définit des méthodes pour manipuler un élément et en particulier les attributs d'un élément. Un élément dans un document XML correspondant à un tag. L'interface Element hérite de l'interface Node.

Un objet de type Element à toujours pour type de noeud ELEMENT_NODE

Méthode

Rôle

String getAttribute(String)

Renvoyer la valeur de l'attribut dont le nom est fourni en paramètre

removeAttribut(String)

Supprimer l'attribut dont le nom est fourni en paramètre

setAttribut(String, String)

Modifier ou créer un attribut dont le nom est fourni en premier paramètre et la valeur en second

String getTagName()

Renvoyer le nom du tag

Attr getAttributeNode(String)

Renvoyer un objet de type Attr qui encapsule l'attribut dont le nom est fourni en paramètre

Attr removeAttributeNode(Attr)

Supprimer l'attribut fourni en paramètre

Attr setAttributNode(Attr)

Modifier ou créer un attribut

NodeList getElementsByTagName(String)

Renvoyer une liste des noeuds enfants dont le nom correspond au paramètre fourni

 

43.1.5. L'interface CharacterData

Cette interface définit des méthodes pour manipuler les données de type PCDATA d'un noeud.

Méthode

Rôle

appendData()

Ajouter le texte fourni en paramètre aux données courantes

getData()

Renvoyer les données sous la forme d'une chaîne de caractères

setData()

Permettre d'initialiser les données avec la chaîne de caractères fournie en paramètre

 

43.1.6. L'interface Attr

Cette interface définit des méthodes pour manipuler les attributs d'un élément.

Les attributs ne sont pas des noeuds dans le modèle DOM. Pour pouvoir les manipuler, il faut utiliser un objet de type Element.

Méthode

Rôle

String getName()

Renvoyer le nom de l'attribut

String getValue()

Renvoyer la valeur de l'attribut

String setValue(String)

Mettre la valeur à celle fournie en paramètre

 

43.1.7. L'interface Comment

Cette interface permet de caractériser un noeud de type commentaire.

Cette interface étend simplement l'interface CharacterData. Un objet qui implémente cette interface générera un tag de la forme <!-- --> .

 

43.1.8. L'interface Text

Cette interface permet de caractériser un noeud de type Text. Un tel noeud représente les données d'un tag ou la valeur d'un attribut.

 

43.2. L'obtention d'un arbre DOM

Pour pouvoir utiliser un arbre DOM représentant un document, il faut utiliser un parseur qui implémente DOM. Ce dernier va parcourir le document XML et créer l'arbre DOM correspondant. Le but est d'obtenir un objet qui implémente l'interface Document car cet objet est le point d'entrée pour toutes les opérations sur l'arbre DOM.

Avant la définition de JAXP par Sun, l'instanciation d'un parseur était spécifique à chaque implémentation.

Exemple : utilisation de Xerces sans JAXP
package perso.jmd.tests.testdom;

import org.apache.xerces.parsers.*;
import org.w3c.dom.*;

public class TestDOM2 {

  public static void main(String[] args) {
    Document document = null;
    DOMParser parser = null;

    try {
      parser = new DOMParser();
      parser.parse("test.xml");
      document = parser.getDocument();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

JAXP permet, si le parseur respecte ses spécifications, de l'instancier de façon normalisée.

Exemple : utilisation de Xerces avec JAXP
package perso.jmd.tests.testdom;

import org.w3c.dom.*;
import javax.xml.parsers.*;

public class TestDOM1 {

  public static void main(String[] args) {
    Document document = null;
    DocumentBuilderFactory factory = null;

    try {
      factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = factory.newDocumentBuilder();
      document = builder.parse("test.xml");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

L'utilisation de JAXP est fortement recommandée.

Remarque : JAXP est détaillé dans une des sections suivantes de ce chapitre.

 

43.3. Le parcours d'un arbre DOM

Un document XML peut être représenté par une vue à plat ou arborescente. DOM Level 2 propose plusieurs interfaces pour permettre la navigation dans un document XML décrites dans la recommandation Traversal.

Le parcours peut être fait de deux manières différentes :

Il est possible d'appliquer un filtre lors des parcours.

Toutes les interfaces sont contenues dans le package org.w3c.dom.traversal.

 

43.3.1. L'interface DocumentTraversal

L'interface DocumentTraversal est le point de départ pour les objets qui vont permettre le parcours du document XML.

L'interface définit deux méthodes :

Méthode

Rôle

NodeIterator createNodeIterator(Node root, int whatToShow, NodeFilter filter, boolean entityReferenceExpansion)

Renvoyer une instance de type NodeIterator permettant le parcours de la sous-arborescence à partir du noeud fourni

TreeWalker createTreeWalker(Node root, int whatToShow, NodeFilter filter, boolean entityReferenceExpansion)

Renvoyer une instance de type TreeWalker permettant le parcours de la sous-arborescence à partir du noeud fourni


Les deux méthodes attendent les mêmes paramètres :

La spécification ne précise pas comment obtenir une instance de type DocumentTraversal : dans la plupart des implémentations, l'objet de type document implémente aussi l'interface DocumentTraversal.

 

43.3.2. L'interface NodeIterator

L'interface NodeIterator définit des méthodes pour parcourir les éléments d'un document XML.

Méthode

Rôle

void detach()

Détacher le NodeIterator de l'ensemble des noeuds qu'il parcourt. L'état du NodeIterator devient invalide

NodeFilter getFilter()

Obtenir le filtre de type NodeFilter

Node getRoot()

Obtenir le noeud initial du parcours

int getWhatToShow()

Obtenir une représentation du ou des types de noeuds qui seront obtenus lors du parcours

Node nextNode()

Renvoyer le noeud suivant dans le parcours

Node previousNode()

Renvoyer le noeud précédent dans le parcours


Le parcours se fait en utilisant un itérateur qui permet d'obtenir chacun des noeuds à tour de rôle.

Exemple :
import javax.xml.parsers.DocumentBuilder;      
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;

public class TestNodeIterator {
  public static void main(String[] args) {
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder loader = factory.newDocumentBuilder();
      Document document = loader.parse("livres.xml");
      DocumentTraversal traversal = (DocumentTraversal) document;
      NodeIterator iterator = traversal.createNodeIterator(
          document.getDocumentElement(), NodeFilter.SHOW_ELEMENT, null, true);
      for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) {
        System.out.println("Element: " + ((Element) n).getTagName());
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}

 

43.3.3. L'interface TreeWalker

Une instance de type TreeWalker permet de parcourir l'arborescence ou une sous-arborescence d'un document XML. Les noeuds obtenus lors de ce parcours peuvent être filtrés soit sur leur type en utilisant la propriété whatToShow ou en utilisant un filtre personnalisé.

L'interface TreeWalker définit plusieurs méthodes :

Méthode

Rôle

Node firstChild()

Se déplacer sur le prochain noeud fils visible par le parcours et le renvoyer

Node getCurrentNode()

Obtenir le noeud courant dans le parcours

NodeFilter getFilter()

Obtenir le filtre associé

Node getRoot()

Obtenir le noeud initial du parcours

int getWhatToShow()

Obtenir une représentation du ou des types de noeuds qui seront obtenus lors du parcours

Node lastChild()

Se déplacer sur le dernier noeud visible par le parcours et le renvoyer

Node nextNode()

Se déplacer sur le prochain noeud visible par le parcours et le renvoyer

Node nextSibling()

Se déplacer sur le prochain noeud frère visible par le parcours et le renvoyer

Node parentNode()

Se déplacer sur le noeud père visible par le parcours et le renvoyer

Node previousNode()

Se déplacer sur le précédent noeud visible par le parcours et le renvoyer

Node previousSibling()

Se déplacer sur le précédent noeud frère visible par le parcours et le renvoyer

void setCurrentNode(Node currentNode)

Changer le noeud courant dans le parcours pour celui fourni en paramètre


Exemple :
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;

public class TestTreeWalker {
  public static void main(String[] argv) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder loader = factory.newDocumentBuilder();
    Document document = loader.parse("livres.xml");
    DocumentTraversal traversal = (DocumentTraversal) document;
    TreeWalker walker = traversal.createTreeWalker(
        document.getDocumentElement(), NodeFilter.SHOW_ALL, null, true);
    walker.getRoot();
    traverseLevel(walker, "");
  }

  private static final void traverseLevel(TreeWalker walker, String indent) {
    Node noeud = walker.getCurrentNode();
    if (noeud instanceof Element) {
      System.out.println(indent + "- " + ((Element) noeud).getTagName());
      for (Node n = walker.firstChild(); n != null; n = walker.nextSibling()) {
        traverseLevel(walker, indent + "  ");
      }
    }
    walker.setCurrentNode(noeud);
  }
}

 

43.3.4. Le filtrage lors du parcours

L'interface NodeFilter définit des fonctionnalités de filtre pour définir les noeuds qui doivent être obtenus lors du parcours.

L'interface NodeFilter définit une seule méthode : short acceptNode(Node n).

La méthode renvoie une valeur de type short qui permet de préciser si le noeud est filtré ou non. L'interface NodeFilter définit trois constantes :

Aucune implémentation standard n'est fournie par l'API DOM.

Une implémentation de l'interface NodeFilter ne connait pas la structure de données parcourue ni la façon dont ces données sont parcourues. La seule qu'elle peut utiliser c'est le noeud fourni en paramètre.

Exemple :
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeFilter;

public class MonNodeFilter implements NodeFilter {
  @Override
  public short acceptNode(Node n) {
    if (n.getNodeName().contentEquals("auteur")) {
      return FILTER_ACCEPT;
    }
    return FILTER_SKIP;
  }
}

Si une instance de type NodeFilter est fournie à un NodeIterator ou à un TreeWalker alors ils appliquent le filtre pour savoir si un noeud doit être retourné ou non. Si le filtre renvoie FILTER_ACCEPT alors le noeud est renvoyé lors du parcours sinon le prochain noeud du parcours est recherché pour lui appliquer le filtre.

Exemple :
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;

public class TestTreeWalkerAvecFiltre {
  public static void main(String[] argv) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder loader = factory.newDocumentBuilder();
    Document document = loader.parse("livres.xml");
    DocumentTraversal traversal = (DocumentTraversal) document;
    TreeWalker walker = traversal.createTreeWalker(
        document.getDocumentElement(), NodeFilter.SHOW_ELEMENT,
        new MonNodeFilter(), true);
    Node noeud = null;
    noeud = walker.nextNode();
    while (noeud != null) {
      System.out.println(noeud.getNodeName() + " : "
          + noeud.getChildNodes().item(0).getNodeValue());
      noeud = walker.nextNode();
    }
  }
}

 

43.4. La modification d'un arbre DOM

Un des grands intérêts du DOM est sa faculté de créer ou modifier l'arbre qui représente un document XML.

 

43.4.1. La création d'un document

La méthode newDocument() de la classe DocumentBuilder renvoie une nouvelle instance d'un objet de type Document qui encapsule un arbre DOM vide.

Il faut a minima ajouter un tag racine au document XML. Pour cela, il faut appeler la méthode createElement() de l'objet Document en lui passant le nom du tag racine pour obtenir une référence sur le nouveau noeud. Il suffit ensuite d'utiliser la méthode appendChild() de l'objet Document en lui fournissant la référence sur le noeud en paramètre.

Exemple :
package perso.jmd.tests.testdom;

import org.w3c.dom.*;
import javax.xml.parsers.*;

public class TestDOM09 {

  public static void main(String[] args) {
    Document document = null;
    DocumentBuilderFactory fabrique = null;

    try {
      fabrique = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = fabrique.newDocumentBuilder();
      document = builder.newDocument();
      Element racine = (Element) document.createElement("bibliotheque");
      document.appendChild(racine);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

43.4.2. L'ajout d'un élément

L'interface Document propose plusieurs méthodes createXXX pour créer des instances de différents types d'éléments. Il suffit alors d'utiliser la méthode appendChild() d'un noeud pour lui attacher un noeud fils.

Exemple :
     Element monElement = document.createElement("monelement");
     Element monElementFils = document.createElement("monelementfils");
     monElement.appendChild(monElementFils);

Pour ajouter un texte à un noeud, il faut utiliser la méthode createTextNode() pour créer un noeud de type Text et l'ajouter au noeud concerné avec la méthode appendChild().

Exemple :
     Element monElementFils = document.createElement("monelementfils");
     monElementFils.appendChild(document.createTextNode("texte du tag fils");
     monElement.appendChild(monElementFils);

Pour ajouter un attribut à un élément, il existe deux méthodes : setAttributeNode() et setAtribute().

La méthode setAttributeNode() attend un objet de type Attr qu'il faut préalablement instancier.

Exemple :
     Attr monAttribut = document.createAttribute("attribut");
     monAttribut.setValue("valeur");
     monElement.setAttributeNode(monAttribut);

La méthode setAttribut permet d'associer directement un attribut et sa valeur grâce aux paramètres fournis.

Exemple :
    monElement.setAttribut("attribut","valeur");

La création d'un commentaire se fait en utilisant la méthode createComment() de la classe Document.

Toutes ces actions permettent la création complète d'un arbre DOM représentant un document XML.

Exemple : un exemple complet
package perso.jmd.tests.testdom;

import org.w3c.dom.*;
import javax.xml.parsers.*;

public class TestDOM11 {

  public static void main(String[] args) {
    Document document = null;
    DocumentBuilderFactory fabrique = null;

    try {
      fabrique = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = fabrique.newDocumentBuilder();
      document = builder.newDocument();
      Element racine = (Element) document.createElement("bibliotheque");
      document.appendChild(racine);
      Element livre = (Element) document.createElement("livre");
      livre.setAttribute("style", "1");
      Attr attribut = document.createAttribute("type");
      attribut.setValue("broche");
      livre.setAttributeNode(attribut);
      racine.appendChild(livre);
      livre.setAttribute("style", "1");
      Element titre = (Element) document.createElement("titre");
      titre.appendChild(document.createTextNode("Titre 1"));
      livre.appendChild(titre);
      racine.appendChild(document.createComment("mon commentaire"));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Résultat :
<?xml version="1.0" encoding="UTF-8"?>
<bibliotheque>
  <livre style="1" type="broche">
    <titre>Titre 1</titre>
  </livre>
  <!--mon commentaire-->
</bibliotheque>

 

43.5. L'envoi d'un arbre DOM dans un flux

Une fois un arbre DOM créé ou modifié, il est souvent utile de l'envoyer dans un flux (sauvegarde dans un fichier ou une base de données, envoi dans un message JMS ...).

Bizarrement, DOM Level 1 et 2 ne proposent rien pour réaliser cette tâche pourtant essentielle. Ainsi, chaque implémentation propose sa propre méthode en attendant des spécifications qui feront sûrement partie du DOM Level 3.

 

43.5.1. Un exemple avec Xerces

Xerces fournit la classe XMLSerializer qui permet de créer un document XML à partir d'un arbre DOM.

Xerces est téléchargeable sur le site web http://xerces.apache.org/xerces2-j/ sous la forme d'une archive de type zip qu'il faut décompresser dans un répertoire du système. Il suffit alors d'ajouter les fichiers xmlParserAPIs.jar et xercesImpl.jar dans le classpath.

Exemple :
package perso.jmd.tests.testdom;

import org.w3c.dom.*;
import javax.xml.parsers.*;
import org.apache.xml.serialize.*;

public class TestDOM10 {

  public static void main(String[] args) {
    Document document = null;
    DocumentBuilderFactory fabrique = null;

    try {
      fabrique = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = fabrique.newDocumentBuilder();
      document = builder.newDocument();
      Element racine = (Element) document.createElement("bibliotheque");
      document.appendChild(racine);

      for (int i = 1; i < 4; i++) {
        Element livre = (Element) document.createElement("livre");
        Element titre = (Element) document.createElement("titre");
        titre.appendChild(document.createTextNode("Titre "+i));
        livre.appendChild(titre);
        Element auteur = (Element) document.createElement("auteur");
        auteur.appendChild(document.createTextNode("Auteur "+i));
        livre.appendChild(auteur);
        Element editeur = (Element) document.createElement("editeur");
        editeur.appendChild(document.createTextNode("Editeur "+i));
        livre.appendChild(editeur);
        racine.appendChild(livre);
      }

      XMLSerializer ser = new XMLSerializer(System.out, 
         new OutputFormat("xml", "UTF-8", true));
      ser.serialize(document);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Résultat :
<?xml version="1.0" encoding="UTF-8"?>
<bibliotheque>
  <livre>
    <titre>Titre 1</titre>
    <auteur>Auteur 1</auteur>
    <editeur>Editeur 1</editeur>
  </livre>
  <livre>
    <titre>Titre 2</titre>
    <auteur>Auteur 2</auteur>
    <editeur>Editeur 2</editeur>
  </livre>
  <livre>
    <titre>Titre 3</titre>
    <auteur>Auteur 3</auteur>
    <editeur>Editeur 3</editeur>
  </livre>
</bibliotheque>

 


  42. SAX (Simple API for XML) 44. XSLT (Extensible Stylesheet Language Transformations) Imprimer Sommaire Consulter avec table des matières Développons en Java   v 2.10  
Copyright (C) 1999-2016 .