Analyse et Manipulation de Fichiers XML en Java : DOM4j, SAX et XPath

Manipulation XML avec DOM4j

DOM4j est une bibliothèque Java largement utilisée pour lire, écrire et naviguer dans des documents XML. Elle repose sur le modèle DOM (Document Object Model), ce qui permet une manipulation intuitive de l'arbre XML.

Configuration et Méthodes Principales

Pour utiliser DOM4j, il faut intégrer la dépendance correspondante dans votre projet. Une fois configurée, l'API permet d'interagir avec les nœuds, les éléments, les attribtus et le texte.

  • Nœuds : Iterator<Node> nodeIterator() pour récupérer tous les nœuds enfants.
  • Éléments : Element getRootElement() pour obtenir la racine, element("nom") pour un enfant spécifique, et elements() pour tous les enfants.
  • Attributs : attributeValue("nom") pour la valeur, attribute("nom") pour l'objet, et attributes() pour la liste complète.
  • Texte : getText() pour le texte courant, elementText("nom") pour le texte d'un enfant.

Modification et Sauvegarde

La création et la mise à jour de documents se font via des méthodes dédiées. Pour persister les modifications, on utilise un XMLWriter.

  • Ajout : DocumentHelper.createDocument(), addElement("nom"), addAttribute("nom", "valeur").
  • Modification : setValue() sur un attribut, setText() sur un élément.
  • Suppression : detach() pour retirer un élément ou un attribut de son parenet.

Analyse Événementielle avec SAX

Contrairement à DOM, SAX (Simple API for XML) est un analyseur événementiel. Il lit le fichier séquentiellement et déclenche des callbacks, ce qui le rend très économe en mémoire.

API Fondamentales

L'analyse repose sur la classe SAXParser et un gestionnaire d'événements héritant de DefaultHandler.


SAXParserFactory fabrique = SAXParserFactory.newInstance();
SAXParser analyseur = fabrique.newSAXParser();
analyseur.parse(new File("ressources/annuaire.xml"), new GestionnaireEvenementsSax());

Le gestionnaire d'événements implémente les méthodes suivantes :

  • startDocument() / endDocument() : Début et fin du document.
  • startElement(...) / endElement(...) : Détection des balises ouvrantes et fermantes.
  • characters(...) : Extraction du contenu textuel entre les balises.

Comparaison : DOM vs SAX

Caractéristique DOM (ex: DOM4j) SAX
Consommation mémoire Charge tout le document en mémoire. Inadapté aux très gros fichiers. Lit flux par flux. Idéal pour les fichiers volumineux.
Opérations Lecture, écriture, modification et suppression. Lecture seule.
Navigation Accès aléatoire, navigation dans toutes les directions. Navigation séquentielle stricte (de haut en bas).
Paradigme Orienté objet (Arbre de nœuds). Plus intuitif. Orienté événement. Plus complexe à implémenter.

Navigation avec XPath

XPath est un langage de requête permettant de cibler précisément des nœuds dans un document XML. Avec DOM4j, il nécessite l'ajout de la dépendance jaxen.

