Skip to content

6. XML i JAVA

W niniejszym rozdziale przedstawiamy sposób korzystania z dokumentów XML w języku Java. Zrobimy to w kontekście aplikacji podatkowej omówionej w poprzednim rozdziale.

6.1. Pliki XML i arkusze stylów XSL

Rozważmy następujący plik XML simulations.xml, który mógłby przedstawiać wyniki symulacji obliczeń podatkowych:

<?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>

Jeśli wyświetlimy to za pomocą IE 6, otrzymamy następujący wynik:

Image

IE6 rozpoznaje, że ma do czynienia z plikiem XML (dzięki rozszerzeniu .xml pliku) i formatuje go na swój własny sposób. W przeglądarce Netscape wyświetla się pusta strona. Jednak po przejrzeniu kodu źródłowego (View/Source) widoczny jest oryginalny plik XML:

Image

Dlaczego Netscape nic nie wyświetla? Ponieważ potrzebuje arkusza stylów, który wskaże mu, jak przekształcić plik XML w plik HTML, który będzie mógł następnie wyświetlić. Okazuje się, że plik IE 6 posiada domyślny arkusz stylów, podczas gdy plik XML go nie zawiera, co miało miejsce w tym przypadku.

Istnieje język o nazwie XSL (eXtended StyleSheet Language), umożliwiający opisanie transformacji niezbędnych do przekształcenia pliku XML w dowolny plik tekstowy. XSL pozwala na stosowanie wielu instrukcji i bardzo przypomina języki programowania. Nie będziemy go tutaj szczegółowo omawiać, ponieważ zajęłoby to kilkadziesiąt stron. Przedstawimy jedynie dwa przykłady arkuszy stylów XSL. Pierwszy z nich służy do przekształcenia pliku XML simulations.xml w kod HTML. Modyfikujemy ten ostatni tak, aby wskazywał arkusz stylów, z którego przeglądarki będą mogły skorzystać w celu przekształcenia go w dokument HTML, który będą mogły wyświetlić:

<?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>

Zlecenie XML

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

oznacza plik simulations.xsl jako arkusz stylów (xml-stylesheet) typu text/xsl c.a.d. plik tekstowy zawierający kod XSL. Ten arkusz stylów zostanie wykorzystany przez przeglądarki do przekształcenia tekstu XML w dokument HTML. Oto wynik uzyskany w przeglądarce Netscape 7 po załadowaniu pliku XML simulations.xml:

Image

Gdy przyjrzymy się kodowi źródłowemu dokumentu (View/Source), widzimy pierwotny dokument XML, a nie wyświetlany dokument HTML:

Image

Netscape wykorzystał arkusz stylów simulations.xsl do przekształcenia powyższego dokumentu XML w dokument HTML nadający się do wyświetlenia. Nadszedł czas, aby przyjrzeć się zawartości tego arkusza stylów:

<?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>
  • Arkusz stylów XSL jest plikiem typu XML i w związku z tym podlega tym samym zasadom. Musi być między innymi „poprawnie sformułowany”, co oznacza, że każdy otwarty tag musi zostać zamknięty.
  • Plik rozpoczyna się od dwóch poleceń XML, które można zachować w każdym arkuszu stylów XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

Atrybut encoding="ISO-8859-1" umożliwia stosowanie znaków akcentowanych w arkuszu stylów.

  • Tag <xsl:output method="html" indent="yes"/> informuje interpreter XSL, że chcemy wygenerować „wcięty” kod HTML.
  • Tag <xsl:template match="element"> służy do zdefiniowania elementu dokumentu XML, do którego będą miały zastosowanie instrukcje znajdujące się pomiędzy tagami <xsl:template ...> i </xsl:template>.
    <xsl:template match="/">
................
        </xsl:template>

W powyższym przykładzie element „/” oznacza katalog główny dokumentu. Oznacza to, że gdy tylko zostanie napotkany początek dokumentu XML, polecenia XSL znajdujące się pomiędzy tymi dwoma znacznikami zostaną wykonane.

  • Wszystko, co nie jest znacznikiem XSL, jest umieszczane w strumieniu wyjściowym bez zmian. Znaczniki XSL są natomiast wykonywane. Niektóre z nich generują wynik, który jest umieszczany w strumieniu wyjściowym. Przyjrzyjmy się następującemu przykładowi:
    <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>

Przypomnijmy, że analizowany dokument XML to:

<?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>

Od początku analizowanego dokumentu XML (match="/") interpreter XSL wygeneruje tekst

<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>

Warto zauważyć, że w tekście wyjściowym występował tag <hr/>, a nie <hr>. W tekście wyjściowym nie można było wpisać <hr>, który – choć jest poprawnym tagiem HTML – jest nieprawidłowym tagiem XML. Mamy tu jednak do czynienia z tekstem XML, który musi być „poprawnie sformułowany”, co oznacza, że każdy tag musi być zamknięty. Piszemy więc <hr/>, a ponieważ napisaliśmy <xsl:output text="html ...>>, interpreter XSL przekształci tekst <hr/> w <hr>. Po tym tekście pojawi się tekst wygenerowany przez polecenie XSL:

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

