Skip to content

6. XML et JAVA

Dans ce chapitre, nous introduisons l'utilisation de documents XML avec Java. Nous le ferons dans le contexte de l'application impôts étudiée dans le chapitre précédent.

6.1. Fichiers XML et feuilles de style XSL

Considérons le fichier XML simulations.xml suivant qui pourrait représenter le résultat de simulations de calculs d'impôts :

<?xml version="1.0" encoding="ISO-8859-1"?>
<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
  <simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>

Si on le visualise avec IE 6, on obtient le résultat suivant :

Image

IE6 reconnaît qu'il a affaire à un fichier XML (grâce au suffixe .xml du fichier) et le met en page d'une façon qui lui est propre. Avec Netscape on obtient une page vide. Cependant si on regarde le code source (View/Source) on a bien le fichier XML d'origine :

Image

Pourquoi Netscape n'affiche-t-il rien ? Parce qu'il lui faut une feuille de style qui lui dise comment transformer le fichier XML en fichier HTML qu'il pourra alors afficher. Il se trouve que IE 6 a lui une feuille de style par défaut lorsque le fichier XML n'en propose pas ce qui était le cas ici.

Il existe un langage appelé XSL (eXtended StyleSheet Language) permettant de décrire les transformations à effectuer pour passer un fichier XML en un fichier texte quelconque. XSL permet l'utilisation de nombreuses instructions et ressemble fort aux langages de programmation. Nous le détaillerons pas ici car il y faudrait plusieurs dizaines de pages. Nous allons simplement décrire deux exemples de feuilles de style XSL. La première est celle qui va transformer le fichier XML simulations.xml en code HTML. On modifie ce dernier afin qu'il désigne la feuille de style que pourront utiliser les navigateurs pour le transformer en document HTML, document qu'ils pourront afficher :

<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="simulations.xsl"?>
<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
  <simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>

La commande XML

<?xml-stylesheet type="text/xsl" href="simulations.xsl"?>

désigne le fichier simulations.xsl comme un feuille de style (xml-stylesheet) de type text/xsl c.a.d. un fichier texte contenant du code XSL. Cette feuille de style sera utilisée par les navigateurs pour transformer le texte XML en document HTML. Voici le résultat obtenu avec Netscape 7 lorsqu'on charge le fichier XML simulations.xml :

Image

Lorsque nous regardons le code source du document (View/Source) nous retrouvons le document XML initial et non le document HTML affiché :

Image

Netscape a utilisé la feuille de style simulations.xsl pour transformer le document XML ci-dessus en document HTML affichable. Il est maintenant temps de regarder le contenu de cette feuille de style :

<?xml version="1.0" encoding="ISO-8859-1"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
        <html>
        <head>
            <title>Simulations de calculs d'impôts</title>
        </head>
        <body>
            <center>
            <h3>Simulations de calculs d'impôts</h3>
            <hr/>
            <table border="1">
                    <th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>
                        <xsl:apply-templates select="/simulations/simulation"/>
                        </table>
         </center>
       </body>
     </html>
        </xsl:template>


    <xsl:template match="simulation">
        <tr>
            <td><xsl:value-of select="@marie"/></td>
            <td><xsl:value-of select="@enfants"/></td>
            <td><xsl:value-of select="@salaire"/></td>
            <td><xsl:value-of select="@impot"/></td>
        </tr>
    </xsl:template>
  </xsl:stylesheet>
  • une feuille de style XSL est un fichier XML et en suit donc les règles. Il doit être en autres choses "bien formé" c'est à dire que toute balise ouverte doit être fermée.
  • le fichier commence par deux commandes XML qu'on pourra garder dans toute feuille de style XSL :
<?xml version="1.0" encoding="ISO-8859-1"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

L'attribut encoding="ISO-8859-1" permet d'utiliser les caractères accentués dans la feuille de style.

  • La balise <xsl:output method="html" indent="yes"/> indique à l'interpréteur XSL qu'on veut produire du HTML "indenté".
  • La balise <xsl:template match="élément"> sert à définir l'élément du document XML sur lequel vont s'appliquer les instructions que l'on va trouver entre <xsl:template ...> et </xsl:template>.
    <xsl:template match="/">
................
        </xsl:template>

Dans l'exemple ci-dessus l'élément "/" désigne la racine du document. Cela signifie que dès que le début du document XML va être rencontré, les commandes XSL situées entre les deux balises vont être exécutées.

  • Tout ce qui n'est pas balise XSL est mis tel quel dans le flux de sortie. Les balises XSL elles sont exécutées. Certaines d'entre-elles produisent un résultat qui est mis dans le flux de sortie. Etudions l'exemple suivant :
    <xsl:template match="/">
        <html>
        <head>
            <title>Simulations de calculs d'impôts</title>
        </head>
        <body>
            <center>
            <h3>Simulations de calculs d'impôts</h3>
            <hr/>
            <table border="1">
                    <th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>
                        <xsl:apply-templates select="/simulations/simulation"/>
                        </table>
          </center>
        </body>
      </html>
        </xsl:template>

Rappelons que le document XML analysé est le suivant :

<?xml version="1.0" encoding="ISO-8859-1"?>
<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
  <simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>

Dès le début du document XML analysé (match="/"), l'interpréteur XSL va produire en sortie le texte

<html>
        <head>
            <title>Simulations de calculs d'impôts</title>
        </head>
        <body>
            <center>
            <h3>Simulations de calculs d'impôts</h3>
            <hr>
            <table border="1">
                    <th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>

On remarquera que dans le texte initial on avait <hr/> et non pas <hr>. Dans le texte initial on ne pouvait pas écrire <hr> qui si elle est une balise HTML valide est une balise XML invalide. Or nous avons affaire ici à un texte XML qui doit être "bien formé", c.a.d que toute balise doit être fermée. On écrit donc <hr/> et parce qu'on a écrit <xsl:output text="html ...> l'interpréteur XSL transformera le texte <hr/> en <hr>. Derrière ce texte, viendra ensuite le texte produit par la commande XSL :

      <xsl:apply-templates select="/simulations/simulation"/>

