6. XML e Java
In questo capitolo, introduciamo l'uso dei documenti XML con Java. Lo faremo nel contesto dell'applicazione fiscale studiata nel capitolo precedente.
6.1. File XML e fogli di stile XSL
Consideriamo il seguente file XML, simulations.xml, che potrebbe rappresentare i risultati delle simulazioni di calcolo delle imposte:
<?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>
Se visualizzato con IE 6, si ottiene il seguente risultato:

IE6 riconosce che si tratta di un file XML (grazie all'estensione .xml del file) e lo formatta a modo suo. Con Netscape, si ottiene una pagina vuota. Tuttavia, se si guarda il codice sorgente (Visualizza/Sorgente), è possibile vedere il file XML originale:

Perché Netscape non visualizza nulla? Perché ha bisogno di un foglio di stile che gli indichi come trasformare il file XML in un file HTML che possa poi visualizzare. A quanto pare, IE 6 dispone di un foglio di stile predefinito quando il file XML non ne fornisce uno, come nel caso in questione.
Esiste un linguaggio chiamato XSL (eXtended StyleSheet Language) che consente di descrivere le trasformazioni necessarie per convertire un file XML in qualsiasi file di testo. XSL supporta numerose istruzioni e assomiglia molto ai linguaggi di programmazione. Non entreremo nei dettagli qui, poiché ciò richiederebbe decine di pagine. Descriveremo semplicemente due esempi di fogli di stile XSL. Il primo è quello che trasformerà il file XML simulations.xml in codice HTML. Modifichiamo quest'ultimo in modo che specifichi il foglio di stile che i browser possono utilizzare per trasformarlo in un documento HTML, che potranno poi visualizzare:
<?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>
Il comando XML
indica il file simulations.xsl come foglio di stile XML di tipo text/xsl, ovvero un file di testo contenente codice XSL. Questo foglio di stile verrà utilizzato dai browser per trasformare il testo XML in un documento HTML. Ecco il risultato ottenuto con Netscape 7 durante il caricamento del file XML simulations.xml:

Quando visualizziamo il codice sorgente del documento (Visualizza/Sorgente), vediamo il documento XML originale anziché il documento HTML visualizzato:

Netscape ha utilizzato il foglio di stile simulations.xsl per trasformare il documento XML sopra riportato in un documento HTML visualizzabile. È ora il momento di esaminare il contenuto di questo foglio di stile:
<?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>
- Un foglio di stile XSL è un file XML e quindi segue le regole XML. Tra le altre cose, deve essere "ben formato", il che significa che ogni tag di apertura deve essere chiuso.
- Il file inizia con due direttive XML che possono essere incluse in qualsiasi foglio di stile XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
L'attributo encoding="ISO-8859-1" consente l'uso di caratteri accentati nel foglio di stile.
- Il tag <xsl:output method="html" indent="yes"/> indica all'interprete XSL che si desidera generare codice HTML "indentato".
- Il tag <xsl:template match="element"> viene utilizzato per definire l'elemento nel documento XML a cui verranno applicate le istruzioni presenti tra <xsl:template ...> e </xsl:template>.
Nell'esempio sopra riportato, l'elemento "/" indica la radice del documento. Ciò significa che non appena viene individuato l'inizio del documento XML, verranno eseguiti i comandi XSL situati tra i due tag.
- Tutto ciò che non è un tag XSL viene incluso così com'è nel flusso di output. I tag XSL stessi vengono eseguiti. Alcuni di essi producono un risultato che viene incluso nel flusso di output. Esaminiamo il seguente esempio:
<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>
Si noti che il documento XML analizzato è il seguente:
<?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>
A partire dall'inizio del documento XML analizzato (match="/"), il processore XSL genererà il testo
<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>
Si noti che nel testo originale avevamo <hr/> e non <hr>. Nel testo originale non potevamo scrivere <hr>, che, pur essendo un tag HTML valido, è un tag XML non valido. Tuttavia, in questo caso abbiamo a che fare con un testo XML che deve essere "ben formato", il che significa che ogni tag deve essere chiuso. Scriviamo quindi <hr/> e, poiché abbiamo scritto <xsl:output text="html ...> il processore XSL trasformerà il testo <hr/> in <hr>. A seguire questo testo ci sarà il testo prodotto dal comando XSL:
Vedremo più avanti di cosa si tratta. Infine, l'interprete aggiungerà il testo:
La direttiva <xsl:apply-templates select="/simulations/simulation"/> indica al processore XSL di applicare il "modello" all'elemento /simulations/simulation. Verrà eseguita ogni volta che l'interprete XSL incontra un tag <simulation>..</simulations> o <simulation/> all'interno di un tag <simulations>..</simulations> nel testo XML analizzato. Quando incontra il tag <simulation>, l'interprete eseguirà le istruzioni del seguente modello:
<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>
Si considerino le seguenti righe XML:
La riga <simulation ..> corrisponde al modello per l'istruzione XSL <xsl:apply-templates select="/simulations/simulation">. L'interprete XSL tenterà quindi di applicare le istruzioni che corrispondono a questo modello. Troverà il modello <xsl:template match="simulation"> e lo eseguirà. Ricordiamo che tutto ciò che non è un comando XSL viene trasmesso senza modifiche dall'interprete XSL, mentre i comandi XSL vengono sostituiti dal risultato della loro esecuzione. L'istruzione XSL <xsl:value-of select="@champ"/> viene quindi sostituita dal valore dell'attributo "champ" del nodo analizzato (in questo caso, un nodo <simulation>). L'analisi della riga XML precedente produrrà il seguente output:
XSL | output |
<tr><td> | <tr><td> |
<xsl:value-of select="@marie"/> | sì |
</td><td> | </td><td> |
<xsl:value-of select="@children"/> | 2 |
</td><td> | </td><td> |
<xsl:value-of select="@salary"/> | 200000 |
</td><td> | </td><td> |
<xsl:value-of select="@tax"/> | 22504 |
</td></tr> | </td></tr> |
In totale, la riga XML
verrà convertito nella seguente riga HTML:
Tutte queste spiegazioni sono un po' rudimentali, ma ora dovrebbe essere chiaro al lettore che il seguente testo 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>
accompagnato dal seguente foglio di stile 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>
genera il seguente testo 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>
Il file XML simulations.xml, insieme al foglio di stile simulations.xsl, se visualizzato in un browser moderno (in questo caso, Netscape 7), viene visualizzato come segue:

6.2. Applicazione per il calcolo delle imposte: versione 6
6.2.1. I file XML e i fogli di stile XSL dell'applicazione per il calcolo delle imposte
Torniamo all'applicazione web per il calcolo delle imposte e modifichiamola in modo che la risposta inviata ai client sia in formato XML anziché HTML. Questa risposta XML sarà accompagnata da un foglio di stile XSL affinché i browser possano visualizzarla. Nella sezione precedente abbiamo presentato:
- il file simulations.xml, che è un prototipo di una risposta XML contenente simulazioni di calcolo delle imposte
- il file simulations.xsl, che sarà il foglio di stile XSL che accompagna questa risposta XML
Dobbiamo anche considerare il caso di una risposta contenente errori. Il prototipo per la risposta XML in questo caso sarà il seguente file errors.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>
Il foglio di stile errors.xsl utilizzato per visualizzare questo documento XML in un browser sarà il seguente:
<?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>
Questo foglio di stile introduce un comando XSL che non abbiamo ancora incontrato: <xsl:value-of select="."/>. Questo comando restituisce il valore del nodo analizzato, in questo caso un nodo <error>text</error>. Il valore di questo nodo è il testo compreso tra i tag di apertura e di chiusura, in questo caso "text".
Il codice errors.xml viene trasformato dal foglio di stile errors.xsl nel seguente documento 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>
Il file errors.xml, insieme al suo foglio di stile, viene visualizzato dal browser come segue:

6.2.2. Il servlet xmlsimulations
Creiamo un file index.html e lo inseriamo nella directory dell'applicazione impots. La pagina visualizzata è la seguente:

Questo documento HTML è un documento statico. Il suo codice è il seguente:
<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>
Si noti che i dati del modulo vengono inviati all'URL /impots/xmlsimulations. Questa applicazione è un servlet Java configurato come segue nel file web.xml dell'applicazione 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>
- Il servlet si chiama xmlsimulations ed è basato su xmlsimulations.class.
- I suoi parametri sono DSNimpots, admimpots e mdpimpots, necessari per accedere al database fiscale. Inoltre, accetta altri due parametri:
- xslSimulations, che è il nome del file del foglio di stile che deve accompagnare la risposta XML contenente le simulazioni
- xslErrors, che è il nome del foglio di stile che deve accompagnare la risposta XML contenente eventuali errori
- Ha un alias, xmlsimulations, che lo rende accessibile tramite l'URL http://localhost:8080/impots/xmlsimulations.
Lo scheletro del servlet xmlsimulations è simile a quello del servlet simulations già discusso. La differenza principale è che deve generare XML invece di HTML. Ciò comporterà la rimozione dei file JSP utilizzati nelle applicazioni precedenti. Il loro ruolo principale era quello di migliorare la leggibilità del codice HTML generato, evitando che fosse sepolto all'interno del codice Java del servlet. Questo ruolo non è più necessario. Il servlet ha due tipi di codice XML da generare:
- uno per le simulazioni
- uno per gli errori
In precedenza abbiamo presentato ed esaminato i due tipi di risposte XML da fornire in questi due casi, nonché i fogli di stile che devono accompagnarli. Il codice del servlet è il seguente:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;
public class xmlsimulations extends HttpServlet{
// instance variables
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{
// retrieve the write stream to the client
PrintWriter out=response.getWriter();
// specify the type of response
response.setContentType("text/xml");
// error list
ArrayList erreurs=new ArrayList();
// was the initialization successful?
if(msgErreur!=null){
// that's it - we send the response with errors to the server
erreurs.add(msgErreur);
sendErreurs(out,xslErreurs,erreurs);
// it's over
return;
}
// retrieve previous simulations from the session
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// retrieve the parameters of the current query
String optMarie=request.getParameter("optMarie"); // marital status
String txtEnfants=request.getParameter("txtEnfants"); // no. of children
String txtSalaire=request.getParameter("txtSalaire"); // annual salary
// do we have all the expected parameters
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// missing parameters
// send response with errors
erreurs.add("Demande incomplète. Il manque des paramètres");
sendErreurs(out,xslErreurs,erreurs);
// it's over
return;
}
// we have all the parameters - we check them
// marital status
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// error
erreurs.add("Etat marital incorrect");
}
// number of children
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// error
erreurs.add("Nombre d'enfants incorrect");
}
// salary
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// error
erreurs.add("Salaire incorrect");
}
if(erreurs.size()!=0){
// if there are errors, we report them
sendErreurs(out,xslErreurs,erreurs);
}else{
// no errors
try{
// you can calculate the tax payable
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
String txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// the current result is added to the previous simulations
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// we send the answer with simulations
sendSimulations(out,xslSimulations,simulations);
}catch(Exception ex){}
}//if-else
// we put the list of simulations back into the 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(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
xslSimulations=config.getInitParameter("xslSimulations");
xslErreurs=config.getInitParameter("xslErreurs");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parameters ok?
if(xslSimulations==null || DSNimpots==null || admimpots==null || mdpimpots==null){
msgErreur="Configuration incorrecte";
return;
}
// create an instance of 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";
// we send the answer
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 no. 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";
// we send the answer
out.println(réponse);
}
}
Analizziamo le principali novità di questo codice rispetto a ciò che già conoscevamo:
- La procedura init recupera nuovi parametri dal file di configurazione web.xml: i nomi dei due fogli di stile XSL che devono accompagnare la risposta sono memorizzati nelle variabili xslSimulations e xslErrors. Questi due fogli di stile sono i file simulations.xsl ed errors.xsl discussi in precedenza. Si trovano nella directory dell'applicazione 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 procedura GET inizia verificando se si è verificato un errore durante l'inizializzazione. In tal caso, chiama la procedura sendErrors, che genera la risposta XML appropriata per quel caso e poi termina. La risposta XML include un tag che specifica il foglio di stile da utilizzare.
- Se non si sono verificati errori, la procedura GET analizza i parametri della richiesta del client. Se rileva un errore, lo segnala utilizzando la procedura sendErrors. Altrimenti, calcola la nuova simulazione, la aggiunge a quelle precedenti memorizzate nella sessione corrente e termina inviando la sua risposta XML tramite la procedura sendSimulations. Quest'ultima procede in modo analogo alla procedura sendErrors.
- Si noti che il servlet dichiara la propria risposta come di tipo text/xml:
Ecco alcuni esempi di esecuzione. Il modulo iniziale viene compilato come segue:

Il database MySQL non è stato avviato, rendendo impossibile la creazione dell'oggetto impots nella procedura init del servlet. La risposta del servlet è quindi la seguente:

Il codice ricevuto dal browser (Visualizza/Sorgente) è il seguente:

Se ora eseguiamo altre due simulazioni dopo aver avviato il database MySQL, otteniamo il seguente risultato:

Questa volta, il browser ha ricevuto il seguente codice:

Si noti che la nostra nuova applicazione è più semplice rispetto a prima grazie alla rimozione dei file JSP. Parte del lavoro precedentemente svolto da queste pagine è stato trasferito ai fogli di stile XSL. Il vantaggio della nostra nuova divisione dei compiti è che, una volta stabilito il formato XML delle risposte del servlet, lo sviluppo dei fogli di stile è indipendente da quello del servlet.
6.3. Analisi di un documento XML in Java
Le versioni 7 e 8 della nostra applicazione impots saranno client programmati per il precedente servlet xmlsimulations. Questi client riceveranno codice XML che dovranno analizzare per estrarre le informazioni di cui hanno bisogno. Ora faremo una pausa dalle nostre varie versioni per imparare come analizzare un documento XML in Java. Lo faremo utilizzando un esempio incluso in JBuilder 7 chiamato MySaxParser. Il programma viene chiamato come segue:
L'applicazione MySaxParser accetta un solo parametro: l'URI (Uniform Resource Identifier) del documento XML da analizzare. Nel nostro esempio, questo URI sarà semplicemente il nome di un file XML situato nella directory dell'applicazione MySaxParser. Consideriamo due esempi di esecuzione. Nel primo esempio, il file XML da analizzare è errors.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'analisi produce i seguenti risultati:
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
Non abbiamo ancora spiegato cosa fa l'applicazione MySaxParser, ma qui possiamo vedere che visualizza la struttura del documento XML analizzato. Il secondo esempio analizza il file 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'analisi fornisce i seguenti risultati:
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 contiene tutto ciò di cui abbiamo bisogno nella nostra applicazione fiscale, poiché è stata in grado di recuperare sia gli errori che le simulazioni che il server web potrebbe inviare. Esaminiamo il suo codice:
import java.io.IOException;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.apache.xerces.parsers.SAXParser;
import java.util.regex.*;
// the class
public class MySaxParser extends DefaultHandler {
// value of a tree element XML
private StringBuffer valeur=new StringBuffer();
// a regular expression of an element's value when you want to ignore
// the "blanks" that precede or follow it
private static Pattern ptnValeur=null;
private static Matcher résultats=null;
// -------- hand
public static void main(String[] argv) {
// check number of parameters
if (argv.length != 1) {
System.out.println("Usage: java MySaxParser [URI]");
System.exit(0);
}
// retrieve the URI from the XML file to be analyzed
String uri = argv[0];
try {
// creation of a XML analyzer (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// we indicate to the parser the object that will implement the methods
// startDocument, endDocument, startElement, endElement, characters
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// initialize an element's value model
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// we indicate to the parser the XML document to be analyzed
parser.parse(uri);
}
catch(Exception ex) {
// error
System.err.println("Erreur : " + ex);
// trace
ex.printStackTrace();
}
}//hand
// -------- startDocument
public void startDocument() throws SAXException {
// procedure called when the parser encounters the start of the document
System.out.println("Début du document");
}//startDocument
// -------- endDocument
public void endDocument() throws SAXException {
// procedure called when the parser reaches the end of the document
System.out.println("Fin du document");
}//endDocument
// -------- startElement
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// procedure called by the parser when it encounters the start of a tag
// uri: URI of the analyzed document?
// localName: name of element being analyzed
// qName: ditto, but "qualified" by a namespace if there is one
// attributes: list of element attributes
// follow-up
System.out.println("Début élément <"+localName+">");
// does the element have attributes?
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 {
// procedure called repeatedly by the parser when it encounters text
// between two <tag>text</tag> tags
// the text is in ch from the start character on length characters
// the text is added to the value buffer
valeur.append(ch, start, length);
}//characters
// -------- endElement
public void endElement(String uri, String localName, String qName)
throws SAXException {
// procedure called by the parser when it encounters an end of tag
// uri: URI of the analyzed document?
// localName: name of element being analyzed
// qName: ditto, but "qualified" by a namespace if there is one
// the value of the
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
// set element value to empty
valeur.setLength(0);
// follow-up
System.out.println("Fin élément <"+localName+">");
}//endElement
}//class
Per prima cosa, definiamo un acronimo che compare spesso nell'analisi dei documenti XML: SAX, che sta per Simple API for XML. Si tratta di un insieme di classi Java che facilitano il lavoro con i documenti XML. Esistono due versioni dell'API: SAX1 e SAX2. L'applicazione sopra riportata utilizza l'API SAX2.
L'applicazione importa una serie di pacchetti:
I primi due sono inclusi nel JDK 1.4, ma il terzo no. Il pacchetto xerces.jar è disponibile sul sito web di Apache Web Server. È incluso in JBuilder 7 e in Tomcat 4.x:

Quindi, se si desidera compilare l'applicazione precedente al di fuori di JBuilder 7 e si dispone di JDK 1.4 e Tomcat 4.x, è possibile scrivere:
Quando si esegue l'applicazione, procedere allo stesso modo:
dos>java -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\xerces.jar" MySaxParser simulations.xml
La classe MySaxParser estende la classe DefaultHandler. Torneremo su questo punto più avanti. Esaminiamo il codice della procedura principale:
// retrieve the URI from the XML file to be analyzed
String uri = argv[0];
try {
// creation of a XML analyzer (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// we indicate to the parser the object that will implement the methods
// startDocument, endDocument, startElement, endElement, characters
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// initialize an element's value model
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// we indicate to the parser the XML document to be analyzed
parser.parse(uri);
}
catch(Exception ex) {
// error
System.err.println("Erreur : " + ex);
// trace
ex.printStackTrace();
}
Per analizzare un documento XML, la nostra applicazione necessita di un parser di codice XML.
Il parser XML utilizzato è quello fornito dal pacchetto xerces.jar. L'oggetto restituito è di tipo XMLReader. XMLReader è un'interfaccia e qui utilizziamo due dei suoi metodi:
indica al parser quale oggetto ContentHandler gestirà gli eventi generati durante l'analisi del documento XML | |
avvia l'analisi del documento XML passato come parametro |
Quando il parser analizza il documento XML, emette eventi quali: "Ho incontrato l'inizio del documento, l'inizio di un tag, un attributo di tag, il contenuto di un tag, la fine di un tag, la fine del documento, ...". Passa questi eventi all'oggetto ContentHandler che gli è stato fornito. ContentHandler è un'interfaccia che definisce i metodi da implementare per gestire tutti gli eventi che il parser XML può generare. DefaultHandler è una classe che fornisce un'implementazione predefinita di questi metodi. I metodi implementati in DefaultHandler non fanno nulla, ma esistono. Quando dobbiamo indicare al parser quale oggetto gestirà gli eventi che genera utilizzando l'istruzione
è conveniente passare un oggetto di tipo DefaultHandler come parametro. Se ci fermassimo qui, nessun evento del parser verrebbe gestito, ma il nostro programma sarebbe sintatticamente corretto. In pratica, passiamo al parser come parametro un oggetto derivato dalla classe DefaultHandler, in cui vengono ridefiniti solo i metodi che gestiscono gli eventi che ci interessano. Questo è ciò che viene fatto qui:
// we indicate to the parser the object that will implement the methods
// startDocument, endDocument, startElement, endElement, characters
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// we indicate to the parser the XML document to be analyzed
parser.parse(uri);
Passiamo al parser un'istanza della classe mySaxParser, che è la nostra classe ed è stata definita in precedenza dalla dichiarazione
e iniziamo ad analizzare il documento il cui URI è stato passato come parametro. Da lì, inizia l'analisi del documento XML. Il parser emette eventi e, per ciascuno di essi, chiama un metodo specifico dell'oggetto responsabile della gestione di questi eventi — in questo caso, il nostro oggetto MySaxParser. Questo oggetto gestisce cinque eventi specifici; gli altri vengono ignorati:
evento emesso dal parser | metodo di gestione |
void startDocument() | |
void fineDocumento() | |
public void startElement(String uri, String localName, String qName, Attributes attributes) uri: ? localName: nome dell'elemento analizzato. Se l'elemento incontrato è <simulations>, localName sarà "simulations". qName: nome qualificato con namespace dell'elemento analizzato. Un documento XML può definire un namespace, come ad esempio XX. Il nome qualificato del tag precedente sarebbe quindi XX:simulations. attributes: elenco degli attributi del tag | |
public void characters(char[] ch, int start, int length) ch: array di caratteri start: indice del primo carattere da utilizzare nell'array ch length: numero di caratteri da prelevare dall'array ch Il metodo characters può essere chiamato più volte. Per costruire il valore di un elemento, usiamo un buffer che:
| |
void endElement(String uri, String localName, String qName) I parametri sono gli stessi del metodo startElement. |
Il metodo startElement consente di recuperare gli attributi dell'elemento utilizzando il parametro attributes di tipo Attributes:
- Il numero di attributi è disponibile in attributes.getLength()
- Il nome dell'attributo i è disponibile in attributes.getLocalName(i)
- Il valore dell'attributo i è disponibile in attributes.getValue(i)
- il valore dell'attributo localName in attributes.getValue(localName)
Con questa spiegazione, il programma precedente e i suoi esempi di esecuzione sono autoesplicativi. È stata utilizzata un'espressione regolare per recuperare i valori degli elementi in modo che un testo XML come:
restituisce il testo "errore 1" come valore associato al tag <error>, privo di eventuali spazi e interruzioni di riga che potrebbero precederlo e/o seguirlo.
6.4. Applicazione per il calcolo delle imposte: Versione 7
Ora disponiamo di tutti gli elementi per scrivere i client per il nostro servizio fiscale che fornisce XML. Utilizzeremo la versione 4 della nostra applicazione per il client e manterremo la versione 6 per il server. In questa applicazione client-server:
- Il servizio di simulazione del calcolo delle imposte è gestito dal servlet xmlsimulations. La risposta del server è quindi in formato XML, come abbiamo visto nella versione 6.
- Il client non è più un browser, ma un client Java autonomo. La sua interfaccia grafica è quella della versione 4.
Ecco alcuni esempi dell'applicazione in azione. Innanzitutto, uno scenario di errore: il client interroga il servlet xmlsimulations anche se non è riuscito a inizializzarsi correttamente perché il DBMS MySQL non era in esecuzione:

Avviamo MySQL ed eseguiamo alcune simulazioni:

Il client in questa nuova versione differisce da quello della versione 4 solo per il modo in cui elabora la risposta del server. Nient’altro cambia. Nella versione 4, il client riceveva codice HTML dal quale estraeva le informazioni necessarie utilizzando espressioni regolari. Qui, il client riceve codice XML dal quale recupera le informazioni necessarie utilizzando un parser XML.
Rivediamo i passaggi principali della procedura associata al menu Calcola nella versione 4 del nostro client, poiché è lì che si verificano principalmente le modifiche:
void mnuCalculer_actionPerformed(ActionEvent e) {
....
try{
// tax calculation
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// error is displayed
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{
// tAX CALCULATION
// urlImpots : URL of the tax department
// married: true if married, false otherwise
// nbEnfants : number of children
// salary: annual salary
// remove from urlImpots the info needed to connect to the tax server
....
try{
// connect to the server
....
// create customer input/output flows TCP
....
// request URL - send HTTP headers
....
// read the 1st line of the answer
....
// we read the response through to the end of the headers, looking for any cookies
while((réponse=IN.readLine())!=null){
.... }//while
// that's it for HTTP headers - move on to HTML code
// to retrieve simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// it's over
....
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
....
}
Tutto questo codice rimane valido nella nuova versione. Solo l'elaborazione della risposta HTML del server (sezione in riquadro sopra) e la sua visualizzazione devono essere sostituite dall'elaborazione della risposta XML del server e dalla sua visualizzazione:
// that's it for HTTP headers - move on to XML code
// to recover simulations or errors
ImpotsSaxParser parseur=new ImpotsSaxParser(IN);
ArrayList listeErreurs=parseur.getErreurs();
ArrayList listeSimulations=parseur.getSimulations();
// close server connection
client.close();
// display list cleaning
simulations.clear();
// errors
if(listeErreurs.size()!=0){
// concatenate all errors
String msgErreur="Le serveur a signalé les erreurs suivantes :\n";
for(int i=0;i<listeErreurs.size();i++){
msgErreur+=" - "+(String)listeErreurs.get(i);
}
// error display
throw new Exception(msgErreur);
}//if
// simulations
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
return;
Cosa fa il frammento di codice sopra riportato?
- Crea un parser XML e gli passa lo stream IN, che contiene il codice XML inviato dal server. Questo stream conteneva anche le intestazioni HTTP, ma queste sono già state lette ed elaborate. Pertanto, rimane solo la parte XML della risposta. Il parser produce due elenchi di stringhe: l'elenco degli errori, se presenti, o l'elenco delle simulazioni. Questi due elenchi si escludono a vicenda.
- Se l'elenco degli errori non è vuoto, i messaggi presenti nell'elenco vengono concatenati in un unico messaggio di errore e viene generata un'eccezione con quel messaggio come parametro. Questa eccezione viene visualizzata nella procedura mnuCalculer_actionPerformed che ha chiamato calculerImpots.
- Se l'elenco delle simulazioni non è vuoto, viene visualizzato nel componente jList dell'interfaccia utente grafica.
Esploriamo ora il parser per la risposta XML del server, un parser che deriva direttamente dal nostro precedente studio su come analizzare un documento XML in 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.*;
// the class
public class ImpotsSaxParser extends DefaultHandler {
// value of a tree element XML
private StringBuffer valeur=new StringBuffer();
// a regular expression of an element's value when you want to ignore
// the "blanks" that precede or follow it
private Pattern ptnValeur=null;
private Matcher résultats=null;
// lists of XML elements
private ArrayList listeSimulations=new ArrayList();
private ArrayList listeErreurs=new ArrayList();
// elements XML
private ArrayList éléments=new ArrayList();
String élément="";
// -------- manufacturer
public ImpotsSaxParser(BufferedReader IN) throws Exception{
// creation of a XML analyzer (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// we indicate to the parser the object that will implement the methods
// startDocument, endDocument, startElement, endElement, characters
parser.setContentHandler(this);
// initialize an element's value model
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// initially no current XML element
éléments.add("");
// document analysis
parser.parse(new InputSource(IN));
}//manufacturer
// -------- startElement
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// procedure called by the parser when it encounters the start of a tag
// uri: URI of the analyzed document?
// localName: name of element being analyzed
// qName: ditto, but "qualified" by a namespace if there is one
// attributes: list of element attributes
// note the name of the element
élément=localName.toLowerCase();
éléments.add(élément);
// does the element have attributes?
if(élément.equals("simulation") && attributes.getLength()==4){
// it's a simulation - we retrieve the attributes
String simulation=attributes.getValue("marie")+","+
attributes.getValue("enfants")+","+
attributes.getValue("salaire")+","+
attributes.getValue("impot");
// add the simulation to the list of simulations
listeSimulations.add(simulation);
}//if
}//startElement
// -------- characters
public void characters(char[] ch, int start, int length) throws SAXException {
// procedure called repeatedly by the parser when it encounters text
// between two <tag>text</tag> tags
// the text is in ch from the start character on length characters
// the text is added to the value buffer if it is the error element
if (élément.equals("erreur"))
valeur.append(ch, start, length);
}//characters
// -------- endElement
public void endElement(String uri, String localName, String qName)
throws SAXException {
// procedure called by the parser when it encounters an end of tag
// uri: URI of the analyzed document?
// localName: name of element being analyzed
// qName: ditto, but "qualified" by a namespace if there is one
// case of error
if(élément.equals("erreur")){
// retrieve the value of the error element
String strValeur=valeur.toString();
// we strip it of its useless "blanks" and register it in the
// errors if non-empty
résultats=ptnValeur.matcher(strValeur);
if (résultats.find() && ! résultats.group(1).equals("")){
listeErreurs.add(résultats.group(1));
}//if
}
// set element value to empty
valeur.setLength(0);
// reset element name
é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;
}
}//class
- Il costruttore riceve il flusso XML da analizzare ed esegue immediatamente tale analisi. Una volta completata, l'oggetto è stato costruito e sono stati creati gli elenchi (ArrayList) degli errori (errorList) e delle simulazioni (simulationList). Tutto ciò che resta da fare alla procedura che ha costruito l'oggetto è recuperare i due elenchi utilizzando i metodi getErrors e getSimulations.
- Solo tre eventi generati dal parser XML sono rilevanti in questo contesto:
- l'inizio di un elemento XML, un evento che sarà gestito dalla procedura startElement. Questa procedura gestirà i tag <simulation marie=".." enfants=".." salaire=".." impot=".."> e <erreur>...</erreur>.
- Il valore di un elemento XML, un evento che verrà gestito dalla procedura characters.
- La fine di un elemento XML, un evento che verrà gestito dalla procedura endElement.
- Nella procedura startElement, se abbiamo a che fare con l'elemento <simulation marie=".." enfants=".." salaire=".." impot="..">, recuperiamo i quattro attributi utilizzando attributes.getValue("nome attributo"). In tutti i casi, memorizziamo il nome dell'elemento in una variabile element e lo aggiungiamo a un elenco (ArrayList) di elementi: elem1, elem2, ..., elemN. Questo elenco è gestito come uno stack, dove l'ultimo elemento è l'elemento XML attualmente in fase di analisi. Quando si verifica l'evento "fine dell'elemento", l'ultimo elemento dell'elenco viene rimosso e viene impostato il nuovo elemento corrente. Ciò avviene nella procedura endElement.
- La procedura characters è identica a quella studiata in un esempio precedente. Ci limitiamo semplicemente a verificare che l'elemento corrente sia effettivamente l'elemento <error>, una precauzione che normalmente qui non è necessaria. Questo tipo di precauzione è stata presa anche nella procedura startElement per verificare che si trattasse di un elemento <simulation>.
6.5. Conclusione
Grazie alla sua risposta XML, l'applicazione impots è diventata più facile da gestire sia per il suo progettista che per i progettisti delle applicazioni client.
- La progettazione dell'applicazione server può ora essere affidata a due figure professionali: lo sviluppatore Java del servlet e il grafico che gestirà l'aspetto della risposta del server nei browser. Quest'ultimo deve semplicemente conoscere la struttura della risposta XML del server per creare i fogli di stile che la accompagneranno. Si noti che questi sono contenuti in file XSL separati, indipendenti dal servlet Java. Il progettista dell'interfaccia utente può quindi lavorare indipendentemente dallo sviluppatore Java.
- Anche i progettisti di applicazioni client devono semplicemente conoscere la struttura della risposta XML del server. Qualsiasi modifica che il grafico possa apportare ai fogli di stile non ha alcun impatto su questa risposta XML, che rimane sempre la stessa. Questo è un enorme vantaggio.
- Come può lo sviluppatore aggiornare il proprio servlet Java senza compromettere il funzionamento del sistema? Innanzitutto, purché la risposta XML rimanga invariata, può strutturare il servlet come preferisce. Può anche aggiornare la risposta XML, purché mantenga gli elementi <error> e <simulation> richiesti dai propri clienti. Può quindi aggiungere nuovi tag a questa risposta. Lo sviluppatore front-end ne terrà conto nei propri fogli di stile e i browser saranno in grado di ricevere le nuove versioni della risposta. I client programmatici, tuttavia, continueranno a funzionare con il vecchio modello, poiché i nuovi tag vengono semplicemente ignorati. Affinché ciò funzioni, i tag ricercati devono essere chiaramente identificati nell'analisi XML della risposta del server. Questo è ciò che è stato fatto nel nostro client XML per l'applicazione fiscale, dove le procedure specificavano che stavamo elaborando i tag <error> e <simulation>. Di conseguenza, gli altri tag vengono ignorati.