Później zobaczymy, co to za tekst. Na koniec interpreter doda tekst:

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

Polecenie <xsl:apply-templates select="/simulations/simulation"/> nakazuje wykonanie „szablonu” (modelu) elementu /simulations/simulation. Zostanie on wykonany za każdym razem, gdy interpreter XSL napotka w analizowanym tekście XML tag <simulation>..</simulations> lub <simulation/> wewnątrz tagu <simulations>..</simulations>. Po napotkaniu znacznika <simulation> interpreter wykona instrukcje zawarte w następującym szablonie:

    <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>

Rozważmy następujące wiersze XML:

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

Wiersz <simulation ..> odpowiada szablonowi instrukcji XSL <xsl:apply-templates select="/simulations/simulation>". Interpreter XSL będzie zatem próbował zastosować do niego instrukcje odpowiadające temu szablonowi. Znajdzie szablon <xsl:template match="simulation"> i wykona go. Przypomnijmy, że elementy, które nie są poleceniami XSL, są przejmowane bez zmian przez interpreter XSL, natomiast polecenia XSL są zastępowane wynikiem ich wykonania. Instrukcja XSL <xsl:value-of select="@champ"/> zostaje zatem zastąpiona wartością atrybutu „champ” analizowanego węzła (w tym przypadku węzła <simulation>). Analiza poprzedniego wiersza XML da następujący wynik:

XSL
wynik
<tr><td>
<tr><td>
<xsl:value-of select="@marie"/>
tak
</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="@podatek"/>
22504
</td></tr>
</td></tr>

W sumie wiersz XML

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

zostanie przekształcona w wiersz HTML:

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

Wszystkie te wyjaśnienia są nieco uproszczone, ale czytelnikowi powinno być już jasne, że następujący tekst 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>

wraz z następującym arkuszem stylów XSL simulations.xsl:

<?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>

generuje następujący tekst: HTML:

<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>

Plik XML simulations.xml wraz z arkuszem stylów simulations.xsl wyświetlany w nowszej przeglądarce (w tym przypadku Netscape 7) wygląda następująco:

Image

6.2. Aplikacja podatkowa: wersja 6

6.2.1. Pliki XML i arkusze stylów XSL aplikacji podatkowej

Wróćmy do aplikacji internetowej dotyczącej podatków i zmodyfikujmy ją tak, aby odpowiedź wysyłana do klientów miała format XML zamiast HTML. Do tej odpowiedzi XML dołączony zostanie arkusz stylów XSL, aby przeglądarki mogły ją wyświetlić. W poprzednim akapicie przedstawiliśmy:

  • plik simulations.xml, który jest prototypem odpowiedzi XML zawierającej symulacje obliczeń podatkowych
  • plik simulations.xsl, który będzie arkuszem stylów XSL dołączonym do tej odpowiedzi XML

Należy również uwzględnić przypadek odpowiedzi zawierającej błędy. Prototypem odpowiedzi XML w tym przypadku będzie następujący plik erreurs.xml:

<?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>

Arkusz stylów erreurs.xsl umożliwiający wyświetlenie tego dokumentu XML w przeglądarce będzie wyglądał następująco:

<?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>    

Ten arkusz stylów wprowadza nieznane dotąd polecenie XSL: <xsl:value-of select="."/>. Polecenie to generuje na wyjściu wartość analizowanego węzła, w tym przypadku węzła <erreur>texte</erreur>. Wartością tego węzła jest tekst zawarty między dwoma tagami otwierającym i zamykającym, w tym przypadku texte.

Kod erreurs.xml jest przekształcany przez arkusz stylów erreurs.xsl w następujący dokument HTML:

<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>

Plik erreurs.xml wraz z arkuszem stylów jest wyświetlany przez przeglądarkę w następujący sposób:

Image

6.2.2. Serwlet xmlsimulations

Tworzymy plik index.html, który umieszczamy w katalogu aplikacji impots. Wyświetlana strona wygląda następująco:

Image

Ten dokument HTML jest dokumentem statycznym. Jego kod wygląda następująco:

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

      function calculer(){
           // weryfikacja parametrów przed wysłaniem ich na serwer
        with(document.frmImpots){
          //liczba dzieci
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // szablon nie został zweryfikowany
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //wynagrodzenie
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // szablon nie został zweryfikowany
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//jeśli
          // w porządku – wysyłamy
          submit();
        }//z
      }//obliczyć        
        </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>

Należy zauważyć, że dane z formularza są przesyłane do aplikacji URL /impots/xmlsimulations. Aplikacja ta jest serwletem Java skonfigurowanym w następujący sposób w pliku web.xml aplikacji 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>
  • Servlet nosi nazwę xmlsimulations i opiera się na klasie xmlsimulations.class.
  • Jej parametrami są: DSNimpots, admimpots oraz mdpimpots, niezbędne do uzyskania dostępu do bazy danych podatków. Ponadto obsługuje ona dwa dodatkowe parametry:
    • xslSimulations, czyli nazwa pliku stylu, który musi towarzyszyć odpowiedzi XML zawierającej symulacje
    • xslErreurs – nazwa pliku stylu, który musi towarzyszyć odpowiedzi XML zawierającej ewentualne błędy
  • posiada alias xmlsimulations, dzięki czemu jest dostępna poprzez URL http://localhost:8080/impots/xmlsimulations.