Nous verrons ultérieurement quel est ce texte. Enfin l'interpréteur ajoutera le texte :

                    </table>
          </center>
        </body>
      </html>

La commande <xsl:apply-templates select="/simulations/simulation"/> demande qu'on exécute le "template" (modèle) de l'élément /simulations/simulation. Elle sera exécutée à chaque fois que l'interpréteur XSL rencontrera dans le texte XML analysé une balise <simulation>..</simulations> ou <simulation/> à l'intérieur d'une balise <simulations>..</simulations>. A la rencontre de la balise <simulation>, l'interpréteur exécutera les instructions du modèle suivant :

    <xsl:template match="simulation">
        <tr>
            <td><xsl:value-of select="@marie"/></td>
            <td><xsl:value-of select="@enfants"/></td>
            <td><xsl:value-of select="@salaire"/></td>
            <td><xsl:value-of select="@impot"/></td>
        </tr>
    </xsl:template>

Considérons les lignes XML suivantes :

<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>

La ligne <simulation ..> correspond au modèle de l'instruction XSL <xsl:apply-templates select="/simulations/simulation>". L'interpréteur XSL va donc chercher à lui appliquer les instructions qui correspondent à ce modèle. Il va trouver le modèle <xsl:template match="simulation"> et va l'exécuter. Rappelons que ce qui n'est pas une commande XSL est repris tel quel par l'interpréteur XSL et que les commandes XSL sont elles remplacées par le résultat de leur exécution. L'instruction XSL <xsl:value-of select="@champ"/> est ainsi remplacé par la valeur de l'attribut "champ" du noeud analysé (ici un noeud <simulation>). L'analyse de la ligne XML précédente va produire en sortie le résultat suivant :

XSL

sortie

<tr><td>

<tr><td>

<xsl:value-of select="@marie"/>

oui

</td><td>

</td><td>

<xsl:value-of select="@enfants"/>

2

</td><td>

</td><td>

<xsl:value-of select="@salaire"/>

200000

</td><td>

</td><td>

<xsl:value-of select="@impot"/>

22504

</td></tr>

</td></tr>

Au total, la ligne XML

<simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>

va être transformée en ligne HTML :

<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>

Toutes ces explications sont un peu rudimentaires mais il devrait apparaître maintenant au lecteur que le texte XML suivant :

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="simulations.xsl"?>
<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
  <simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>

accompagné de la feuille de style XSL simulations.xsl suivante :

<?xml version="1.0" encoding="ISO-8859-1"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
        <html>
        <head>
            <title>Simulations de calculs d'impôts</title>
        </head>
        <body>
            <center>
            <h3>Simulations de calculs d'impôts</h3>
            <hr/>
            <table border="1">
                    <th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>
                        <xsl:apply-templates select="/simulations/simulation"/>
                        </table>
         </center>
       </body>
     </html>
        </xsl:template>


    <xsl:template match="simulation">
        <tr>
            <td><xsl:value-of select="@marie"/></td>
            <td><xsl:value-of select="@enfants"/></td>
            <td><xsl:value-of select="@salaire"/></td>
            <td><xsl:value-of select="@impot"/></td>
        </tr>
    </xsl:template>
  </xsl:stylesheet>

produit le texte HTML suivant :

<html>
<head>
<title>Simulations de calculs d'impôts</title>
</head>
<body>
<center>
<h3>Simulations de calculs d'impots</h3>
<hr>
<table border="1">
<th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>
<tr>
<td>oui</td><td>2</td><td>200000</td><td>22504</td>
</tr>
<tr>
<td>non</td><td>2</td><td>200000</td><td>33388</td>
</tr>
</table>
</center>
</body>
</html>

Le fichier XML simulations.xml accompagné de la feuille de style simulations.xsl lu par un navigateur récent (ici Netscape 7) est alors affiché comme suit :

Image

6.2. Application impôts : version 6

6.2.1. Les fichiers XML et feuilles de style XSL de l'application impôts

Revenons à l'application web impôts et modifions la afin que la réponse faite aux clients soit une réponse au format XML plutôt qu'une réponse HTML. Cette réponse XML sera accompagnée d'une feuille de style XSL afin que les navigateurs puissent l'afficher. Dans le paragraphe précédent, nous avons présenté :

  • le fichier simulations.xml qui est le prototype d'une réponse XML comportant des simulations de calculs d'impôts
  • le fichier simulations.xsl qui sera la feuille de style XSL qui accompagnera cette réponse XML

Il nous faut prévoir également le cas de la réponse avec des erreurs. Le prototype de la réponse XML dans ce cas sera le fichier erreurs.xml suivant :

<?xml version="1.0" encoding="windows-1252"?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl"?>
<erreurs>
    <erreur>erreur 1</erreur>
  <erreur>erreur 2</erreur>
</erreurs>

La feuille de style erreurs.xsl permettant d'afficher ce document XML dans un navigateur sera la suivante :

<?xml version="1.0" encoding="windows-1252"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
        <html>
        <head>
            <title>Simulations de calculs d'impôts</title>
        </head>
        <body>
            <center>
            <h3>Simulations de calculs d'impôts</h3>
          </center>            
          <hr/>
          Les erreurs suivantes se sont produites :
          <ul>
                <xsl:apply-templates select="/erreurs/erreur"/>
            </ul>            
        </body>
      </html>
        </xsl:template>

    <xsl:template match="erreur">
        <li><xsl:value-of select="."/></li>
        </xsl:template>
</xsl:stylesheet>    

