La sauvegarde d'un document XML permet de conserver les éventuelles modifications de valeurs ou de structure pratiquées sur ce document.

La sauvegarde d'un document XML s'effectue généralement par l'intermédiaire de certaines classes du paquetage java.xml.transform.

La classe TransformerFactory consitue une fabrique d'objets Transformer à partir desquels, des propriétés de sortie peuvent être spécifiées.

TransformerFactory tfabrique = TransformerFactory.newInstance();
Transformer transformeur = tfabrique.newTransformer();

Ces paramètres sont la méthode de rendu (methode = "xml | html | text | autre"), l'encodage de caractères (encoding = "ISO-8859-1 | UTF-16 | UTF-8 | autre"), l'indentation du texte (indent = "yes | no"), l'écriture du prologue XML (omit-xml-declaration = "yes | no"), la spécification d'un identificateur PUBLIC et/ou SYSTEM (doctype-public = "Identificateur" et doctype-system = "Identificateur"), etc. Les propriétés de sortie représentent les attributs de 'élément XSLT xsl:output, lequel indique le format de sortie du document résultant dans une feuille de style de transformation. La méthode setOutputProperties() permet d'assigner les propriétés aux objets Transformer.

Properties proprietes = new Properties();
proprietes.put("method", "xml");
proprietes.put("version", "1.0"); 
proprietes.put("encoding", "ISO-8859-1"); 
proprietes.put("standalone", "yes");
proprietes.put("indent", "yes");
proprietes.put("omit-xml-declaration", "no");
transformeur.setOutputProperties(proprietes);

Ensuite, un objet Source doit être conçu à partir d'une ressource XML DOM (Document Object Model) ou SAX (Simple API XML).

DocumentBuilderFactory fabrique = 
                               DocumentBuilderFactory.newInstance();
DocumentBuilder analyseur = fabrique.newDocumentBuilder();
Document document = analyseur.parse(new File("logitheque.xml"));
DOMSource entree = new DOMSource(document);

XMLReader analyseurSax = XMLReaderFactory.createXMLReader();
InputSource sourceEntree = 
                new InputSource(new FileInputStream("logitheque.xml"));
SAXSource entree = new SAXSource(analyseurSax, sourceEntree);

Un objet Result doit être créé préalablement afin de recevoir le résultat envoyé par la méthode transform(). L'objet Result peut être un résultat DOM (DOMResult) ou SAX (SAXResult) ou encore un flux (StreamResult).

File fichier = new File("resultat.xml");
FileOutputStream flux = new FileOutputStream(fichier);
Result sortie = new StreamResult(flux);

Enfin, les objets Source et Result doivent être passés à la méthode transform() de l'objet Transformer. Après le processus de transformation, le résultat est transmis à l'objet Result, lequel pourrait être directement exploité ultérieurement par l'application sous la forme d'une arborescence DOM ou d'un objet SAX. Dans le cas d'un flux, le résultat sera être écrit dans un fichier local (File) ou distant (URL).

public static void ecrireXML(Document document) {
  try{
    TransformerFactory tfabrique = TransformerFactory.newInstance();
    Transformer transformeur = tfabrique.newTransformer();

    Properties proprietes = new Properties();
    proprietes.put("method", "xml");
    proprietes.put("encoding", "ISO-8859-1"); 
    proprietes.put("indent", "yes");
    transformeur.setOutputProperties(proprietes);

    Source entree = new DOMSource(document);
    File oFic = new File(
                   document.getDocumentElement().getNodeValue() + ".xml");
    FileOutputStream fos = new FileOutputStream(oFic);

    if(fos != null) {
      Result sortie = new StreamResult(fos);
      transformeur.transform(entree, sortie);
    }

    fos.flush();
    fos.close();
  }
  catch (FileNotFoundException e) {
    e.printStackTrace();
  }
  catch (FactoryConfigurationError e) {
    //erreur de configuration de fabrique
    e.printStackTrace();
  }
  catch (TransformerException e) {
    //erreur lors de la transformation
    e.printStackTrace();
  }
  catch (IOException e) {
    e.printStackTrace();
  }
}

La spécification DOM Level 3 Load and Save Specification fournit de nouveaux outils de sauvegarde et de chargement de documents XML. Elle intègre des objets d'entrée (LSInput), de sortie (LSOutput) et de sérialisation (LSSerializer).