Méthodes principales : selectNodes("expression") pour une liste de nœuds, et selectSingleNode("expression") pour un nœud unique.

  • / : Chemin absolu depuis la racine.
  • // : Chemin relatif, recherche à n'imoprte quel niveau.
  • * : Jokers pour tous les éléments.
  • [] : Filtres conditionnels (ex: //contact[@id='123']).
  • @ : Sélection d'attributs.
  • text() : Sélection du contenu textuel.

Étude de Cas : Système de Gestion d'Annuaire

Pour illustrer ces concepts, nous allons concevoir une application de gestion de contacts stockée en XML. L'architecture sera découpée en plusieurs couches pour garantir la maintenabilité.

Modèle de Données


public class FicheContact {
    private String identifiant;
    private String nomComplet;
    private String genre;
    private int age;
    private String telephone;
    private String courriel;
    private String pseudo;

    public FicheContact(String identifiant, String nomComplet, String genre, int age, 
                        String telephone, String courriel, String pseudo) {
        this.identifiant = identifiant;
        this.nomComplet = nomComplet;
        this.genre = genre;
        this.age = age;
        this.telephone = telephone;
        this.courriel = courriel;
        this.pseudo = pseudo;
    }

    // Getters et Setters omis pour concision
    
    @Override
    public String toString() {
        return String.format("[%s] %s (%s, %d ans) - %s", identifiant, nomComplet, genre, age, courriel);
    }
}

Contrat de Service


import java.util.List;

public interface ServiceAnnuaire {
    void ajouterFiche(FicheContact fiche) throws Exception;
    void mettreAJourFiche(FicheContact fiche) throws Exception;
    void supprimerFiche(String identifiant) throws Exception;
    List<fichecontact> listerFiches();
}
</fichecontact>

Utilitaire XML

Pour éviter la duplication de code lors des opérations d'entrée/sortie, nous centralisons la logique de lecture et d'écriture.


import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class OutilXml {
    private static final String CHEMIN_FICHIER = "data/annuaire.xml";

    public static void sauvegarder(Document document) throws Exception {
        try (OutputStream flux = new FileOutputStream(CHEMIN_FICHIER)) {
            OutputFormat format = OutputFormat.createPrettyPrint();
            format.setEncoding("UTF-8");
            try (XMLWriter redacteur = new XMLWriter(flux, format)) {
                redacteur.write(document);
            }
        }
    }

    public static Document charger() {
        try {
            return new SAXReader().read(CHEMIN_FICHIER);
        } catch (DocumentException e) {
            throw new RuntimeException("Impossible de charger le fichier XML", e);
        }
    }
}

Implémentation du Service


import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ServiceAnnuaireImpl implements ServiceAnnuaire {

    @Override
    public void ajouterFiche(FicheContact fiche) throws Exception {
        File fichier = new File("data/annuaire.xml");
        Document doc;
        Element racine;

        if (fichier.exists()) {
            doc = OutilXml.charger();
            racine = doc.getRootElement();
        } else {
            doc = DocumentHelper.createDocument();
            racine = doc.addElement("annuaire");
        }

        Element noeudContact = racine.addElement("contact");
        noeudContact.addAttribute("id", fiche.getIdentifiant());
        noeudContact.addElement("nom").setText(fiche.getNomComplet());
        noeudContact.addElement("genre").setText(fiche.getGenre());
        noeudContact.addElement("age").setText(String.valueOf(fiche.getAge()));
        noeudContact.addElement("telephone").setText(fiche.getTelephone());
        noeudContact.addElement("courriel").setText(fiche.getCourriel());
        noeudContact.addElement("pseudo").setText(fiche.getPseudo());

        OutilXml.sauvegarder(doc);
    }

    @Override
    public void mettreAJourFiche(FicheContact fiche) throws Exception {
        Document doc = OutilXml.charger();
        String xpath = "//contact[@id='" + fiche.getIdentifiant() + "']";
        Element noeudContact = (Element) doc.selectSingleNode(xpath);

        if (noeudContact != null) {
            noeudContact.element("nom").setText(fiche.getNomComplet());
            noeudContact.element("genre").setText(fiche.getGenre());
            noeudContact.element("age").setText(String.valueOf(fiche.getAge()));
            noeudContact.element("telephone").setText(fiche.getTelephone());
            noeudContact.element("courriel").setText(fiche.getCourriel());
            noeudContact.element("pseudo").setText(fiche.getPseudo());
            OutilXml.sauvegarder(doc);
        }
    }

    @Override
    public void supprimerFiche(String identifiant) throws Exception {
        Document doc = OutilXml.charger();
        String xpath = "//contact[@id='" + identifiant + "']";
        Element noeudContact = (Element) doc.selectSingleNode(xpath);
        
        if (noeudContact != null) {
            noeudContact.detach();
            OutilXml.sauvegarder(doc);
        }
    }

    @Override
    public List<fichecontact> listerFiches() {
        Document doc = OutilXml.charger();
        List<fichecontact> annuaire = new ArrayList<>();
        List<element> elements = doc.selectNodes("//contact");

        for (Element elem : elements) {
            FicheContact fiche = new FicheContact(
                elem.attributeValue("id"),
                elem.elementText("nom"),
                elem.elementText("genre"),
                Integer.parseInt(elem.elementText("age")),
                elem.elementText("telephone"),
                elem.elementText("courriel"),
                elem.elementText("pseudo")
            );
            annuaire.add(fiche);
        }
        return annuaire;
    }
}
</element></fichecontact></fichecontact>

Tests Unitaires

L'utilisation de JUnit permet de valider chaque opération de manière isolée avant l'intégration dans l'interface utilisateur.


import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

public class TestServiceAnnuaire {

    private ServiceAnnuaire service;

    @BeforeEach
    public void initialiser() {
        service = new ServiceAnnuaireImpl();
    }

    @Test
    public void testAjoutEtListage() throws Exception {
        FicheContact nouvelleFiche = new FicheContact("C001", "Alice Dupont", "F", 28, 
                                                      "0612345678", "alice@test.com", "alice95");
        service.ajouterFiche(nouvelleFiche);
        
        List<fichecontact> fiches = service.listerFiches();
        assertFalse(fiches.isEmpty());
        assertEquals("Alice Dupont", fiches.get(0).getNomComplet());
    }

    @Test
    public void testMiseAJour() throws Exception {
        FicheContact ficheModifiee = new FicheContact("C001", "Alice Martin", "F", 29, 
                                                      "0687654321", "alice.martin@test.com", "alice_m");
        service.mettreAJourFiche(ficheModifiee);
        
        List<fichecontact> fiches = service.listerFiches();
        assertEquals("Alice Martin", fiches.stream()
                .filter(f -> f.getIdentifiant().equals("C001"))
                .findFirst().get().getNomComplet());
    }

    @Test
    public void testSuppression() throws Exception {
        service.supprimerFiche("C001");
        List<fichecontact> fiches = service.listerFiches();
        assertTrue(fiches.stream().noneMatch(f -> f.getIdentifiant().equals("C001")));
    }
}
</fichecontact></fichecontact></fichecontact>

Étiquettes: Java Dom4j sax xpath xml-parsing

Publié le 1 août à 18h13