Connaître les APIs du langage Java pour XML
Utiliser XML sur la couche Application d'une application web
Avant de commencer l'exercice il est temps de créer notre fichier d'exemple :
Fichier diapos.xml
<?xml version='1.0' encoding="ISO-8859-1"?>
<!-- un jeu de diapositives -->
<slideshow
title="Diaporama"
date="13/12/2001"
author="T.Tetu"
>
<!-- diapo de titre -->
<slide type="all">
<title>Réveillez vous avec les APIs XML pour Java!</title>
</slide>
<!-- presentation -->
<slide type="all">
<title>Introduction</title>
<item>Pourquoi <em>SAX</em> et <em>DOM</em> c'est cool ?</item>
<item/>
<item>Qui peut <em>utiliser</em> SAX & DOM ?</item>
</slide>
</slideshow>
Une fois ce fichier crée, nous allons utiliser SAX et DOM pour créer nos premiéres applications Java qui utilisent XML. Les applications devront finalement afficher le document précédent.
Evidémment le plus simple pour afficher un document même XML est de l'ouvrir puis d'afficher chaque ligne sur l'écran.
On utilise ici des techniques plus sophistiquéés.
Dans ce TP, on va créer des classes Java, qui seront lancées à la ligne de commande. Le TP "Atelier XML" combine les techniques des servlets et des APIs Java pour XML dans une application web, ce n'est pas l'objet de ce TP.
avant de poursuivre, assurez vous que crimson.jar et xalan.jar, installés dans le pre,mier TP sont déclarés dans la variable d'environnement CLASSPATH.
DOM est une API qui spécifie la façcon de modéliser un document XML. Les parsers XML implémentent DOM et renvoient des structures de données conformes à DOM.
1h
Pour lire un document XML et créer un objet correspondant, le principe est d'instancier un parser, de lui faire lire les données (méthodes parse, readstream,...). Le parser renvoie alors un objet de classe Document, prêt à être traité.
Le programme suivant lit un document XML (et ne fait rien d'autre). Il utilise à la fois SAX et DOM mais cela est trnapsarent pour le programmeur car il utilise un parser.
Classe SimpleParse.java
// Import des classes JAXP
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.w3c.dom.*;
import java.io.*;
/**
* Programme pour lire et afficher un document XML
*
*/
public class SimpleParse {
public static void main(String[] argv) {
if (argv.length != 1) {
System.err.println("Require a filename.");
System.exit(1);
}
try {
// Step 1: create a DocumentBuilderFactory and setNamespaceAware
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.isValidating(true);
// Step 2: create a DocumentBuilder
DocumentBuilder db = dbf.newDocumentBuilder();
// Step 3: parse the input file to get a Document object
Document doc = db.parse(new File(argv[0]));
// Codes for process will be here
} catch (Exception e) {
}
}
}
Créer la classe et la compiler :
D:\formation\xml_pratique\exemples>echo %CLASSPATH%
D:\jdk\lib;D:\xml\lib\crimson.jar;D:\xml\lib\xalan.jar
D:\formation\xml_pratique\exemples>javac SimpleParse.java
D:\formation\xml_pratique\exemples>java -cp %CLASSPATH%;. SimpleParse department
.xml
Pour tester la classe, récupérer les fichier department.xml, department2.xml, department.dtd et modifier la ligne de déclaration de DTD dans les fichiers XML pour que le DTD référencé pointe sur le bon fichier.
Normalement le programme n'affiche rien.
Cependant, le dernier objet renvoyé par le programme est le classe Document et c'est sur cet objet que nous ferons des traitements.
Les documents ont une structure en forme d'arbre. Ils sont composés d'élements de la classe Node.
Les descriptions des ces deux classes sont
Il est possible d'utiliser différents type de flux à parser : fichier, uri (ressources réseaux), flux de données (InputStream), données SAX,...
Cette variante permet d'utiliser un flux de données plutôt qu'un fichier.
Remplacer le step 3 par :
// Step 3: parse the input file to get a Document object
FileInputStream is = new FileInputStream(argv[0]);
Document doc = db.readStream(is);
Il est possible que les documents lus comportent des erreurs (mauvaise syntaxe XML,...).
Pour connaître les erreurs dans de document il faut implémenter une gestionnaire d'erreurs (ErrorHandler).
Ajouter la classe privée MyErrorHandler et modifier la classe SimpleParse pour qu'elle l'utilise :
//dans la methode main :
...
// Step 2: create a DocumentBuilder
DocumentBuilder db = null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException pce) {
System.err.println(pce);
System.exit(1);
}
db.setValidating(true);
// Set an ErrorHandler before parsing
OutputStreamWriter errorWriter =
new OutputStreamWriter(System.err, outputEncoding);
db.setErrorHandler(
new MyErrorHandler(new PrintWriter(errorWriter, true)));
// Step 3: parse the input file to get a Document object
...
}
// fin de la methode main
// Error handler to report errors and warnings
private static class MyErrorHandler implements ErrorHandler {
/** Error handler output goes here */
private PrintWriter out;
MyErrorHandler(PrintWriter out) {
this.out = out;
}
/**
* Returns a string describing parse exception details
*/
private String getParseExceptionInfo(SAXParseException spe) {
String systemId = spe.getSystemId();
if (systemId == null) {
systemId = "null";
}
String info = "URI=" + systemId +
" Line=" + spe.getLineNumber() +
": " + spe.getMessage();
return info;
}
// The following methods are standard SAX ErrorHandler methods.
// See SAX documentation for more info.
public void warning(SAXParseException spe) throws SAXException {
out.println("Warning: " + getParseExceptionInfo(spe));
}
public void error(SAXParseException spe) throws SAXException {
String message = "Error: " + getParseExceptionInfo(spe);
throw new SAXException(message);
}
public void fatalError(SAXParseException spe) throws SAXException {
String message = "Fatal Error: " + getParseExceptionInfo(spe);
throw new SAXException(message);
}
}
Recompiler SimpleParse.java et tester les deux fichiers XML. Normalement une erreur est signalée durant le parsing de department2.xml.
Pour afficher un document XML avec DOM il faut itérer sur tout ses éléments (Nodes).
On va donc créer des méthodes pour parcourir tous les noeuds de l'objet Document retourné par le parser.
Chaque noeud peut avoir des noeuds enfants (Childs). Il faudra aussi les parcourir pour traiter l'intégralité du document.
Pour cela le plus simple est d'écrire une procédure récursive et d'utiliser les méthodes de l'objet Node pour parcourir les noeuds.
Ajouter le bout de code suivant pour afficher le document.
// JAXP packages
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.w3c.dom.*;
import java.io.*;
import java.util.Hashtable;
/**
* Programme pour afficher un document XML
*
*/
public class SimpleDisplay {
public static void main(String[] argv) {
if (argv.length != 1) {
System.err.println("Require a filename.");
System.exit(1);
}
try {
// Step 1: create a DocumentBuilderFactory and setNamespaceAware
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
// Step 2: create a DocumentBuilder
DocumentBuilder db = null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException pce) {
System.err.println(pce);
System.exit(1);
}
// Set an ErrorHandler before parsing
OutputStreamWriter errorWriter =
new OutputStreamWriter(System.err, "UTF8");
db.setErrorHandler(
new MyErrorHandler(new PrintWriter(errorWriter, true)));
// Step 3: parse the input file to get a Document object
FileInputStream is = new FileInputStream(argv[0]);
Document doc = db.parse(is);
// Create hashtable for string key-value pairs
Hashtable hash = new Hashtable();
String key = null, value = null;
// Traverse all the children of the root element
for (Node kvchild = doc.getDocumentElement().getFirstChild();
kvchild != null;
kvchild = kvchild.getNextSibling()) {
// When child is an element
if (kvchild instanceof Element) {
// If tag name is "key", store its content in vkey
if (kvchild.getNodeName().equals("key")) {
key = makeChildrenText(kvchild);
// If tag name is "value"
} else if (kvchild.getNodeName().equals("value")) {
// Extract the text content from the child
value = makeChildrenText(kvchild);
// Check key is specified and
// store the key-value pair int the hashtable
if (key != null) {
hash.put(key, value);
key = null;
}
}
}
}
// Display the hashtable
System.out.println(hash);
} catch (Exception e) {
e.printStackTrace();
}
}
private static String makeChildrenText (Node node){
// Create a StringBuffer to store the result.
// StringBuffer is more efficient than String
StringBuffer buffer = new StringBuffer();
return makeChildrenText1 (node, buffer);
}
private static String makeChildrenText1 (Node node, StringBuffer buffer){
// Visit all the child nodes
for (Node ch = node.getFirstChild();
ch != null;
ch = ch.getNextSibling()) {
// Recursively call if the child may have children
if (ch instanceof Element || ch instanceof EntityReference) {
buffer.append(makeChildrenText(ch));
// If the child is a text, append it to the result buffer
} else if (ch instanceof Text) {
buffer.append(ch.getNodeValue());
}
}
return buffer.toString();
}
// Class for Errors
// Error handler to report errors and warnings
private static class MyErrorHandler implements ErrorHandler {
/** Error handler output goes here */
private PrintWriter out;
MyErrorHandler(PrintWriter out) {
this.out = out;
}
/**
* Returns a string describing parse exception details
*/
private String getParseExceptionInfo(SAXParseException spe) {
String systemId = spe.getSystemId();
if (systemId == null) {
systemId = "null";
}
String info = "URI=" + systemId +
" Line=" + spe.getLineNumber() +
": " + spe.getMessage();
return info;
}
// The following methods are standard SAX ErrorHandler methods.
// See SAX documentation for more info.
public void warning(SAXParseException spe) throws SAXException {
out.println("Warning: " + getParseExceptionInfo(spe));
}
public void error(SAXParseException spe) throws SAXException {
String message = "Error: " + getParseExceptionInfo(spe);
throw new SAXException(message);
}
public void fatalError(SAXParseException spe) throws SAXException {
String message = "Fatal Error: " + getParseExceptionInfo(spe);
throw new SAXException(message);
}
}
}
1h
SAX est une bibliothèque pour analyser (parser) des documents XML.
SAX est implémenté dans plusieurs langages mais dans cet exercice nous ne verrons que l'utilisation de SAX avec Java.
SAX est une API évenementielle, c'est à dire que le document XML va être analysé et lors de cette analyse des événements vont être déclenchés. Le principe est d'associer à chaque événement une méthode pour effectuer des taitements sur les données (construire un élément XML,...).
Classe SimpleSAXParse.java
public class SimpleSAXParse extends DefaultHandler {
public static void main(String argv[])
{
if (argv.length != 1) {
System.err.println("Usage: cmd filename");
System.exit(1);
}
// Use an instance of ourselves as the SAX event handler
DefaultHandler handler = new SimpleSAXParse();
// Use the default (non-validating) parser
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
// Set up output stream
out = new OutputStreamWriter(System.out, "UTF8");
// Parse the input
SAXParser saxParser = factory.newSAXParser();
saxParser.parse( new File(argv[0]), handler );
} catch (Throwable t) {
t.printStackTrace();
}
System.exit(0);
}
...
}
A partir du programme SAXTagCount.java créer un programme qui affiche la liste des noms des employés du fichier department.xml.
Créer un programme, à l'aide de la documentation des APIs SAX et DOM, qui concatène deux fichier XMLs avec DOM (création d'éléments (Nodes)).
Les programmes du sous répertoire exemple peuvent être utilisés comme base.
1/4h
Il est possible d'appliquer par programmation des feuilles de style à des données XML.
L'exemple suivant présente une façon de faire avec TRAX.
Classe SimpleTransform.java
// Imported TraX classes
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
// Imported java classes
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* Utilisation de l'interface TRAX pour appliquer une feuille de style XSL
* de la facon la plus simple possible (3 instructions)
*/
public class SimpleTransform
{
public static void main(String[] args)
throws TransformerException, TransformerConfigurationException,
FileNotFoundException, IOException
{
// Utilisation de la methode statique TransformerFactory.newInstance()*
// pour creer une instance de TransformerFactory
// La propriete systeme javax.xml.transform.TransformerFactory
// permet de choisir la classe de Transformer a instantier
// (ici org.apache.xalan.transformer.TransformerImpl.)
TransformerFactory tFactory = TransformerFactory.newInstance();
// Utiliser la fabrique de transformeur pour instantier un transformeur
// avec la feuille de style qui va bien
Transformer transformer = tFactory.newTransformer(new StreamSource("birds.xsl"));
// Use the Transformer to apply the associated Templates object to an XML document
// (foo.xml) and write the output to a file (foo.out).
transformer.transform(new StreamSource("birds.xml"), new StreamResult(new FileOutputStream("birds.out")));
System.out.println("************* Le resultat est dans birds.out *************");
}
}
Tester le programme avec les fichiers birds.xml et birds.xsl