Considérez cet exemple comme la version XML d'"Hello World" d'un programme de traitement XML. Il vous montrera comment récupérer les données, et les afficher ensuite.
Note:Commencez par créer un fichier nommé Echo.java et taper le squelette de notre application :
Le code utilisé dans cette partie est disponible pour vérification ici Echo01.java. Il fonctionne avec le fichier d'exemple de la section précédante slideSample01.xml.Création du squelette
public class Echo extends HandlerBase { public static void main (String argv[]) { } } |
Cette classe étend la classe HandlerBase, qui implémente les interfaces nts EntityResolver, DTDHandler, DocumentHandler, ErrorHandler. Cela nous permettra de surcharger les méthodes nous intéressant, tout en laissant notre classe "mère" s'occuper du reste !
Comme ce programme tournera en tant qu'application, il nous faut une méthode main. De plus, nous avons besoin des arguments de la ligne de commande pour spécifier le fichier à afficher.
Ensuite, il nous faut rajouter les instructions d'importation des classes qui nous seront utiles dans l'application :Importer les classes nécessaires
import java.io.*; import org.xml.sax.*; import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; public class Echo extends HandlerBase { ... |
Les classes dejava.io, bien sur, sont nécessaire pour l'impression. Le paquetage org.xml.sax définit toutes les interfaces utilisées par le parser SAX. La classe SAXParserFactory permet de créer l'instance de parser que nous utiliserons. Elle nous retournera une ParserConfigurationException si elle ne peut pas créer un parser correspondant aux options de configuration. Enfin, la classe SAXParser qui est retournée par la factory qui nous servira à ... parser le fichier !
La première chose à faire, est de récupérer de la ligne de commande le nom du fichier à tariter, et de régler le flux de sortie. Rajoutez le texte en gras ci-dessous, qui se chargera de ce travail :Setting up for I/O
public static void main (String argv []) { if (argv.length != 1) { System.err.println ("Usage: cmd filename"); System.exit (1); } try { // Set up output stream out = new OutputStreamWriter (System.out, "UTF8"); } catch (Throwable t) { t.printStackTrace (); } System.exit (0); } static private Writer out; |
Lorsque nous créons le flux d'écriture, nous choisissons l'encodage de caractère UTF-8. Nous aurions pu choisir US-ASCII, ou UTF-16, que la plateforme Java supporte aussi. Pour plus d'informations sur les différents ensembles de caractères, reportez-vous à Java's Encoding Schemes.
Maintenant nous sommes pret à utiliser le parser. Rajoutez le texte en gras, pour le régler et le lancer :Mise en place du parser
public static void main (String argv []) { if (argv.length != 1) { System.err.println ("Usage: cmd filename"); System.exit (1); } // 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]), new Echo() ); } catch (Throwable t) { t.printStackTrace (); } System.exit (0); } |
Avec ces lignes de code, vous avez créé une instance de SAXParserFactory, déterminée par le réglage de la propriété système javax.xml.parsers.SAXParserFactory. Vous obtenez alors un parser de la factory, et vous précisez à ce parser la classe qui traitera les événements ainsi que le fichier à traiter.
Note: La classejavax.xml.parsers.SAXParser est un wrapper qui définit un ensemble de méthodes utiles. Elle recouvre l'objet org.xml.sax.Parser (qui est un peu moins sympathique). Si nécessaire, vous pouvez toujours obtenir ce parser en utilisant la méthode getParser() de la classe SAXParser.For now, you are simply catching any exception that the parser might throw. You'll learn more about error processing in a later section of the tutorial, Gestion des erreurs avec les parser non-validants.
Note:L'interface qui nous intéresse le plus dans notre cas est DocumentHandler. Cette interface nécessite l'implémentation d'un certain nombre de méthodes que le parser SAX invoquera en réponse aux différents événements qui se produiront. Pour l'instant, nous ne nous occuperons que des cinq méthodes suivantes : startDocument, endDocument, startElement, endElement, et characters. Entrez le code en gras ci-dessous, pour pouvoir gérer ces événements :
La méthode parse opére sur un fichier de type File pour des raisons de facilité. Cependant, un objet org.xml.sax.InputSource sous-javent est créé pour que le parser SAX puisse travailler dessus. Pour faire cela, il utilise une méthode statique de la classe com.sun.xml.parser.Resolver pour créer un InputSource à partir d'un objet java.io.File. Vous pourriez effectuer ce travail, mais cette méthode vous est fournie pour vous simplifier la vie (par contre elle vous lie au parser de chez Sun ...).Implémenter l'interface DocumentHandler
... static private Writer out; public void startDocument () throws SAXException { } public void endDocument () throws SAXException { } public void startElement (String name, AttributeList attrs) throws SAXException { } public void endElement (String name) throws SAXException { } public void characters (char buf [], int offset, int len) throws SAXException { } ... |
Chacune de ces méthodes doit pouvoir lancer une SAXException. Une exception levée à ce niveau, sera répercuté sur le parser, qui la retournera au code qui a invoqué le parser. Dans notre cas, on la récupérera dans la méthode main avec le catch Throwable.
A chaque fois qu'un tag ouvrant ou qu'un tag fermant est rencontré, le nom du tag est passé en tant que chaine aux méthodes startElement ou endElement. Quand un tag ouvrant est rencontré, tous les attributs qu'il définit seront aussi passés dans un objet AttributeList. Les caractères trouvés dans l'élément sont passés comme un tableau de caractères, avec le nombre de caractères (length) et le décalage dans le tableau correspondant au premier caractère.
La méthode DocumentHandler peut lancer une SAXExceptions mais pas de IOExceptions, qui pourrait arriver lors de l'écriture. En fait, la SAXException peut contenir une autre exception, ainsi il y a un sens à utiliser cette méthode pour rediriger toutes les erreurs vers le meme gestionnaire d'erreurs. Rajoutez le code ci-dessous pour définir une méthode emit qui effectue l'impression :Ecriture sur la sortie standard
public void characters (char buf [], int offset, int Len) throws SAXException { } private void emit (String s) throws SAXException { try { out.write (s); out.flush (); } catch (IOException e) { throw new SAXException ("I/O error", e); } } ... |
Lorsque la méthode emit est invoquée, toutes les erreurs d'I/O sont enrobées dans une SAXException avec un message l'identifiant. Cette exception est ensuite retournée au parser SAX. Rappelons nous juste que la méthode emit est une petite méthode qui gère l'affichage.
Avant de procéder au parsage, nous devons rajouter une toute petite chose. Rajoutez le code de la méthode nl permettant de générer un passage à la ligne (en respectant les réglages courant du système) :Gestion de l'indentation
private void emit (String s) ... } private void nl () throws SAXException { String lineEnd = System.getProperty("line.separator"); try { out.write (lineEnd); } catch (IOException e) { throw new SAXException ("I/O error", e); } } |
Note: Bien que cette méthode ne semble pas fondamentale, vous verrez qu'elle nous sera très utile dans la suite.Finallement, écrivons le vode qui effectue les traitements sur les événements DocumentHandler. Rajoutez juste le code en gras ci-dessous, gérant les événements de début et de fin de document :Gestion des événements de type Document
public void startDocument () throws SAXException { emit ("<?xml version='1.0' encoding='UTF-8'?>"); nl(); } public void endDocument () throws SAXException { try { nl(); out.flush (); } catch (IOException e) { throw new SAXException ("I/O error", e); } } |
Ici, nous produisons un écho de la déclaration XLM lorsque le parser rencontrera le début du document. Comme nous avons réglé le OutputStreamWriter avec un encodage UTF-8, on inclut cette spécification dans la déclaration.
Note: Cependant les classes d'entrées-sorties ne gèrent pas les encodages avec le signe "-", on utilise donc la notation "UTF8" plutot que "UTF-8".A la fin du document, on affiche juste le dernier tag, et on vide le flux de sortie. Plus grand chose à ajouter. Passons aux choses intéresantes. Ajouter le code gérant les début et fin d'éléments :
public void startElement (String name, AttributeList attrs) throws SAXException { emit ("<"+name); if (attrs != null) { for (int i = 0; i < attrs.getLength (); i++) { emit (" "); emit (attrs.getName(i)+"=\""+attrs.getValue (i)+"\""); } } emit (">"); } public void endElement (String name) throws SAXException { emit ("</"+name+">"); }With |
Avec ce code, on affiche le tag élément, en incluant tous
les attributs définit dans l'élément ouvrant. Pour
finaliser ce programme, ajoutez juste la dernière méthode
ci-dessous, qui se chargera de l'affichage des caractères :
public void characters (char buf [], int offset, int len) throws SAXException { String s = new String(buf, offset, len); emit (s); } |
Félicitations! Vous venez juste d'écrire votre prmière applicatiion utilisant un parser SAX. La prochaine étape est la compilation et l'exécution !
Note: Pour etre plus précis, le gestionnaire de caractères devrait scanner le buffer pour chercher les caractères ('&') et ('<') et les remplacer par les chaines de caractères "&" ou "<".Pour compiler le programme que vous venez de créer, utilisez la commande approprié à votre système :Compilation du programme
Windows:Pour exécuter le programme, il faudra bien prendre garde à inclure les ressources nécessaires, cequi donnera selon votre système :Unix:javac -classpath %XML_HOME%\jaxp.jar;%XML_HOME%\parser.jar Echo.javaou:javac -classpath ${XML_HOME}/jaxp.jar:${XML_HOME}/parser.jar Echo.java
- XML_HOME est le répertoire ou vous avez installé la bibliothèque XML JAXP ou chez moi (plus rapide).
- jaxp.jar contiens les API spécifiques de JAXP
- parser.jar contines les interfaces et classes qui gère les API SAX et DOM, ainsi que l'implémentation de référence de Sun, Project X.
Exécution du programme
Windows:Pour vous simplifier la vie, récupérez les scripts suivants qui automatiseront un peu ces taches fastidieuses.Unix:java -classpath .;%XML_HOME%\jaxp.jar;%XML_HOME%\parser.jar Echo slideSample.xmljava -classpath .:${XML_HOME}/jaxp.jar:${XML_HOME}/parser.jar Echo slideSample.xmlScripts de commandes
L'affichage produit par le programme est disponible dans Echo01-01.log. Voici une partie de ce dernier, nous montrant quelques espacements ... embetants :
Unix Windows Scripts build, run build.bat, run.bat Netscape Cliquez, puis File-->Save As Clic droit, puis Save Link As. Internet
Explorer-/- Clic droit, puis Save Target As. Vérification de l'affichage
... <slideshow title="Sample Slide Show" date="Date of publication" author="Yours Truly"> <slide type="all"> <title>Wake up to WonderWidgets!</title> </slide> ... |
En examinant lea sortie produite, un certain nombre de questions se posent. D'abord pourquoi y-a-t-il tant de passage à la ligne ? Et pourquoi les éléments sont_ils indentés correctement alors que l'on ne fait rien de spécial dans le code ? Nous verrons ces réponses dans un court instant, mais voisi déjà un certain nombre de choses à noter :
<!-- A SAMPLE set of slides -->n'apparait pas. Les commentaires sont par définition ignorés, sauf si vous implémentez un LexicalEventListener au lieu d'un DocumentHandler.
Le tag vide que nous avions définit (<item/>) est traité exactement de la meme manière qu'un élément vide à deux tags (<item></item>). De toute façon, c'est strictement identique du point de vue de la sémantique (juste au niveau de la taille prise).
Note: Le code que nous verrons dans cette section se trouve ici Echo02.java. L'affichage qu'il produit se trouve là Echo02-01.log.Effectuez les changements suivants pour que nous identifions mieux les différents événements :
public void startDocument () throws SAXException { |
Compilez et exécutez ce programme pour obtenir une trace plus détaillée. Les attributs sont maintenant affichés un par ligne, ce qui propre. Mais plus important, les lignes telles que celle-ci :
nous montre que la méthode characters est responsable de l'affichage des espaces qui créent l'indentation et les retours à la ligne qui séparent les attribtus.CHARS: | |
Note: The XML specification requires all input line separators to be normalized to a single newline. The newline character is specified as \n in Java, C, and Unix systems, but goes by the alias "linefeed" in Windows systems.Pour rendre l'affichage plus lisible, modifier le programme de telle manière qu'il n'affiche que les caractères différents de l'espace ' '."Compression" de l'affichage
Note: Le code de cette partie se trouve dans Echo03.java.
public void characters (char buf [], int offset, int len) throws SAXException { |
Si vous exécuteez le programme, vous remarquerez que vous avez
supprimé l'indentation aussi, car les espaces produisant l'indentation
font parties des espaces qui précédent le début d'un
élément. Rajouter le code en gras de ci-dessous pour gérer
de nouveau l'indentation :
static private Writer out; |
Ce code permet de conserver une trace des noveaux d'indentation, et d'indiquer si il est nécessaire ou non d'invoquer la méthode nl. Si vous régler la chaine effactuant l'indantation à "", l'affichage sera non-indenté (essayez, vous comprendrez mieux l'intéret de l'indentation ;-).
Vous serez heureux d'apprendre que vous avez atteint la fin de la partie "mécanique" d'ajout de code dans notre programme Echo. A partir de maintenant, vous n'allez plus qu'examiner des choses vous montrant le fonctionnement du parser. Ce que nous avons effectué auparavant, vous a montré comment le parser traite un message XML. Nous avons aussi obtenu un outil de débugage nous permettant de "dumper" un message XML tel que le voit le parser.
L'affichage complet de la dernière version du programme est contenu dans Echo03-01.log. Voici un extrait de cet affichage :Examen de l'affichage
ELEMENT: <slideshow ... CHARS: CHARS: ELEMENT: <slide ... END_ELM: </slide> CHARS: CHARS:Remarquez que la méthode characters est invoquée deux fois dans une meme ligne. En regardant le fichier source slideSample01.xml on remarque qu'il y a un comentaire juste avant le premier slide. Le premier appel à characters intervient avant ce commentaire. Le second juste après.
Remarquez également que la méthode characters est invoquée après le premier élément slide, ainsi qu'avant. Lorsque l'on y pense en termes de données structurées de manière hiérarchique, cela semble étrange. Après tout, nous nous attendions à ce que l'élément slideshow contienne un élément slide, pas du texte. Plus tard, nous verrons comment restreindre l'élément slideshow en utilisant un DTD. Lorsque l'on fait cela, la méthode characters n'est plus invoquée.
Cependant, en l'absence d'un DTD, le parser suppose que tous les éléments contiennent du texte comme l'élément <item> :
Voici à quoi ressemble la structure hiérarchique :<item>Why <em>WonderWidgets</em> are great</item>
Dans cet exemple, il est clair qu'il ya des caractères mélangés avec la structure hiérarchique des éléments. Le fait est que du texte puisse entourer des éléments (ou ne le puisse pas avec un DTD) nous aide à comprendre pourquoi l'on entends parfois parler de "données XML" et d'autre fois de "documents XML". XML gère à la fois les textes structurés, et les documents textuels incluant des balises. La seule différence entre les deux est de savois si du texte est autorisé ou pas entre les éléments.ELEMENT: <item> CHARS: Why ELEMENT: <em> CHARS: WonderWidgets END_ELM: </em> CHARS: are great END_ELM: </item>Documents et données