Szkielet serwletu xmlsimulations jest podobny do szkieletu serwletu simulations, który już omówiliśmy. Główna różnica polega na tym, że musi ona generować kod XML zamiast HTML. Spowoduje to usunięcie plików JSP wykorzystywanych w poprzednich aplikacjach. Ich głównym zadaniem było poprawienie czytelności generowanego kodu HTML poprzez uniknięcie jego zagubienia w kodzie Java serwletu. Ta rola nie ma już obecnie racji bytu. Serwlet musi wygenerować dwa rodzaje kodu XML:

  • kod do symulacji
  • kod dotyczący błędów

Wcześniej przedstawiliśmy i przeanalizowaliśmy oba rodzaje odpowiedzi XML, które należy dostarczyć w tych dwóch przypadkach, a także arkusze stylów, które powinny im towarzyszyć. Kod serwletu wygląda następująco:

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

public class xmlsimulations extends HttpServlet{

    // zmienne instancji
    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{

        // pobieramy strumień zapisu do klienta
        PrintWriter out=response.getWriter();

        // określamy typ odpowiedzi
        response.setContentType("text/xml");

         // lista błędów
        ArrayList erreurs=new ArrayList();

         // czy inicjalizacja przebiegła pomyślnie?
        if(msgErreur!=null){
             // zakończono – wysyłamy odpowiedź z błędami do serwera
            erreurs.add(msgErreur);
            sendErreurs(out,xslErreurs,erreurs);
             // zakończono
            return;
        }

         // pobieramy poprzednie symulacje z sesji
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();

        // pobieramy parametry bieżącego zapytania
        String optMarie=request.getParameter("optMarie");              // stan cywilny
        String txtEnfants=request.getParameter("txtEnfants");       // liczba dzieci
        String txtSalaire=request.getParameter("txtSalaire");       // roczne wynagrodzenie

         // czy są wszystkie oczekiwane parametry
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // brakuje parametrów
             // wysyłamy odpowiedź z błędami
            erreurs.add("Demande incomplète. Il manque des paramètres");
            sendErreurs(out,xslErreurs,erreurs);
             // zakończono
            return;
        }

         // mamy wszystkie parametry – sprawdzamy je
         // stan cywilny
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // błąd
            erreurs.add("Etat marital incorrect");
        }
         // liczba dzieci
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // błąd
            erreurs.add("Nombre d'enfants incorrect");
        }
         // wynagrodzenie
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // błąd
            erreurs.add("Salaire incorrect");
        }

        if(erreurs.size()!=0){
            // jeśli występują błędy, są one zgłaszane
            sendErreurs(out,xslErreurs,erreurs);
        }else{
             // brak błędów
            try{
                 // można obliczyć podatek do zapłaty
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                String txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // dodajemy bieżący wynik do poprzednich symulacji
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // wysyła się odpowiedź wraz z symulacjami
                sendSimulations(out,xslSimulations,simulations);
            }catch(Exception ex){}
        }//if-else
         // przekazujemy listę symulacji do sesji
        session.setAttribute("simulations",simulations);
    }//GET

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

     //-------- INIT
    public void init(){
         // odzyskuje się parametry inicjalizacyjne
        ServletConfig config=getServletConfig();
        xslSimulations=config.getInitParameter("xslSimulations");
        xslErreurs=config.getInitParameter("xslErreurs");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parametry w porządku?
        if(xslSimulations==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

         // tworzymy instancję impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }//inicjalizacja

     //-------- 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";
         // wysyłamy odpowiedź
        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++){
            // symulacja nr 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";
        }//dla
        réponse+="</simulations>\n";
         // wysyłamy odpowiedź
        out.println(réponse);
    }
}

Przyjrzyjmy się bliżej głównym zmianom w tym kodzie w porównaniu z tym, co znaliśmy dotychczas:

  • procedura init pobiera nowe parametry z pliku konfiguracyjnego web.xml: nazwy dwóch arkuszy stylów XSL, które muszą towarzyszyć odpowiedzi, są umieszczane w zmiennych xslSimulations i xslErreurs. Te dwa arkusze stylów to pliki simulations.xsl i erreurs.xsl, które omówiliśmy wcześniej. Znajdują się one w katalogu aplikacji 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
  • Procedura GET rozpoczyna się od sprawdzenia, czy podczas inicjalizacji wystąpił błąd. Jeśli tak, wywołuje procedurę sendErreurs, która generuje odpowiedź XML dostosowaną do tego przypadku, a następnie kończy działanie. W tej odpowiedzi XML umieszczana jest instrukcja wskazująca arkusz stylów, który ma zostać użyty.
  • Jeśli nie wystąpiły żadne błędy, procedura GET analizuje parametry żądania klienta. Jeśli wykryje jakikolwiek błąd, zgłasza go, również korzystając z procedury sendErreurs. W przeciwnym razie oblicza nową symulację, dodaje ją do poprzednich zapisanych w bieżącej sesji i kończy wysyłając swoją odpowiedź XML za pośrednictwem procedury sendSimulations. Ta ostatnia działa analogicznie do procedury sendErreurs.
  • Należy zauważyć, że serwlet zgłasza swoją odpowiedź jako typu text/xml:
         // określa się typ odpowiedzi
        response.setContentType("text/xml");