Cette feuille de style introduit une commande XSL non encore rencontrée : <xsl:value-of select="."/>. Cette commande produit en sortie la valeur du noeud analysé, ici un noeud <erreur>texte</erreur>. La valeur de ce noeud est le texte compris entre les deux balises d'ouverture et de fermeture, ici texte.

Le code erreurs.xml est transformé par la feuille de style erreurs.xsl en document HTML suivant :

<html>
<head>
<title>Simulations de calculs d'impots</title>
</head>
<body>
<center>
<h3>Simulations de calculs d'impots</h3>
</center>
<hr>
          Les erreurs suivantes se sont produites :
<ul>
<li>erreur 1</li>
<li>erreur 2</li>
</ul>
</body>
</html>

Le fichier erreurs.xml accompagné de sa feuille de style est affiché par un navigateur de la façon suivante :

Image

6.2.2. La servlet xmlsimulations

Nous créons un fichier index.html que nous mettons dans le répertoire de l'application impots. La page visualisée est la suivante :

Image

Ce document HTML est un document statique. Son code est le suivant :

<html>
    <head>
    <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
        // raz du formulaire
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//with
      }//effacer

      function calculer(){
        // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // c'est bon - on envoie
          submit();
        }//with
      }//calculer        
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
    <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/xmlsimulations" method="POST">
        <table>
            <tr>
            <td>Etes-vous marié(e)</td>
            <td>
                <input type="radio" name="optMarie" value="oui">oui
              <input type="radio" name="optMarie" value="non" checked>non
            </td>
          </tr>
          <tr>
            <td>Nombre d'enfants</td>
            <td><input type="text" size="3" name="txtEnfants" value=""></td>
          </tr>
          <tr>
            <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value=""></td>
          </tr>
          <tr></tr>
          <tr>
            <td><input type="button" value="Calculer" onclick="calculer()"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
 </body>
</html>

On notera que les données du formulaire sont postées à l'URL /impots/xmlsimulations. Cette application est une servlet Java configurée de la façon suivante dans le fichier web.xml de l'application impots :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
...........
    <servlet>
    <servlet-name>xmlsimulations</servlet-name>
    <servlet-class>xmlsimulations</servlet-class>
    <init-param>
        <param-name>xslSimulations</param-name>
        <param-value>simulations.xsl</param-value>
    </init-param>
    <init-param>
        <param-name>xslErreurs</param-name>
        <param-value>erreurs.xsl</param-value>
    </init-param>

    <init-param>
        <param-name>DSNimpots</param-name>
        <param-value>mysql-dbimpots</param-value>
    </init-param>
    <init-param>
        <param-name>admimpots</param-name>
        <param-value>admimpots</param-value>
    </init-param>
    <init-param>
        <param-name>mdpimpots</param-name>
        <param-value>mdpimpots</param-value>
    </init-param>    
  </servlet>
........
  <servlet-mapping>
    <servlet-name>xmlsimulations</servlet-name>
    <url-pattern>/xmlsimulations</url-pattern>
  </servlet-mapping>
</web-app>
  • la servlet s'appelle xmlsimulations et s'appuie sur la classe xmlsimulations.class.
  • elle a pour paramètres les paramètres DSNimpots, admimpots, mdpimpots nécessaires pour accéder à la base de données des impôts. Par ailleurs, elle admet deux autres paramètres :
    • xslSimulations qui est le nom du fichier de style qui doit accompagner la réponse XML contenant les simulations
    • xslErreurs qui est le nom du fichier de style qui doit accompagner la réponse XML contenant les éventuelles erreurs
  • elle a un alias xmlsimulations qui la rend accessible via l'URL http://localhost:8080/impots/xmlsimulations.

Le squelette de la servlet xmlsimulations est semblable à celui de la servlet simulations déjà étudiée. La principale différence vient du fait qu'elle doit générer du XML au lieu du HTML. Cela va entraîner la suppression des fichiers JSP utilisés dans les applications précédentes. Leur rôle principal était d'améliorer la lisibilité du code HTML généré en évitant que celui-ci soit noyé dans le code Java de la servlet. Ce rôle n'a maintenant plus lieu d'être. La servlet a deux types de code XML à générer :

  • celui pour les simulations
  • celui pour les erreurs

Nous avons précédemment présenté et étudié les deux types de réponse XML à fournir dans ces deux cas ainsi que les feuilles de style qui doivent les accompagner. Le code de la servlet est le suivant :

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;

public class xmlsimulations extends HttpServlet{

    // variables d'instance
    String msgErreur=null;
    String xslSimulations=null;
    String xslErreurs=null;
    String DSNimpots=null;
    String admimpots=null;
    String mdpimpots=null;
    impotsJDBC impots=null;

    //-------- GET
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{

        // on récupère le flux d'écriture vers le client
        PrintWriter out=response.getWriter();

        // on précise le type de la réponse
        response.setContentType("text/xml");

        // la liste des erreurs
        ArrayList erreurs=new ArrayList();

        // l'initialisation s'est-elle bien passée ?
        if(msgErreur!=null){
            // c'est fini - on envoie la réponse avec erreurs au serveur
            erreurs.add(msgErreur);
            sendErreurs(out,xslErreurs,erreurs);
            // c'est fini
            return;
        }

        // on récupère les simulations précédentes de la session
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();

        // on récupère les paramètres de la requête courante
        String optMarie=request.getParameter("optMarie");              // statut marital
        String txtEnfants=request.getParameter("txtEnfants");       // nbre d'enfants
        String txtSalaire=request.getParameter("txtSalaire");       // salaire annuel

        // a-t-on tous les paramètres attendus
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
            // il manque des paramètres
            // on envoie la réponse avec erreurs
            erreurs.add("Demande incomplète. Il manque des paramètres");
            sendErreurs(out,xslErreurs,erreurs);
            // c'est fini
            return;
        }