Les objets implémentant l'interface LSInput acceptent diverses entrées :

  • un flux d'octets avec la méthode setByteStream(),
  • un flux de caractères avec la méthode setCharacterStream(),
  • une chaine de caractères avec la méthode setStringData(),
  • une adresse URI avec la méthode setSystemId().

L'adresse URI est résolue par l'analyseur seulement s'il n'existe aucune autre source d'entrée, c'est-à-dire, qu'il ne doit pas y avoir de flux d'octets ou de caractères et de chaîne de caractères.

L'encodage de caractères des sources d'entrée doit être fourni, si nécessaire, au moyen de la méthode setEncoding().

Les objets implémentant l'interface LSOutput supportent plusieurs types de sortie.

  • un flux d'octets avec la méthode setByteStream(),
  • un flux de caractères avec la méthode setCharacterStream(),
  • une adresse URI avec la méthode setSystemId().

Le paquetage org.apache.xerces.dom propose deux classes implémentant les interfaces LSInput et LSOutput. Il s'agît des classes DOMInputImpl et DOMOutputImpl qui permettront de charger et de sauvegarder une source XML sans utiliser le paquetage java.xml.transform dont le but originel n'est pas de sauvegarder des documents XML, mais de les transformer.

File fichier = new File("source.xml");
FileInputStream fluxEntree = new FileInputStream(fichier);
DOMInputImpl entree = new DOMInputImpl();
entree.setEncoding("ISO-8859-1");
entree.setSystemId("source.xml");
entree.setByteStream(fluxEntree);

File fichier = new File("resultat.xml");
FileOutputStream fluxSortie = new FileOutputStream(fichier);
DOMOutputImpl sortie = new DOMOutputImpl();
sortie.setEncoding("ISO-8859-1");
sortie.setSystemId("resultat.xml");
sortie.setByteStream(fluxSortie);

Il est possible d'obtenir une implémentation des interfaces LSInput, LSOutput et LSSerializer, en obtenant d'abord un objet DOMImplementationRegistry, à partir duquel il faut invoquer la méthode getDOMImplementation(), en lui passant les caractéristiques à supporter pour prendre en compte l'API de sauvegarde et de chargement. Ensuite, il ne reste plus qu'à employer les méthodes énoncées dans les interfaces.

DOMImplementationRegistry enregistreur = 
                DOMImplementationRegistry.newInstance();
DOMImplementationLS implLS = (DOMImplementationLS)
                enregistreur.getDOMImplementation("LS 3.0");
LSSerializer serialiseur = implLS.createLSSerializer();

DOMImplementationLS implDOM = (DOMImplementationLS)
                enregistreur.getDOMImplementation("Core 3.0");
LSParser analyseur = implDOM.createLSParser(
                               DOMImplementationLS.MODE_ASYNCHRONOUS, 
                               "http://www.w3.org/TR/REC-xml");
Document doc = analyseur.parseURI(
                       "http://laltruiste.com/ressource.xml");

La caractéristique LS 3.0 ou LS-Async 3.0 permettent d'obtenir une implémentation de l'API DOM Load and Save, tandis que Core 3.0 ou XML 3.0 donnera une implémentation complète de l'API DOM de niveau 3, comprenant évidemment l'implémentation du paquetage org.w3c.dom.ls. De cette manière, tous les dispositifs de chargement et de sauvegarde seront disponibles directement.

public boolean sauvegarder(Document document, String repertoire) {
    try {
        DOMImplementationLS implLS = null;
        if(document.getImplementation().hasFeature("LS", "3.0")) {
            implLS = (DOMImplementationLS)
                                      document.getImplementation();
        }
        else { 
            DOMImplementationRegistry enregistreur = 
                                          DOMImplementationRegistry.newInstance();
            implLS = (DOMImplementationLS)
                                enregistreur.getDOMImplementation("LS 3.0");
        }
        if(implLS == null)
            System.out.println(
                "Votre implémentation ne supporte "
              + "pas l'API DOM Load and Save !");

        File fichier = new File(
                document.getDocumentURI().replaceFirst("http:/", repertoire));
        File dossier = fichier.getParentFile();
        if(!dossier.mkdirs())
            System.out.println("Le dossier n'a pu être créé : " 
                                   + dossier.toString());
        FileOutputStream fluxSortie = new FileOutputStream(fichier);
        LSSerializer serialiseur = implLS.createLSSerializer();
        LSOutput sortie = implLS.createLSOutput();
        sortie.setEncoding("ISO-8859-1");
        sortie.setSystemId(fichier.toString());
        sortie.setByteStream(fluxSortie);
        serialiseur.write(document, sortie);
        fluxSortie.flush();
        fluxSortie.close();
        return true;
    }
    catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    catch (ClassCastException e) {
        e.printStackTrace();
    }
    catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    catch (InstantiationException e) {
        e.printStackTrace();
    }
    catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
    return false;
}