Oto przykłady działania. Formularz wyjściowy jest wypełniony w następujący sposób:

Image

Baza danych MySQL nie została uruchomiona, co uniemożliwiło utworzenie obiektu impots w procedurze init serwletu. Odpowiedź serwletu wygląda zatem następująco:

Image

Kod otrzymany przez przeglądarkę (View/Source) wygląda następująco:

Image

Jeśli teraz przeprowadzimy dwie symulacje po uruchomieniu bazy danych MySQL, otrzymamy następujący wynik:

Image

Tym razem przeglądarka otrzymała następujący kod:

Image

Warto zauważyć, że nasza nowa aplikacja jest prostsza niż poprzednio dzięki usunięciu plików JSP. Część zadań wykonywanych przez te strony została przeniesiona do arkuszy stylów XSL. Zaletą naszego nowego podziału zadań jest to, że po ustaleniu formatu odpowiedzi serwletu XML tworzenie arkuszy stylów przebiega niezależnie od rozwoju serwletu.

6.3. Analiza dokumentu XML w języku Java

Wersje 7 i 8 naszej aplikacji podatkowej będą klientami zaprogramowanymi dla poprzedniej serwletu xmlsimulations. Będą one otrzymywać kod XML, który będą musiały przeanalizować w celu wyodrębnienia interesujących je informacji. Zrobimy teraz przerwę w omawianiu poszczególnych wersji i nauczymy się, jak analizować dokument XML w Javie. Zrobimy to na podstawie przykładu dostarczonego wraz z wersją JBuilder 7, o nazwie MySaxParser. Program nazywa się następująco:

dos>java MySaxParser
Usage: java MySaxParser [URI]

Aplikacja MySaxParser przyjmuje jeden parametr: identyfikator URI (Uniform Resource Identifier) dokumentu XML, który ma zostać przeanalizowany. W naszym przykładzie ten identyfikator URI będzie po prostu nazwą pliku XML umieszczonego w katalogu aplikacji MySaxParser. Rozważmy dwa przykłady działania. W pierwszym przykładzie analizowanym plikiem XML jest plik 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>

Analiza daje następujące wyniki:

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

Nie wyjaśniliśmy jeszcze, do czego służy aplikacja MySaxParser, ale widać tutaj, że wyświetla ona strukturę przeanalizowanego dokumentu XML. W drugim przykładzie analizowany jest plik 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>

Analiza daje następujące wyniki:

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

Klasa MySaxParser zawiera wszystko, czego potrzebujemy w naszej aplikacji podatkowej, ponieważ jest w stanie wykryć zarówno błędy, jak i symulacje, które może wysłać serwer WWW. Przyjrzyjmy się jej kodowi:

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

// klasa
public class MySaxParser extends DefaultHandler {

     // wartość elementu drzewa XML
    private StringBuffer valeur=new StringBuffer();
    // wyrażenie regularne wartości elementu, który ma zostać pominięty
     // „spacje” poprzedzające lub następujące po niej
    private static Pattern ptnValeur=null;
    private static Matcher résultats=null;

    // -------- main
    public static void main(String[] argv) {
        // sprawdzanie liczby parametrów
        if (argv.length != 1) {
            System.out.println("Usage: java MySaxParser [URI]");
            System.exit(0);
        }
         // pobieramy URI z pliku XML do analizy
        String uri = argv[0];
        try {
            // tworzenie analizatora XML (parser)
            XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
             // określamy dla parsera obiekt, który będzie implementował metody
             // startDocument, endDocument, startElement, endElement, znaków
            MySaxParser MySaxParserInstance = new MySaxParser();
            parser.setContentHandler(MySaxParserInstance);
             // inicjuje się model wartości elementu
            ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
             // wskazuje się parserowi dokument XML do analizy
            parser.parse(uri);
        }
        catch(Exception ex) {
             // błąd
            System.err.println("Erreur : " + ex);
             // wyświetlenie
            ex.printStackTrace();
        }
    }//main


     // -------- startDocument
    public void startDocument() throws SAXException {
         // procedura wywoływana, gdy parser napotka początek dokumentu
        System.out.println("Début du document");
    }//startDocument