        // on a tous les paramètres - on les vérifie
        // état marital
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
            // erreur
            erreurs.add("Etat marital incorrect");
        }
        // nombre d'enfants
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // erreur
            erreurs.add("Nombre d'enfants incorrect");
        }
        // salaire
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // erreur
            erreurs.add("Salaire incorrect");
        }

        if(erreurs.size()!=0){
            // s'il y a des erreurs, on les signale
            sendErreurs(out,xslErreurs,erreurs);
        }else{
            // pas d'erreurs
            try{
                // on peut calculer l'impôt à payer
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                String txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                // on ajoute le résultat courant aux simulations précédentes
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                // on envoie la réponse avec simulations
                sendSimulations(out,xslSimulations,simulations);
            }catch(Exception ex){}
        }//if-else
        // on remet la liste des simulations dans la session
        session.setAttribute("simulations",simulations);
    }//GET

    //-------- POST
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
        doGet(request,response);
    }//POST

    //-------- INIT
    public void init(){
        // on récupère les paramètres d'initialisation
        ServletConfig config=getServletConfig();
        xslSimulations=config.getInitParameter("xslSimulations");
        xslErreurs=config.getInitParameter("xslErreurs");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

        // paramètres ok ?
        if(xslSimulations==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

        // on crée une instance d'impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }//init

    //-------- sendErreurs
    private void sendErreurs(PrintWriter out,String xslErreurs,ArrayList erreurs){
        String réponse="<?xml version=\"1.0\" encoding=\"windows-1252\"?>"
                    + "<?xml-stylesheet type=\"text/xsl\" href=\""+xslErreurs+"\"?>\n"
              +"<erreurs>\n";
        for(int i=0;i<erreurs.size();i++){
            réponse+="<erreur>"+(String)erreurs.get(i)+"</erreur>\n";
        }//for
        réponse+="</erreurs>\n";
        // on envoie la réponse
        out.println(réponse);
    }

    //-------- sendSimulations
    private void sendSimulations(PrintWriter out, String xslSimulations, ArrayList simulations){
        String réponse="<?xml version=\"1.0\" encoding=\"windows-1252\"?>"
                    + "<?xml-stylesheet type=\"text/xsl\" href=\""+xslSimulations+"\"?>\n"
              + "<simulations>\n";
        String[] simulation=null;
        for(int i=0;i<simulations.size();i++){
            // simulation n° i
            simulation=(String[])simulations.get(i);
            réponse+="<simulation "
                     +"marie=\""+(String)simulation[0]+"\" "
                     +"enfants=\""+(String)simulation[1]+"\" "
                     +"salaire=\""+(String)simulation[2]+"\" "
                 +"impot=\""+(String)simulation[3]+"\" />\n";
        }//for
        réponse+="</simulations>\n";
        // on envoie la réponse
        out.println(réponse);
    }
}

Détaillons les principales nouveautés de ce code par rapport à ce que nous connaissions déjà :

  • la procédure init récupère de nouveaux paramètres dans le fichier de configuration web.xml : les noms des deux feuilles de style XSL qui doivent accompagner la réponse sont placés dans les variables xslSimulations et xslErreurs. Ces deux feuilles de style sont les fichiers simulations.xsl et erreurs.xsl étudiés précédemment. Ceux-ci sont placés dans le répertoire de l'application impots :
dos>dir E:\data\serge\Servlets\impots\*.xsl
27/08/2002  08:15                1 030 simulations.xsl
27/08/2002  09:23                  795 erreurs.xsl
  • la procédure GET commence par regarder s'il y a eu une erreur lors de l'initialisation. Si oui, elle appelle la procédure sendErreurs qui génère la réponse XML adaptée à ce cas puis s'arrête. Dans cette réponse XML est insérée l'instruction désignant la feuille de style à utiliser.
  • s'il n'y a pas eu d'erreurs, la procédure GET analyse les paramètres de la requête du client. Si elle trouve une erreur quelconque elle le signale en utilisant là aussi la procédure sendErreurs. Sinon, elle calcule la nouvelle simulation, l'ajoute aux anciennes mémorisées dans la session courante et termine en envoyant sa réponse XML via la procédure sendSimulations. Cette dernière procède de façon analogue à la procédure sendErreurs.
  • on remarquera que la servlet annonce sa réponse comme étant de type text/xml :
        // on précise le type de la réponse
        response.setContentType("text/xml");

Voici des exemples d'exécution. Le formulaire de départ est rempli de la façon suivante :

Image

La base de données MySQL n'a pas été lancée rendant impossible la construction de l'objet impots dans la procédure init de la servlet. La réponse de celle-ci est alors la suivante :

Image

Le code reçu par le navigateur (View/Source) est le suivant :

Image

Si maintenant on refait deux simulations après avoir lancé la base de données MySQL, on obtient le résultat suivant :

Image

Le navigateur a cette fois reçu le code suivant :

Image

On remarquera que notre nouvelle application est plus simple qu'auparavant du fait de la suppression des fichiers JSP. Une partie du travail fait par ces pages a été transférée sur les feuilles de style XSL. L'intérêt de notre nouvelle répartition des tâches est qu'une fois le format XML des réponses de la servlet a été fixé, le développement des feuilles de style est indépendant de celui de la servlet.

6.3. Analyse d'un document XML en Java

Les versions 7 et 8 de notre application impots vont être des clients programmés de la servlet précédente xmlsimulations. Ceux-ci vont recevoir du code XML qu'ils vont devoir analyser pour en retirer les informations qui les intéressent. Nous allons faire ici une pause dans nos différentes versions et apprendre comment on peut analyser un document XML en Java. Nous le ferons à partir d'un exemple livré avec JBuilder 7 appelé MySaxParser. Le programme s'appelle de la façon suivante :

dos>java MySaxParser
Usage: java MySaxParser [URI]

L'application MySaxParser admet un paramètre : l'URI (Uniform Resource Identifier) du document XML à analyser. Dans notre exemple, cette URI sera simplement le nom d'un fichier XML placé dans le répertoire de l'application MySaxParser. Considérons deux exemples d'exécution. Dans le premier exemple, le fichier XML analysé est le fichier erreurs.xml :

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl"?>
<erreurs>
    <erreur>erreur 1</erreur>
  <erreur>erreur 2</erreur>
</erreurs>

L'analyse donne les résultats suivants :

dos> java MySaxParser erreurs.xml
Début du document
Début élément <erreurs>
Début élément <erreur>
[erreur 1]
Fin élément <erreur>
Début élément <erreur>
[erreur 2]
Fin élément <erreur>
Fin élément <erreurs>
Fin du document

Nous n'avions pas encore indiqué ce que faisait l'application MySaxParser mais l'on voit ici qu'elle affiche la structure du document XML analysé. Le second exemple analyse le fichier XML simulations.xml :

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="simulations.xsl"?>
<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
  <simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>

L'analyse donne les résultats suivants :

dos>java MySaxParser simulations.xml
Début du document
Début élément <simulations>
Début élément <simulation>
marie = oui
enfants = 2
salaire = 200000
impot = 22504
Fin élément <simulation>
Début élément <simulation>
marie = non
enfants = 2
salaire = 200000
impot = 33388
Fin élément <simulation>
Fin élément <simulations>
Fin du document

La classe MySaxParser contient tout ce dont nous avons besoin dans notre application impots puisqu'elle a été capable de retrouver soit les erreurs soit les simulations que pourrait envoyer le serveur web. Examinons son code :

import java.io.IOException;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.apache.xerces.parsers.SAXParser;
import java.util.regex.*;

// la classe
public class MySaxParser extends DefaultHandler {

    // valeur d'un élement de l'arbre XML
    private StringBuffer valeur=new StringBuffer();
    // une expression régulière de la valeur d'un élément lorsqu'on veut ignorer
    // les "blancs" qui la précèdent ou la suivent
    private static Pattern ptnValeur=null;
    private static Matcher résultats=null;

    // -------- main
    public static void main(String[] argv) {
        // vérification du nombre de paramètres
        if (argv.length != 1) {
            System.out.println("Usage: java MySaxParser [URI]");
            System.exit(0);
        }
        // on récupère l'URI du fichier XML à analyser
        String uri = argv[0];
        try {
            // création d'un analyseur XML (parseur)
            XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
            // on indique au parseur l'objet qui va implémenter les méthodes
            // startDocument, endDocument, startElement, endElement, characters
            MySaxParser MySaxParserInstance = new MySaxParser();
            parser.setContentHandler(MySaxParserInstance);
            // on initialise le modèle de valeur d'un élément
            ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
            // on indique au parseur le document XML à analyser
            parser.parse(uri);
        }
        catch(Exception ex) {
            // erreur
            System.err.println("Erreur : " + ex);
            // trace
            ex.printStackTrace();
        }
    }//main


    // -------- startDocument
    public void startDocument() throws SAXException {
        // procédure appelée lorsque le parseur rencontre le début du document
        System.out.println("Début du document");
    }//startDocument

    // -------- endDocument
    public void endDocument() throws SAXException {
        // procédure appelée lorsque le parseur rencontre la fin du document

        System.out.println("Fin du document");
    }//endDocument

    // -------- startElement
    public void startElement(String uri, String localName, String qName,
        Attributes attributes) throws SAXException {
        // procédure appelée par le parseur lorsqu'il rencontre un début de balise
        // uri : URI du document analysé ?
        // localName : nom de l'élément en cours d'analyse
        // qName : idem mais "qualifié" par un espace de noms s'il y en a un
        // attributes : liste des attributs de l'éélment

        // suivi
        System.out.println("Début élément <"+localName+">");
        // l'élément a-t-il des attributs ?
        for (int i = 0; i < attributes.getLength(); i++) {
            System.out.println(attributes.getLocalName(i) + " = " + attributes.getValue(i));
        }//for
    }//startElement

    // -------- characters
    public void characters(char[] ch, int start, int length) throws SAXException {
        // procédure appelée de façon répétée par le parseur lorsqu'il rencontre du texte
        // entre deux balises <balise>texte</balise>
        // le texte est dans ch à partir du caractère start sur length caractères

        // le texte est ajouté au buffer valeur
        valeur.append(ch, start, length);
    }//characters

        // -------- endElement
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        // procédure appelée par le parseur lorsqu'il rencontre une fin de balise
        // uri : URI du document analysé ?
        // localName : nom de l'élément en cours d'analyse
        // qName : idem mais "qualifié" par un espace de noms s'il y en a un

        // on affiche la valeur de l'élément
        String strValeur=valeur.toString();
        if (ptnValeur==null) System.out.println("null");
        résultats=ptnValeur.matcher(strValeur);
        if (résultats.find() && ! résultats.group(1).equals("")){
            System.out.println("["+résultats.group(1)+"]");
        }//if
        // on met la valeur de l'élément à vide
        valeur.setLength(0);

        // suivi
        System.out.println("Fin élément <"+localName+">");
    }//endElement

}//classe