La classe CoreDocumentImpl de l'implémentation Xerces recèle d'autres dispositifs de chargement et de sauvegarde des document XML. Les méthodes load(), loadXML() et saveXML(), actuellement expérimentales, permettent de charger un document XML à partir d'une ressource pointée par une adresse URI ou d'une chaîne de caractères, et de sauvegarder le noeud passé en argument dans une chaîne de caractères.

CoreDocumentImpl doc = new CoreDocumentImpl();
doc.getFeature("entities", "false");
doc.getFeature("normalize-characters", "false");
doc.getFeature("check-character-normalization", "false");
if(doc.load("logitheque.xml"))
  System.out.println("Le fichier a été chargé !");
else if(doc.loadXML(
           "<?xml version=\"1.0\"?><racine>texte</racine>"))
  System.out.println("La chaîne de caractères a été chargée !");
else {
  System.out.println("Un document XML a été créé !");
  Element racine = doc.createElement("racine");
  doc.appendChild(racine);
  Text texte = doc.createTextNode("Une valeur textuelle...");
  racine.appendChild(texte);
}
String chaine = doc.saveXML(doc.getDocumentElement());
System.out.println(chaine);

La méthode saveXML() fournit le même résultat que la méthode writeToString() de l'interface LSSerializer, laquelle dispose de deux autres méthodes de sérialisation writeToURI() et write(). L'interface LSSerializer, définie dans le paquetage org.w3c.dom.ls, fournit une API spécialisée dans la sérialisation des objets DOM en données XML envoyées dans une chaîne de caractères ou dans un flux de sortie.

DOMImplementationRegistry enregistreur = 
                DOMImplementationRegistry.newInstance();
DOMImplementationLS impl = (DOMImplementationLS)
                enregistreur.getDOMImplementation("LS 3.0");
LSSerializer serialiseur = impl.createLSSerializer();
boolean reussi = serialiseur.write(noeud, oLSOutput);
String resultat = serialiseur.writeToString(noeud);
reussi = serialiseur.writeToURI(noeud, adresseURI);

La normalisation d'un document XML peut s'opérer au moyen de la méthode normalizeDocument() des objets Document. Cette opération peut être utile pour stocker un document normalisé dans une représentation qui sera identique lors d'opérations successives de sauvegarde et de rechargement. Elle est également utile lorsque des opérations spécifiques, dépendant directement de la structure de l'arborescence spécifique à un document, doivent être pratiquées.

La méthode normalizeDocument() exprime le document sous une forme "normale". En fait, elle met à jour les références d'entité en fonction du dictionnaire d'entités de la DTD et normalise des noeuds Text, c'est-à-dire place tous les noeuds Text dans toute la profondeur de la sous arborescence en dessous du noeud courant, dans une forme "normale" incluant les attributs, où seulement le balisage (éléments, commentaires, instructions de traitement, sections CDATA et références d'entité) sépare les noeuds Text, c'est-à-dire qu'il ne doit pas y avoir de noeuds de type Text adjacents ou vides.

document.normalizeDocument();

Si aucune normalisation n'est pratiqué, le résultat dépendra des caractéristiques fixées sur l'objet DOMConfiguration du document XML courant. Les différentes dispositions mises en place par ces caractéristiques, régissant les opérations exécutées réellement sur le document XML. Concrètement, cette méthode a pu également rendre l'espace de noms du document bien-formé selon l'algorithme décrit en son sein, vérifier la normalisation des caractères, enlever les noeuds CDATASection, etc..