     // -------- endDocument
    public void endDocument() throws SAXException {
         // procedura wywoływana, gdy parser napotka koniec dokumentu

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

     // -------- startElement
    public void startElement(String uri, String localName, String qName,
        Attributes attributes) throws SAXException {
         // procedura wywoływana przez parser po napotkaniu początku tagu
         // URI: URI analizowanego dokumentu?
         // localName: nazwa analizowanego elementu
         // qName: to samo, ale „określone” przez przestrzeń nazw, jeśli taka istnieje
         // attributes: lista atrybutów elementu

         // kontynuacja
        System.out.println("Début élément <"+localName+">");
         // czy element posiada atrybuty?
        for (int i = 0; i < attributes.getLength(); i++) {
            System.out.println(attributes.getLocalName(i) + " = " + attributes.getValue(i));
        }//dla
    }//startElement

    // -------- znaki
    public void characters(char[] ch, int start, int length) throws SAXException {
        // procedura wywoływana wielokrotnie przez parser po napotkaniu tekstu
         // między dwoma tagami <tag>tekst</tag>
         // tekst znajduje się w ch, począwszy od znaku start, na długości length znaków

         // tekst jest dodawany do bufora o wartości
        valeur.append(ch, start, length);
    }//znaków

        // -------- endElement
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
         // procedura wywoływana przez parser po napotkaniu końca tagu
         // URI: URI analizowanego dokumentu?
         // localName: nazwa analizowanego elementu
         // qName: to samo, ale „określone” przez przestrzeń nazw, jeśli taka istnieje

         // wyświetlana jest wartość elementu
        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
         // ustawia się wartość elementu na pustą
        valeur.setLength(0);

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

}//klasa

Na początek zdefiniujmy skrót, który często pojawia się podczas analizy dokumentów XML: SAX, co oznacza „Simple API for XML”. Jest to zbiór klas Java ułatwiających pracę z dokumentami XML. Istnieją dwie wersje API: SAX1 i SAX2. Powyższa aplikacja korzysta z API i SAX2.

Aplikacja importuje pewną liczbę pakietów:

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

Dwa pierwsze są dostarczane wraz z pakietem JDK 1.4, trzeci natomiast nie. Pakiet xerces.jar jest dostępny na stronie serwera WWW Apache. Jest on dostarczany wraz z JBuilder 7, ale także z Tomcatem 4.x:

Image

Jeśli więc chcemy skompilować poprzednią aplikację poza pakietem JBuilder 7 i dysponujemy pakietem JDK 1.4 oraz Tomcatem 4.x, możemy napisać:

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

Podczas uruchamiania należy postąpić analogicznie:

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

Klasa MySaxParser wywodzi się z klasy DefaultHandler. Wrócimy do tego później. Przyjrzyjmy się kodowi procedury main:

         // pobieramy plik URI z pliku XML do analizy
        String uri = argv[0];
        try {
            // tworzenie analizatora XML (parser)
            XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
             // określamy dla parsera obiekt, który będzie implementował metody
             // startDocument, endDocument, startElement, endElement, znaków
            MySaxParser MySaxParserInstance = new MySaxParser();
            parser.setContentHandler(MySaxParserInstance);
             // inicjuje się model wartości elementu
            ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
             // wskazuje się parserowi dokument XML do analizy
            parser.parse(uri);
        }
        catch(Exception ex) {
             // błąd
            System.err.println("Erreur : " + ex);
             // ślad
            ex.printStackTrace();
        }

Aby przeanalizować dokument XML, nasza aplikacja potrzebuje analizatora kodu XML zwanego „parserem”.

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

Wykorzystywany parser XML pochodzi z pakietu xerces.jar. Pobierany obiekt jest typu XMLReader. XMLReader to interfejs, z którego korzystamy tutaj z dwóch metod:

void setContentHandler(ContentHandler handler)
wskazuje parserowi obiekt typu ContentHandler, który będzie przetwarzał zdarzenia generowane przez niego podczas analizy dokumentu XML
void parse(InputSource input)
rozpoczyna analizę dokumentu XML przekazanego jako parametr

Gdy parser będzie analizował dokument XML, wygeneruje takie zdarzenia jak: „napotkałem początek dokumentu, początek tagu, atrybut tagu, zawartość tagu, koniec tagu, koniec dokumentu...”. Przekazuje te zdarzenia do obiektu ContentHandler, który został mu przekazany. ContentHandler to interfejs definiujący metody, które należy zaimplementować w celu przetworzenia wszystkich zdarzeń, jakie może generować parser XML. DefaultHandler to klasa, która zapewnia domyślną implementację tych metod. Metody zaimplementowane w DefaultHandler nie wykonują żadnych działań, ale istnieją. Gdy trzeba wskazać parserowi, który obiekt będzie przetwarzał generowane przez niego zdarzenia, należy użyć instrukcji

void setContentHandler(ContentHandler handler)

, wygodnym rozwiązaniem jest przekazanie jako parametru obiektu typu DefaultHandler. Gdybyśmy na tym poprzestali, żadne zdarzenie parsera nie zostałoby przetworzone, ale nasz program byłby poprawny pod względem składniowym. W praktyce jako parametr do parsera przekazuje się obiekt pochodny od klasy DefaultHandler, w którym przedefiniowano metody obsługujące wyłącznie te zdarzenia, które nas interesują. Tak właśnie zrobiono w tym przypadku:

             // wskazuje się parserowi obiekt, który będzie implementował metody
             // startDocument, endDocument, startElement, endElement, znaki
            MySaxParser MySaxParserInstance = new MySaxParser();
            parser.setContentHandler(MySaxParserInstance);
             // wskazuje się parserowi dokument XML do analizy
            parser.parse(uri);

Przekazujemy do parsera instancję klasy mySaxParser, która jest naszą klasą i została zdefiniowana powyżej za pomocą deklaracji

public class MySaxParser extends DefaultHandler {

i uruchamiamy analizę dokumentu, którego obiekt URI przekazaliśmy jako parametr. W tym momencie rozpoczyna się analiza dokumentu XML. Parser generuje zdarzenia i dla każdego z nich wywołuje konkretną metodę obiektu odpowiedzialnego za obsługę tych zdarzeń, w tym przypadku naszego obiektu MySaxParser. Obiekt ten obsługuje pięć konkretnych zdarzeń, pozostałe są ignorowane:

zdarzenie wygenerowane przez parser
metoda przetwarzania
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: nazwa analizowanego elementu. Jeśli napotkanym elementem jest <simulations>, otrzymamy localName="simulations".
qName: nazwa analizowanego elementu w przestrzeni nazw. Dokument XML może definiować przestrzeń nazw, na przykład XX. W takim przypadku kwalifikowana nazwa poprzedniego tagu brzmiałaby: XX:simulations.
attributes: lista atrybutów znacznika
valeur d'un élément :
<balise>valeur</balise>
public void characters(char[] ch, int start, int length)
ch: tablica znaków
start: indeks pierwszego znaku do wykorzystania w tablicy ch
length: liczba znaków, które mają zostać pobrane z tablicy ch
Metodę characters można wywoływać wielokrotnie. Aby utworzyć wartość elementu, używa się bufora, który:
  • na początku elementu jest pusty
  • uzupełnia przy każdym nowym wywołaniu metody characters
  • zamyka na końcu elementu
fin d'un élément : </balise> ou <balise .../>
void endElement(String uri, String localName, String qName)
parametry są takie same jak w metodzie startElement.

Metoda startElement pozwala pobrać atrybuty elementu za pomocą parametru attributes typu Attributes:

  • liczba atrybutów jest dostępna w metodzie attributes.getLength()
  • nazwa atrybutu i jest dostępna w attributes.getLocalName(i)
  • wartość atrybutu i jest dostępna w attributes.getValue(i)
  • wartość atrybutu o nazwie localName znajduje się w attributes.getValue(localName)

Po tym wyjaśnieniu poprzedni program wraz z przykładami wykonania jest zrozumiały sam w sobie. Do pobrania wartości elementów użyto wyrażenia regularnego, dzięki czemu tekst XML, taki jak:

<erreur>
    erreur 1
</erreur>

dawał jako wartość powiązaną z tagiem <erreur> tekst „błąd 1” pozbawiony spacji i znaków końca linii, które mogłyby go poprzedzać i/lub następować po nim.

6.4. Aplikacja podatkowa: wersja 7

Mamy teraz wszystkie elementy potrzebne do napisania klientów zaprogramowanych dla naszej usługi podatkowej, która generuje kod XML. Wykorzystujemy wersję 4 naszej aplikacji do stworzenia klienta, a wersję 6 zachowujemy dla serwera. W tej aplikacji klient-serwer:

  • usługę symulacji obliczeń podatkowych realizuje serwlet xmlsimulations. Odpowiedź serwera ma zatem format XML, tak jak widzieliśmy w wersji 6.
  • klientem nie jest już przeglądarka, lecz samodzielna aplikacja Java. Jej interfejs graficzny pochodzi z wersji 4.

Oto kilka przykładów działania. Najpierw przypadek błędu: klient wysyła zapytanie do serwletu xmlsimulations, podczas gdy ten nie mógł się poprawnie zainicjować, ponieważ nie uruchomiono serwletów SGBD i MySQL:

Image

Uruchamiamy MySQL i przeprowadzamy kilka symulacji:

Image

Klient tej nowej wersji różni się od klienta wersji 4 jedynie sposobem przetwarzania odpowiedzi serwera. Nic więcej się nie zmienia. W wersji 4 klient otrzymywał kod HTML, z którego wyszukiwał interesujące go informacje za pomocą wyrażeń regularnych. W tym przypadku klient otrzymuje kod XML, z którego pobiera interesujące go informacje za pomocą parsera XML.

Przypomnijmy główne założenia procedury związanej z menu „Oblicz” w wersji 4 naszego klienta, ponieważ to właśnie tam zachodzą główne zmiany:

  void mnuCalculer_actionPerformed(ActionEvent e) {
....
    try{
             // oblicza się podatek
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // wyświetla się błąd
            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{
         // obliczanie podatku
         // urlImpots: URL z urzędu skarbowego
         // stan cywilny: true, jeśli jest żonaty/zamężna, false w przeciwnym razie
         // nbEnfants: liczba dzieci
         // wynagrodzenie: roczne wynagrodzenie

         // z urlImpots pobierane  informacje niezbędne do połączenia z serwerem urzędu skarbowego
....
        try{
             // nawiązuje się połączenie z serwerem
....

             // tworzy się przepływy przychodów i rozchodów klienta TCP
....

             // wysyłamy żądanie URL  wysyłamy nagłówki HTTP
....

             // odczytuje się pierwszy wiersz odpowiedzi
....
             // odczytuje się odpowiedź  do końca nagłówków, szukając ewentualnego pliku cookie
            while((réponse=IN.readLine())!=null){
....            }//while

            // kończy się sekcja nagłówków HTTP  przechodzimy do kodu HTML
             // w celu pobrania symulacji
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // to już koniec
....
    }//calculerImpots

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

Cały ten kod pozostaje aktualny w nowej wersji. Jedynie sposób przetwarzania odpowiedzi serwera HTML (część zaznaczona ramką powyżej) oraz jej wyświetlanie należy zastąpić sposobem przetwarzania odpowiedzi serwera XML i jej wyświetlaniem:

             // koniec z nagłówkami HTTP  przechodzimy do kodu XML
             // w celu pobrania symulacji lub błędów
            ImpotsSaxParser parseur=new ImpotsSaxParser(IN);
            ArrayList listeErreurs=parseur.getErreurs();
            ArrayList listeSimulations=parseur.getSimulations();
             // zamknięcie połączenia z serwerem
            client.close();

             // oczyszczenie listy wyświetlania
            simulations.clear();
             // błędy
            if(listeErreurs.size()!=0){
                 // łączymy wszystkie błędy
                String msgErreur="Le serveur a signalé les erreurs suivantes :\n";
                for(int i=0;i<listeErreurs.size();i++){
                    msgErreur+=" - "+(String)listeErreurs.get(i);
                }
                 // wyświetlanie błędów
                throw new Exception(msgErreur);
            }//if

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

Co robi powyższy fragment kodu?

  • Tworzy parser o nazwie XML i przekazuje mu strumień IN, który zawiera kod XML wysłany przez serwer. Strumień ten zawierał również nagłówki HTTP, ale zostały one już odczytane i przetworzone. Pozostaje więc tylko część odpowiedzi oznaczona jako XML. Parser generuje dwie listy ciągów znaków: listę błędów, jeśli takie wystąpiły, a w przeciwnym razie listę symulacji. Te dwie listy wykluczają się wzajemnie.
  • Jeśli lista błędów nie jest pusta, komunikaty zawarte na liście są łączone w jeden komunikat o błędzie i zgłaszany jest wyjątek z tym komunikatem jako parametrem. Wyjątek ten jest wyświetlany w procedurze mnuCalculer_actionPeformed, która wywołała procedurę calculerImpots.
  • Jeśli lista symulacji nie jest pusta, jest ona wyświetlana w komponencie jList interfejsu graficznego.

Przyjrzyjmy się teraz parserowi odpowiedzi XML z serwera, który wynika bezpośrednio z przeprowadzonej wcześniej analizy sposobu analizowania dokumentu XML w języku 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.*;

// klasa
public class ImpotsSaxParser extends DefaultHandler {

    // wartość elementu drzewa XML
    private StringBuffer valeur=new StringBuffer();
    // wyrażenie regularne wartości elementu, gdy chcemy pominąć
     // spacje poprzedzające lub następujące po niej
    private Pattern ptnValeur=null;
    private Matcher résultats=null;
     // listy elementów XML
    private ArrayList listeSimulations=new ArrayList();
    private ArrayList listeErreurs=new ArrayList();
     // elementy XML
    private ArrayList éléments=new ArrayList();
    String élément="";

     // -------- producent
    public ImpotsSaxParser(BufferedReader IN) throws Exception{
        // utworzenie analizatora XML (parser)
        XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
         // określamy dla parsera obiekt, który będzie implementował metody
         // startDocument, endDocument, startElement, endElement, znaków
        parser.setContentHandler(this);
        // inicjuje się wartość domyślną elementu
        ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
         // na początku nie ma bieżącego elementu XML
        éléments.add("");
         // analizowany jest dokument
        parser.parse(new InputSource(IN));
    }//konstruktor

     // -------- startElement
    public void startElement(String uri, String localName, String qName,
        Attributes attributes) throws SAXException {
         // procedura wywoływana przez parser po napotkaniu początku tagu
         // URI: URI analizowanego dokumentu?
         // localName: nazwa analizowanego elementu
         // qName: to samo, ale określone przez przestrzeń nazw, jeśli taka istnieje
         // attributes: lista atrybutów elementu

         // podano nazwę elementu
        élément=localName.toLowerCase();
        éléments.add(élément);
         // czy element posiada atrybuty?
        if(élément.equals("simulation") && attributes.getLength()==4){
            // jest to symulacja  pobieramy atrybuty
            String simulation=attributes.getValue("marie")+","+
                                         attributes.getValue("enfants")+","+
                                         attributes.getValue("salaire")+","+
                                         attributes.getValue("impot");
             // dodajemy symulację do listy symulacji
            listeSimulations.add(simulation);
        }//if
    }//startElement

    // -------- znaki
    public void characters(char[] ch, int start, int length) throws SAXException {
        // procedura wywoływana wielokrotnie przez parser po napotkaniu tekstu
         // między dwoma tagami <tag>tekst</tag>
         // tekst znajduje się w ch, począwszy od znaku start, na długości length znaków

         // tekst jest dodawany do bufora o wartości s, jeśli jest to element błędu
        if (élément.equals("erreur"))
           valeur.append(ch, start, length);
    }//znaków

        // -------- endElement
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
         // procedura wywoływana przez parser po napotkaniu końca tagu
         // URI: URI analizowanego dokumentu?
         // localName: nazwa analizowanego elementu
         // qName: to samo, ale określone przez przestrzeń nazw, jeśli taka istnieje

         // przypadek błędu
        if(élément.equals("erreur")){
             // pobieramy wartość elementu błędu
            String strValeur=valeur.toString();
             // usuwamy z niej zbędne spacje i zapisujemy  na liście
             // błędów, jeśli lista ta nie jest pusta
            résultats=ptnValeur.matcher(strValeur);
            if (résultats.find() && ! résultats.group(1).equals("")){
                listeErreurs.add(résultats.group(1));
            }//if
        }
         // ustawia się wartość elementu na pustą
        valeur.setLength(0);
         // resetujemy nazwę elementu
        é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;
    }

}//klasa
  • Konstruktor otrzymuje strumień XML IN do analizy i natychmiast przeprowadza tę analizę. Po zakończeniu analizy obiekt został utworzony, a także sporządzono listy (ArrayList) błędów (listeErreurs) oraz symulacji (listeSimulations). Teraz procedura, która utworzyła obiekt, musi jedynie pobrać te dwie listy za pomocą metod getErreurs i getSimulations.
  • W tym przypadku interesują nas tylko trzy zdarzenia wygenerowane przez parser XML:
    • początek elementu XML – zdarzenie to zostanie przetworzone przez procedurę startElement. Procedura ta będzie musiała przetworzyć tagi <simulation marie=".." enfants=".." salaire=".." impot=".."> oraz <erreur>...</erreur>.
    • wartość elementu XML – zdarzenie to zostanie przetworzone przez procedurę characters.
    • koniec elementu XML – zdarzenie to zostanie przetworzone przez procedurę endElement.
  • W procedurze startElement, jeśli mamy do czynienia z elementem <simulation marie=".." enfants=".." salaire=".." impot="..">, pobieramy cztery atrybuty za pomocą attributes.getValue("nazwa atrybutu"). W każdym przypadku nazwa elementu jest zapisywana w zmiennej „element” i dodawana do listy (ArrayList) elementów: el1, el2, ..., eln. Lista ta jest zarządzana jako stos, którego ostatnim elementem jest aktualnie analizowany element XML. Po wystąpieniu zdarzenia „koniec elementu” ostatni element listy jest usuwany, a nowy element bieżący jest modyfikowany. Odbywa się to w procedurze endElement.
  • Procedura characters jest identyczna z tą, którą omówiono w poprzednim przykładzie. Należy jedynie upewnić się, że bieżący element to rzeczywiście element <erreur>, co w tym przypadku jest zazwyczaj zbędnym środkiem ostrożności. Tego rodzaju środek ostrożności zastosowano również w procedurze startElement w celu sprawdzenia, czy mamy do czynienia z elementem <simulation>.

6.5. Wniosek

Dzięki odpowiedzi XML aplikacja impots stała się łatwiejsza w zarządzaniu zarówno dla jej twórcy, jak i dla twórców aplikacji klienckich.

  • Projektowanie aplikacji serwerowej można teraz powierzyć dwóm rodzajom osób: programiście Java odpowiedzialnemu za serwlet oraz grafikowi, który będzie zarządzał wyglądem odpowiedzi serwera w przeglądarkach. Wystarczy, że ten ostatni zna strukturę odpowiedzi serwera XML, aby stworzyć arkusze stylów, które będą jej towarzyszyć. Należy pamiętać, że arkusze te są przechowywane w oddzielnych plikach XSL, niezależnych od serwletu Java. Projektant graficzny może zatem pracować niezależnie od programisty Java.
  • Projektanci aplikacji klienckich również muszą jedynie znać strukturę odpowiedzi serwera XML. Zmiany, jakie grafik może wprowadzić w arkuszach stylów, nie mają żadnego wpływu na tę odpowiedź XML, która pozostaje zawsze taka sama. To ogromna zaleta.
  • W jaki sposób programista może rozbudowywać swój serwlet Java bez powodowania błędów? Po pierwsze, dopóki odpowiedź XML pozostaje niezmieniona, programista może zorganizować swój serwlet tak, jak chce. Może również modyfikować odpowiedź XML, o ile zachowa elementy <błąd> i <symulacja>, których oczekują jego klienci. W ten sposób może dodawać do tej odpowiedzi nowe tagi. Grafik uwzględni je w swoich arkuszach stylów, a przeglądarki będą mogły wyświetlać nowe wersje odpowiedzi. Natomiast zaprogramowani klienci będą nadal działać zgodnie ze starym modelem, a nowe tagi zostaną po prostu zignorowane. Aby było to możliwe, w analizie odpowiedzi serwera o numerze XML poszukiwane tagi muszą być prawidłowo zidentyfikowane. Tak właśnie zrobiono w naszym kliencie XML aplikacji podatkowej, gdzie w procedurach wyraźnie określono, że przetwarzane są tagi <erreur> i <simulation>. W rezultacie pozostałe tagi są ignorowane.