Définissons tout d'abord un sigle qui revient fréquemment dans l'analyse de documents XML : SAX qui signifie Simple Api for Xml. C'est un ensemble de classes java facilitant le travail avec les documents XML. Il y a deux versions d'API : SAX1 et SAX2. L'application ci-dessus utilise l'API SAX2.

L'application importe un certain nombre de paquetages :

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.apache.xerces.parsers.SAXParser;

Les deux premiers viennent avec le JDK 1.4, le troisième non. Le paquetage xerces.jar est disponible sur le site du serveur Web Apache. Il vient avec JBuilder 7 mais aussi avec Tomcat 4.x :

Image

Si donc on veut compiler l'application précédente en-dehors de JBuilder 7 et qu'on dispose du JDK 1.4 et de Tomcat 4.x on pourra écrire :

dos>javac -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\xerces.jar" MySaxParser.java

A l'exécution, on fera de même :

dos>java -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\xerces.jar" MySaxParser simulations.xml

La classe MySaxParser dérive de la classe DefaultHandler. Nous y reviendrons. Etudions le code de la procédure main :

        // on récupère l'URI du fichier XML à analyser
        String uri = argv[0];
        try {
            // création d'un analyseur XML (parseur)
            XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
            // on indique au parseur l'objet qui va implémenter les méthodes
            // startDocument, endDocument, startElement, endElement, characters
            MySaxParser MySaxParserInstance = new MySaxParser();
            parser.setContentHandler(MySaxParserInstance);
            // on initialise le modèle de valeur d'un élément
            ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
            // on indique au parseur le document XML à analyser
            parser.parse(uri);
        }
        catch(Exception ex) {
            // erreur
            System.err.println("Erreur : " + ex);
            // trace
            ex.printStackTrace();
        }

Pour analyser un document XML, notre application a besoin d'un analyseur de code XML appelé "parseur".

            XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");

Le parseur XML utilisé est celui fourni par le paquetage xerces.jar. L'objet récupéré est de type XMLReader. XMLReader est une interface dont nous utilisons ici deux méthodes :


void setContentHandler(ContentHandler handler)

indique au parseur l'objet de type ContentHandler qui va traiter les événements qu'il va générer lors de l'analyse du document XML


void parse(InputSource input)

démarre l'analyse du document XML passé en paramètre

Lorsque le parseur va analyser le document XML, il va émettre des événements tels que : "j'ai rencontré le début du document, le début d'une balise, un attribut de balise, le contenu d'une balise, la fin d'une balise, la fin du document, ...". Il transmet ces événements à l'objet ContentHandler qu'on lui a donné. ContentHandler est une interface qui définit les méthodes à implémenter pour traiter tous les événements que le parseur XML peut générer. DefaultHandler est une classe qui fait une implémentation par défaut de ces méthodes. Les méthodes implémentées dans DefaultHandler ne font rien mais elles existent. Lorsqu'on doit indiquer au parseur quel objet va traiter les événements qu'il va générer au moyen de l'instruction

void setContentHandler(ContentHandler handler)

il est alors pratique de passer en paramètre un objet de type DefaultHandler. Si on s'en tenait là, aucun événement du parseur ne serait traité mais notre programme serait syntaxiquement correct. Dans la pratique, on passe en paramètre au parseur, un objet dérivé de la classe DefaultHandler, dans lequel sont redéfinies les méthodes traitant les seuls événements qui nous intéressent. C'est ce qui est fait ici :

            // on indique au parseur l'objet qui va implémenter les méthodes
            // startDocument, endDocument, startElement, endElement, characters
            MySaxParser MySaxParserInstance = new MySaxParser();
            parser.setContentHandler(MySaxParserInstance);
            // on indique au parseur le document XML à analyser
            parser.parse(uri);

On passe au parseur un exemplaire de la classe mySaxParser qui est notre classe et qui a été définie plus haut par la déclaration

public class MySaxParser extends DefaultHandler {

et on lance l'analyse du document dont on a passé l'URI en paramètre. A partir de là, l'analyse du document XML commence. Le parseur émet des événements et pour chacun d'eux appelle une méthode précise de l'objet chargé de traiter ces événements, ici notre objet MySaxParser. Celui-ci traite cinq événements particuliers, les autres étant ignorés :

événement émis par le parseur

méthode de traitement


début du document  

void startDocument()


fin du document

void endDocument()


début d'un élément :<balise>

public void startElement(String uri, String localName, String qName, Attributes attributes)

uri : ?

localName : nom de l'élément analysé. Si l'élément rencontré est <simulations>, on aura localName="simulations".

qName : nom qualifié par un espace de noms de l'élément analysé. Un document XML peut définir un espace de noms, XX par exemple. Le nom qualifié de la balise précédente serait alors XX:simulations.

attributes : liste des attributs de la balise


valeur d'un élément :
<balise>valeur</balise>

public void characters(char[] ch, int start, int length)

ch : tableau de caractères

start : indice du 1er caractère à utiliser dans le tableau ch

length : nombre de caractères à prendre dans le tableau ch

La méthode characters peut être appelée de façon répétée. Pour construire la valeur d'un élément, on utilise alors un buffer qu'on :

  • vide au départ de l'élément
  • complète à chaque nouvel appel de la méthode characters
  • termine à la fin de l'élément

fin d'un élément : </balise> ou <balise .../>

void endElement(String uri, String localName, String qName)

les paramètres sont ceux de la méthode startElement.

La méthode startElement permet de récupérer les attributs de l'élément grâce au paramètre attributes de type Attributes :

  • le nombre d'attributs est disponible dans attributes.getLength()
  • le nom de l'attribut i est disponible dans attributes.getLocalName(i)
  • la valeur de l'attribut i est disponible dans attributes.getValue(i)
  • la valeur de l'attribut de nom localName dans attributes.getValue(localName)

Une fois ceci expliqué, le programme précédent accompagné des exemples d'exécution se comprend de lui-même. Une expression régulière a été utilisée pour récupérer les valeurs des éléments afin qu'un texte XML comme :

<erreur>
    erreur 1
</erreur>

donne pour valeur associée à la balise <erreur> le texte erreur 1 débarrassé des espaces et sauts de lignes qui pourraient le précéder et/ou le suivre.

6.4. Application impôts : version 7

Nous avons maintenant tous les éléments pour écrire des clients programmés pour notre service d'impôts qui délivre du XML. Nous reprenons la version 4 de notre application pour faire le client et nous gardons la version 6 pour ce qui est du serveur. Dans cette application client-serveur :

  • le service de simulations du calcul d'impôts est fait la servlet xmlsimulations. La réponse du serveur est donc au format XML comme nous l'avons vu dans la version 6.
  • le client n'est plus un navigateur mais un client java autonome. Son interface graphique est celle de la version 4.

Voici quelques exemples d'exécution. Tout d'abord un cas d'erreur : le client interroge la servlet xmlsimulations alors que celle-ci n'a pas pu s'initialiser correctement du fait que le SGBD MySQL n'était pas lancé :

Image

On lance MySQL et on fait quelques simulations :

Image

Le client de cette nouvelle version ne diffère du client de la version 4 que par la façon dont il traite la réponse du serveur. Rien d'autre ne change. Dans la version 4, le client recevait du code HTML dans lequel il allait chercher les informations qui l'intéressaient en utilisant des expressions régulières. Ici le client reçoit du code XML dans lequel il va récupérer les informations qui l'intéressent à l'aide d'un parseur XML.

Rappelons les grandes lignes de la procédure liée au menu Calculer de la version 4 de notre client puisque c'est principalement là que se font les changements :

  void mnuCalculer_actionPerformed(ActionEvent e) {
....
    try{
            // on calcule l'impôt
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
            // on affiche l'erreur
            JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
        }
....
    }//mnuCalculer_actionPerformed

    public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
          throws Exception{
        // calcul de l'impôt
        // urlImpots : URL du service des impôts
        // marié : true si marié, false sinon
        // nbEnfants : nombre d'enfants
        // salaire : salaire annuel

        // on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
....
        try{
            // on se connecte au serveur
....

            // on crée les flux d'entrée-sortie du client TCP
....

            // on demande l'URL - envoi des entêtes HTTP
....

            // on lit la 1ère ligne de la réponse
....
            // on lit la réponse jusqu'à la fin des entêtes en cherchant l'éventuel cookie
            while((réponse=IN.readLine())!=null){
....            }//while

            // c'est fini pour les entêtes HTTP - on passe au code HTML
            // pour récupérer les simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // c'est fini
....
    }//calculerImpots

    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
....
    }

Tout ce code reste valide dans la nouvelle version. Seul le traitement de la réponse HTML du serveur (partie encadrée ci-dessus) et son affichage doivent être remplacés par le traitement de la réponse XML du serveur et son affichage :

            // c'est fini pour les entêtes HTTP - on passe au code XML
            // pour récupérer les simulations ou les erreurs
            ImpotsSaxParser parseur=new ImpotsSaxParser(IN);
            ArrayList listeErreurs=parseur.getErreurs();
            ArrayList listeSimulations=parseur.getSimulations();
            // fermeture connexion au serveur
            client.close();

            // nettoyage liste d'affichage
            simulations.clear();
            // erreurs
            if(listeErreurs.size()!=0){
                // on concatène toutes les erreurs
                String msgErreur="Le serveur a signalé les erreurs suivantes :\n";
                for(int i=0;i<listeErreurs.size();i++){
                    msgErreur+=" - "+(String)listeErreurs.get(i);
                }
                // affichage erreurs
                throw new Exception(msgErreur);
            }//if

            // simulations
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }
            return;

Que fait la portion de code ci-dessus ?

  • elle crée un parseur XML et lui passe le flux IN qui contient le code XML envoyé par le serveur. Ce flux contenait aussi les entêtes HTTP mais ceux-ci ont déjà été lus et traités. Ne reste donc que la partie XML de la réponse. Le parseur produit deux listes de chaînes de caractères : la liste des erreurs s'il y en a eu, sinon celle des simulations. Ces deux listes sont exclusives l'une de l'autre.
  • si la liste des erreurs est non vide, les messages contenus dans la liste sont concaténés en un seul message d'erreur et une exception est lancée avec comme paramètre ce message. Cette exception fait l'objet d'un affichage dans la procédure mnuCalculer_actionPeformed qui a appelé calculerImpots.
  • si la liste des simulations est non vide, elle fait l'objet d'un affichage dans le composant jList de l'interface graphique.

Découvrons maintenant le parseur de la réponse XML du serveur, parseur qui découle directement de l'étude que nous avons faite précédemment sur la façon d'analyser un document XML en java :

import java.io.IOException;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.apache.xerces.parsers.SAXParser;
import java.util.regex.*;
import java.io.*;
import java.util.*;
import javax.swing.*;

// la classe
public class ImpotsSaxParser extends DefaultHandler {

    // valeur d'un élement de l'arbre XML
    private StringBuffer valeur=new StringBuffer();
    // une expression régulière de la valeur d'un élément lorsqu'on veut ignorer
    // les "blancs" qui la précèdent ou la suivent
    private Pattern ptnValeur=null;
    private Matcher résultats=null;
    // les listes d'éléments XML
    private ArrayList listeSimulations=new ArrayList();
    private ArrayList listeErreurs=new ArrayList();
    // éléments XML
    private ArrayList éléments=new ArrayList();
    String élément="";

    // -------- constructeur
    public ImpotsSaxParser(BufferedReader IN) throws Exception{
        // création d'un analyseur XML (parseur)
        XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        // on indique au parseur l'objet qui va implémenter les méthodes
        // startDocument, endDocument, startElement, endElement, characters
        parser.setContentHandler(this);
        // on initialise le modèle de valeur d'un élément
        ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
        // au départ pas d'élément XML courant
        éléments.add("");
        // on analyse le document
        parser.parse(new InputSource(IN));
    }//constructeur

    // -------- startElement
    public void startElement(String uri, String localName, String qName,
        Attributes attributes) throws SAXException {
        // procédure appelée par le parseur lorsqu'il rencontre un début de balise
        // uri : URI du document analysé ?
        // localName : nom de l'élément en cours d'analyse
        // qName : idem mais "qualifié" par un espace de noms s'il y en a un
        // attributes : liste des attributs de l'éélment

        // on note le nom de l'élément
        élément=localName.toLowerCase();
        éléments.add(élément);
        // l'élément a-t-il des attributs ?
        if(élément.equals("simulation") && attributes.getLength()==4){
            // c'est une simulation - on récupère les attributs
            String simulation=attributes.getValue("marie")+","+
                                         attributes.getValue("enfants")+","+
                                         attributes.getValue("salaire")+","+
                                         attributes.getValue("impot");
            // on ajoute la simulation à la liste des simulations
            listeSimulations.add(simulation);
        }//if
    }//startElement

    // -------- characters
    public void characters(char[] ch, int start, int length) throws SAXException {
        // procédure appelée de façon répétée par le parseur lorsqu'il rencontre du texte
        // entre deux balises <balise>texte</balise>
        // le texte est dans ch à partir du caractère start sur length caractères

        // le texte est ajouté au buffer valeur s'il s'agit de l'élément erreur
        if (élément.equals("erreur"))
           valeur.append(ch, start, length);
    }//characters

        // -------- endElement
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        // procédure appelée par le parseur lorsqu'il rencontre une fin de balise
        // uri : URI du document analysé ?
        // localName : nom de l'élément en cours d'analyse
        // qName : idem mais "qualifié" par un espace de noms s'il y en a un

        // cas de l'erreur
        if(élément.equals("erreur")){
            // on récupère la valeur de l'élément erreur
            String strValeur=valeur.toString();
            // on la débarrasse de ses "blancs" inutiles et on l'enregistre dans la liste des
            // erreurs si elle est non vide
            résultats=ptnValeur.matcher(strValeur);
            if (résultats.find() && ! résultats.group(1).equals("")){
                listeErreurs.add(résultats.group(1));
            }//if
        }
        // on met la valeur de l'élément à vide
        valeur.setLength(0);
        // on réinitialise le nom de l'élément
        éléments.remove(éléments.size()-1);
        élément=(String)éléments.get(éléments.size()-1);
    }//endElement

    // --------- getErreurs
    public ArrayList getErreurs(){
        return listeErreurs;
    }

    // --------- getSimulations
    public ArrayList getSimulations(){
        return listeSimulations;
    }

}//classe
  • le constructeur reçoit le flux XML IN à analyser et fait immédiatement cette analyse. Celle-ci terminée, l'objet a été construit ainsi que les listes (ArrayList) d'erreurs (listeErreurs) et de simulations (listeSimulations) ont été construites. Il ne reste plus à la procédure qui a construit l'objet qu'à récupérer les deux listes à l'aide des méthodes getErreurs et getSimulations.
  • seuls trois événements générés par le parseur XML nous intéressent ici :
    • début d'un élément XML, événement qui sera traité par la procédure startElement. Celle-ci aura à traiter les balises <simulation marie=".." enfants=".." salaire=".." impot=".."> et <erreur>...</erreur>.
    • valeur d'un élément XML, événement qui sera traité par la procédure characters.
    • fin d'un élément XML, événement qui sera traité par la procédure endElement.
  • dans la procédure startElement, si on a affaire à l'élément <simulation marie=".." enfants=".." salaire=".." impot=".."> on récupère les quatre attributs par attributes.getValue("nom de l'attribut"). Dans tous les cas, on mémorise le nom de l'élément dans une variable élément et on l'ajoute à une liste (ArrayList) d'éléments : élé1 élé2 ... élén. Cette liste est gérée comme une pile dont le dernier élément est l'élément XML en cours d'analyse. Lorsqu'on a l'événement "fin d'élément" le dernier élément de la liste est retiré et le nouvel élément courant est modifié. Ceci est fait dans la procédure endElement.
  • la procédure characters est identique à celle qui avait été étudiée dans un exemple précédent. On prend simplement soin de vérifier que l'élément courant est bien l'élément <erreur>, précaution inutile normalement ici. Ce type de précaution avait été également pris dans la procédure startElement pour vérifier qu'on avait affaire à un élément <simulation>.

6.5. Conclusion

Grâce à sa réponse XML l'application impots est devenue plus facile à gérer à la fois pour son concepteur et les concepteurs des application clientes.

  • la conception de l'application serveur peut maintenant être confiée à deux types de personnes : le développeur Java de la servlet et l'infographiste qui va gérer l'apparence de la réponse du serveur dans les navigateurs. Il suffit à ce dernier de connaître la structure de la réponse XML du serveur pour construire les feuilles de style qui vont l'accompagner. Rappelons que celles-ci font l'objet de fichiers XSL à part et indépendants de la servlet java. L'infographiste peut donc travailler indépendamment du développeur Java.
  • les concepteurs des applications clientes ont eux aussi simplement besoin de connaître la structure de la réponse XML du serveur. Les modifications que pourrait apporter l'infographiste aux feuilles de style n'ont aucune répercussion sur cette réponse XML qui reste toujours la même. C'est un énorme avantage.
  • Comment le développeur peut-il faire évoluer sa servlet Java sans tout casser ? Tout d'abord, tant que sa réponse XML ne change pas, il peut organiser sa servlet comme il le veut. Il peut aussi faire évoluer la réponse XML tant qu'il garde les éléments <erreur> et <simulation> attendus par ses clients. Il peut ainsi ajouter de nouvelles balises à cette réponse. L'infographiste les prendra en compte dans ses feuilles de style et les navigateurs pourront avoir les nouvelles versions de la réponse. Les clients programmés eux continueront à fonctionner avec l'ancien modèle, les nouvelles balises étant tout simplement ignorées. Pour que cela soit possible, il faut que dans l'analyse XML de la réponse du serveur, les balises cherchées soient bien identifiées. C'est ce qui a été fait dans notre client XML de l'application impôts où dans les procédures on disait spécifiquement qu'on traitait les balises <erreur> et <simulation>. Du coup les autres balises sont ignorées.