6. XML y JAVA
En este capítulo, presentamos el uso de documentos XML con Java. Lo haremos en el contexto de la aplicación de impuestos analizada en el capítulo anterior.
6.1. Archivos XML y hojas de estilo XSL
Consideremos el siguiente archivo XML simulations.xml, que podría representar el resultado de simulaciones de cálculos de impuestos:
<?xml version="1.0" encoding="ISO-8859-1"?>
<simulations>
<simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
<simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>
Si lo visualizamos con IE 6, obtenemos el siguiente resultado:

IE6 reconoce que se trata de un archivo XML (gracias a la extensión .xml del archivo) y lo formatea a su manera. Con Netscape se obtiene una página en blanco. Sin embargo, si se revisa el código fuente (Ver/Fuente), se observa que sí está presente el archivo original XML:

¿Por qué Netscape no muestra nada? Porque necesita una hoja de estilo que le indique cómo transformar el archivo XML en el archivo HTML, el cual sí podrá mostrar. Resulta que IE 6 sí cuenta con una hoja de estilo por defecto, mientras que el archivo XML no la incluye, como era el caso aquí.
Existe un lenguaje llamado XSL (eXtended StyleSheet Language) que permite describir las transformaciones que se deben realizar para convertir un archivo XML en cualquier archivo de texto. XSL permite el uso de numerosas instrucciones y se asemeja mucho a los lenguajes de programación. No lo detallaremos aquí, ya que se necesitarían varias decenas de páginas. Simplemente describiremos dos ejemplos de hojas de estilo XSL. La primera es la que transformará el archivo XML simulations.xml en código HTML. Modificamos este último para que indique la hoja de estilo que los navegadores podrán utilizar para transformarlo en el documento HTML, documento que podrán mostrar:
<?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>
El pedido XML
designa el archivo simulations.xsl como una hoja de estilo (xml-stylesheet) de tipo text/xsl c.a.d. Un archivo de texto que contiene código XSL. Los navegadores utilizarán esta hoja de estilo para transformar el texto XML en el documento HTML. Este es el resultado obtenido con Netscape 7 al cargar el archivo XML simulations.xml:

Al revisar el código fuente del documento (Ver/Fuente), encontramos el documento XML original y no el documento HTML que se muestra:

Netscape utilizó la hoja de estilo simulations.xsl para transformar el documento XML anterior en el documento HTML que se puede visualizar. Ahora es el momento de revisar el contenido de esta hoja de estilo:
<?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>
- Una hoja de estilo XSL es un archivo XML y, por lo tanto, sigue sus reglas. Entre otras cosas, debe estar «bien formado», es decir, toda etiqueta abierta debe cerrarse.
- El archivo comienza con dos comandos XML que se pueden mantener en cualquier hoja de estilo XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
El atributo encoding="ISO-8859-1" permite utilizar caracteres acentuados en la hoja de estilo.
- La etiqueta <xsl:output method="html" indent="yes"/> le indica al intérprete XSL que se desea generar HTML «sangrado».
- La etiqueta <xsl:template match="elemento"> sirve para definir el elemento del documento XML al que se aplicarán las instrucciones que se encuentran entre <xsl:template ...> y </xsl:template>.
En el ejemplo anterior, el elemento «/» designa la raíz del documento. Esto significa que, tan pronto como se encuentre el inicio del documento XML, se ejecutarán los comandos XSL ubicados entre las dos etiquetas.
- Todo lo que no sea una etiqueta XSL se incluye tal cual en el flujo de salida. Las etiquetas XSL, por su parte, se ejecutan. Algunas de ellas producen un resultado que se incluye en el flujo de salida. Analicemos el siguiente ejemplo:
<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>
Recordemos que el documento XML analizado es el siguiente:
<?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>
Desde el inicio del documento XML analizado (match="/"), el intérprete XSL generará como salida el texto
<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>
Cabe señalar que en el texto original aparecía <hr/> y no <hr>. En el texto original no se podía escribir <hr>, ya que, si bien es una etiqueta HTML válida, es una etiqueta XML inválida. Sin embargo, aquí nos encontramos con un texto XML que debe estar «bien formado», es decir, c.a.d, lo que significa que todas las etiquetas deben estar cerradas. Por lo tanto, escribimos <hr/> y, como hemos escrito <xsl:output text="html ...">, el intérprete XSL transformará el texto <hr/> en <hr>. A continuación de este texto, vendrá el texto generado por el comando XSL:
Más adelante veremos de qué texto se trata. Por último, el intérprete agregará el texto:
La orden <xsl:apply-templates select="/simulations/simulation"/> solicita que se ejecute la «plantilla» (modelo) del elemento /simulations/simulation. Se ejecutará cada vez que el intérprete XSL encuentre en el texto XML analizado una etiqueta <simulation>..</simulations> o <simulation/> dentro de una etiqueta <simulations>..</simulations>. Al encontrar la etiqueta <simulation>, el intérprete ejecutará las instrucciones de la siguiente plantilla:
<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>
Consideremos las siguientes líneas XML:
La línea <simulation ..> corresponde a la plantilla de la instrucción XSL <xsl:apply-templates select="/simulations/simulation>". Por lo tanto, el intérprete XSL buscará aplicar las instrucciones que correspondan a esta plantilla. Encontrará la plantilla <xsl:template match="simulation"> y la ejecutará. Recordemos que lo que no es un comando XSL es tomado tal cual por el intérprete XSL, y que los comandos XSL son reemplazados por el resultado de su ejecución. La instrucción XSL <xsl:value-of select="@champ"/> se reemplaza así por el valor del atributo «champ» del nodo analizado (en este caso, un nodo <simulation>). El análisis de la línea anterior XML generará el siguiente resultado:
XSL | salida |
<tr><td> | <tr><td> |
<xsl:value-of select="@marie"/> | sí |
</td><td> | </td><td> |
<xsl:value-of select="@hijos"/> | 2 |
</td><td> | </td><td> |
<xsl:value-of select="@salaire"/> | 200000 |
</td><td> | </td><td> |
<xsl:value-of select="@impot"/> | 22504 |
</td></tr> | </td></tr> |
En total, la línea XML
se transformará en la línea HTML:
Todas estas explicaciones son un poco rudimentarias, pero ahora debería quedar claro para el lector que el siguiente texto 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>
acompañado de la siguiente hoja de estilo 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 el siguiente texto: 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>
El archivo XML simulations.xml, junto con la hoja de estilo simulations.xsl, cuando se abre en un navegador reciente (en este caso, Netscape 7), se muestra de la siguiente manera:

6.2. Aplicación de impuestos: versión 6
6.2.1. Los archivos XML y las hojas de estilo XSL de la aplicación de impuestos
Volvamos a la aplicación web de impuestos y modifiquémosla para que la respuesta enviada a los clientes sea una respuesta en formato XML en lugar de una respuesta HTML. Esta respuesta XML irá acompañada de una hoja de estilo XSL para que los navegadores puedan mostrarla. En el párrafo anterior, presentamos:
- el archivo simulations.xml, que es el prototipo de una respuesta XML con simulaciones de cálculos de impuestos
- el archivo simulations.xsl, que será la hoja de estilo XSL que acompañará a esta respuesta XML
También debemos considerar el caso de una respuesta con errores. El prototipo de la respuesta XML en este caso será el siguiente archivo 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>
La hoja de estilo erreurs.xsl que permite visualizar este documento XML en un navegador será la siguiente:
<?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>
Esta hoja de estilo introduce un comando XSL que aún no se ha encontrado: <xsl:value-of select="."/>. Este comando genera como salida el valor del nodo analizado, en este caso un nodo <erreur>texte</erreur>. El valor de este nodo es el texto que se encuentra entre las dos etiquetas de apertura y cierre, en este caso texte.
El código erreurs.xml es transformado por la hoja de estilo erreurs.xsl en el siguiente 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>
El archivo erreurs.xml, junto con su hoja de estilo, se muestra en un navegador de la siguiente manera:

6.2.2. El servlet xmlsimulations
Creamos un archivo index.html que colocamos en el directorio de la aplicación impots. La página que se visualiza es la siguiente:

Este documento HTML es un documento estático. Su código es el siguiente:
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// borrar el formulario
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//con
}//borrar
function calculer(){
// verificación de los parámetros antes de enviarlos al servidor
with(document.frmImpots){
//número de hijos
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// el modelo no se ha verificado
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//si
//salario
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// el modelo no se ha verificado
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//si
// está bien, se envía
submit();
}//con
}//calcular
</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>
Cabe señalar que los datos del formulario se envían a URL /impuestos/xmlsimulaciones. Esta aplicación es un servlet de Java configurado de la siguiente manera en el archivo web.xml de la aplicación 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>
- El servlet se llama xmlsimulations y se basa en la clase xmlsimulations.class.
- Tiene como parámetros los valores DSNimpots, admimpots y mdpimpots, necesarios para acceder a la base de datos de impuestos. Además, admite otros dos parámetros:
- xslSimulations, que es el nombre del archivo de estilo que debe acompañar a la respuesta XML que contiene las simulaciones
- xslErreurs, que es el nombre del archivo de estilo que debe acompañar a la respuesta XML, la cual contiene los posibles errores
- tiene un alias «xmlsimulations» que permite acceder a ella a través de URL y http://localhost:8080/impots/xmlsimulations.
La estructura básica del servlet xmlsimulations es similar a la del servlet simulations que ya hemos analizado. La principal diferencia radica en que debe generar XML en lugar de HTML. Esto implicará la eliminación de los archivos JSP utilizados en las aplicaciones anteriores. Su función principal era mejorar la legibilidad del código HTML generado, evitando que este quedara oculto en el código Java del servlet. Ahora esta función ya no es necesaria. El servlet tiene dos tipos de código XML que debe generar:
- el de las simulaciones
- el de los errores
Anteriormente hemos presentado y analizado los dos tipos de respuesta XML que se deben proporcionar en estos dos casos, así como las hojas de estilo que deben acompañarlas. El código del servlet es el siguiente:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;
public class xmlsimulations extends HttpServlet{
// variables de instancia
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{
// se recupera el flujo de escritura hacia el cliente
PrintWriter out=response.getWriter();
// se especifica el tipo de respuesta
response.setContentType("text/xml");
// la lista de errores
ArrayList erreurs=new ArrayList();
// ¿Se realizó correctamente la inicialización?
if(msgErreur!=null){
// Ya está listo: se envía la respuesta con los errores al servidor
erreurs.add(msgErreur);
sendErreurs(out,xslErreurs,erreurs);
// Se ha completado
return;
}
// se recuperan las simulaciones anteriores de la sesión
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// se recuperan los parámetros de la consulta actual
String optMarie=request.getParameter("optMarie"); // estado civil
String txtEnfants=request.getParameter("txtEnfants"); // número de hijos
String txtSalaire=request.getParameter("txtSalaire"); // salario anual
// ¿Se tienen todos los parámetros esperados?
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// faltan parámetros
// se envía la respuesta con errores
erreurs.add("Demande incomplète. Il manque des paramètres");
sendErreurs(out,xslErreurs,erreurs);
// se ha completado
return;
}
// Tenemos todos los parámetros; los verificamos
// estado civil
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// error
erreurs.add("Etat marital incorrect");
}
// número de hijos
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// error
erreurs.add("Nombre d'enfants incorrect");
}
// salario
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// error
erreurs.add("Salaire incorrect");
}
if(erreurs.size()!=0){
// si hay errores, se señalan
sendErreurs(out,xslErreurs,erreurs);
}else{
// sin errores
try{
// se puede calcular el impuesto a pagar
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
String txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// se suma el resultado actual a las simulaciones anteriores
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// se envía la respuesta con las simulaciones
sendSimulations(out,xslSimulations,simulations);
}catch(Exception ex){}
}//if-else
// se vuelve a incluir la lista de simulaciones en la sesión
session.setAttribute("simulations",simulations);
}//GET
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}//POST
//-------- INIT
public void init(){
// se recuperan los parámetros de inicialización
ServletConfig config=getServletConfig();
xslSimulations=config.getInitParameter("xslSimulations");
xslErreurs=config.getInitParameter("xslErreurs");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// ¿Parámetros correctos?
if(xslSimulations==null || DSNimpots==null || admimpots==null || mdpimpots==null){
msgErreur="Configuration incorrecte";
return;
}
// se crea una instancia de impotsJDBC
try{
impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){
msgErreur=ex.getMessage();
}
}//inicialización
//-------- 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";
}//para
réponse+="</erreurs>\n";
// se envía la respuesta
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++){
// simulación n.º i
simulation=(String[])simulations.get(i);
réponse+="<simulation "
+"marie=\""+(String)simulation[0]+"\" "
+"enfants=\""+(String)simulation[1]+"\" "
+"salaire=\""+(String)simulation[2]+"\" "
+"impot=\""+(String)simulation[3]+"\" />\n";
}//para
réponse+="</simulations>\n";
// se envía la respuesta
out.println(réponse);
}
}
Veamos en detalle las principales novedades de este código en comparación con lo que ya conocíamos:
- el procedimiento `init` recupera nuevos parámetros del archivo de configuración web.xml: los nombres de las dos hojas de estilo XSL que deben acompañar a la respuesta se almacenan en las variables xslSimulations y xslErreurs. Estas dos hojas de estilo son los archivos simulations.xsl y erreurs.xsl que analizamos anteriormente. Estos se colocan en el directorio de la aplicación 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
- El procedimiento GET comienza por verificar si se produjo un error durante la inicialización. Si es así, llama al procedimiento sendErreurs, el cual genera la respuesta XML adecuada para este caso y luego se detiene. En esta respuesta XML se inserta la instrucción que indica la hoja de estilo que se debe utilizar.
- Si no se han producido errores, el procedimiento GET analiza los parámetros de la solicitud del cliente. Si encuentra algún error, lo notifica utilizando también el procedimiento sendErreurs. De lo contrario, calcula la nueva simulación, la agrega a las anteriores almacenadas en la sesión actual y finaliza enviando su respuesta XML a través del procedimiento sendSimulations. Este último procede de manera análoga al procedimiento sendErreurs.
- Cabe destacar que el servlet indica que su respuesta es del tipo text/xml:
A continuación se muestran algunos ejemplos de ejecución. El formulario inicial se completa de la siguiente manera:

La base de datos MySQL no se ha iniciado, lo que imposibilita la construcción del objeto impots en el procedimiento init del servlet. La respuesta de este es entonces la siguiente:

El código recibido por el navegador (Ver/Fuente) es el siguiente:

Si ahora repetimos dos simulaciones después de haber iniciado la base de datos MySQL, obtenemos el siguiente resultado:

En esta ocasión, el navegador recibió el siguiente código:

Cabe destacar que nuestra nueva aplicación es más sencilla que antes debido a la eliminación de los archivos JSP. Parte del trabajo que realizaban estas páginas se ha transferido a las hojas de estilo XSL. La ventaja de nuestra nueva distribución de tareas es que, una vez establecido el formato XML de las respuestas del servlet, el desarrollo de las hojas de estilo es independiente del del servlet.
6.3. Análisis de un documento XML en Java
Las versiones 7 y 8 de nuestra aplicación de impuestos serán clientes programados del servlet anterior xmlsimulations. Estos recibirán código XML que deberán analizar para extraer la información que les interesa. Aquí haremos una pausa en nuestras diferentes versiones para aprender cómo se puede analizar un documento XML en Java. Lo haremos a partir de un ejemplo que viene con JBuilder 7, llamado MySaxParser. El programa se llama de la siguiente manera:
La aplicación MySaxParser admite un parámetro: el URI (Identificador Uniforme de Recursos) del documento XML que se va a analizar. En nuestro ejemplo, este URI será simplemente el nombre de un archivo XML ubicado en el directorio de la aplicación MySaxParser. Veamos dos ejemplos de ejecución. En el primer ejemplo, el archivo XML que se analiza es el archivo 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>
El análisis arroja los siguientes resultados:
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
Aún no habíamos explicado para qué sirve la aplicación MySaxParser, pero aquí podemos ver que muestra la estructura del documento XML analizado. El segundo ejemplo analiza el archivo 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>
El análisis arroja los siguientes resultados:
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 clase MySaxParser contiene todo lo que necesitamos en nuestra aplicación de impuestos, ya que ha sido capaz de detectar tanto los errores como las simulaciones que podría enviar el servidor web. Analicemos su código:
import java.io.IOException;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.apache.xerces.parsers.SAXParser;
import java.util.regex.*;
// la clase
public class MySaxParser extends DefaultHandler {
// valor de un elemento del árbol XML
private StringBuffer valeur=new StringBuffer();
// una expresión regular del valor de un elemento cuando se desea ignorarlo
// los «espacios en blanco» que lo preceden o lo siguen
private static Pattern ptnValeur=null;
private static Matcher résultats=null;
// -------- main
public static void main(String[] argv) {
// verificación del número de parámetros
if (argv.length != 1) {
System.out.println("Usage: java MySaxParser [URI]");
System.exit(0);
}
// se recupera el URI del archivo XML que se va a analizar
String uri = argv[0];
try {
// creación de un analizador XML (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// se le indica al analizador el objeto que implementará los métodos
// startDocument, endDocument, startElement, endElement, caracteres
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// se inicializa el modelo de valor de un elemento
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// se le indica al analizador el documento XML que debe analizar
parser.parse(uri);
}
catch(Exception ex) {
// error
System.err.println("Erreur : " + ex);
// registro
ex.printStackTrace();
}
}//main
// -------- startDocument
public void startDocument() throws SAXException {
// procedimiento que se ejecuta cuando el analizador encuentra el inicio del documento
System.out.println("Début du document");
}//startDocument
// -------- endDocument
public void endDocument() throws SAXException {
// procedimiento que se invoca cuando el analizador llega al final del documento
System.out.println("Fin du document");
}//endDocument
// -------- startElement
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// procedimiento llamado por el analizador cuando encuentra el inicio de una etiqueta
// URI: ¿URI del documento analizado?
// localName: nombre del elemento que se está analizando
// qName: lo mismo, pero «calificado» por un espacio de nombres, si lo hay
// atributos: lista de los atributos del elemento
// seguimiento
System.out.println("Début élément <"+localName+">");
// ¿Tiene el elemento atributos?
for (int i = 0; i < attributes.getLength(); i++) {
System.out.println(attributes.getLocalName(i) + " = " + attributes.getValue(i));
}//para
}//startElement
// -------- caracteres
public void characters(char[] ch, int start, int length) throws SAXException {
// procedimiento llamado repetidamente por el analizador cuando encuentra texto
// entre dos etiquetas <etiqueta>texto</etiqueta>
// el texto se encuentra en ch a partir del carácter start y tiene una longitud de length caracteres
// el texto se agrega al búfer valor
valeur.append(ch, start, length);
}//caracteres
// -------- endElement
public void endElement(String uri, String localName, String qName)
throws SAXException {
// procedimiento llamado por el analizador cuando encuentra el fin de una etiqueta
// URI: ¿URI del documento analizado?
// localName: nombre del elemento que se está analizando
// qName: lo mismo, pero «calificado» por un espacio de nombres, si lo hay
// se muestra el valor del elemento
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)+"]");
}//si
// se deja el valor del elemento en blanco
valeur.setLength(0);
// seguido de
System.out.println("Fin élément <"+localName+">");
}//endElement
}//clase
En primer lugar, definamos una sigla que aparece con frecuencia en el análisis de documentos XML: SAX, que significa «Simple API for XML». Se trata de un conjunto de clases Java que facilitan el trabajo con los documentos XML. Existen dos versiones de API: SAX1 y SAX2. La aplicación anterior utiliza API y SAX2.
La aplicación importa varios paquetes:
Los dos primeros vienen con el JDK 1.4, pero el tercero no. El paquete xerces.jar está disponible en el sitio web del servidor web Apache. Viene con JBuilder 7, pero también con Tomcat 4.x:

Por lo tanto, si queremos compilar la aplicación anterior fuera de JBuilder 7 y contamos con JDK 1.4 y Tomcat 4.x, podremos escribir:
Al ejecutarla, se hará lo mismo:
dos>java -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\xerces.jar" MySaxParser simulations.xml
La clase MySaxParser deriva de la clase DefaultHandler. Volveremos sobre esto más adelante. Analicemos el código del procedimiento main:
// se recupera el URI del archivo XML que se va a analizar
String uri = argv[0];
try {
// creación de un analizador XML (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// se le indica al analizador el objeto que implementará los métodos
// startDocument, endDocument, startElement, endElement, caracteres
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// se inicializa el modelo de valor de un elemento
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// se le indica al analizador el documento XML que debe analizar
parser.parse(uri);
}
catch(Exception ex) {
// error
System.err.println("Erreur : " + ex);
// registro
ex.printStackTrace();
}
Para analizar un documento XML, nuestra aplicación necesita un analizador de código XML llamado «parser».
El analizador XML que se utiliza es el que proporciona el paquete xerces.jar. El objeto recuperado es de tipo XMLReader. XMLReader es una interfaz de la que aquí utilizamos dos métodos:
indica al analizador el objeto de tipo ContentHandler que procesará los eventos que generará durante el análisis del documento XML | |
inicia el análisis del documento XML pasado como parámetro |
Cuando el analizador analice el documento XML, emitirá eventos tales como: «he encontrado el inicio del documento, el inicio de una etiqueta, un atributo de etiqueta, el contenido de una etiqueta, el fin de una etiqueta, el fin del documento, ...». Transmite estos eventos al objeto ContentHandler que se le ha proporcionado. ContentHandler es una interfaz que define los métodos que deben implementarse para procesar todos los eventos que el analizador XML puede generar. DefaultHandler es una clase que proporciona una implementación predeterminada de estos métodos. Los métodos implementados en DefaultHandler no hacen nada, pero existen. Cuando se debe indicar al analizador qué objeto procesará los eventos que generará mediante la instrucción
, resulta práctico pasar como parámetro un objeto de tipo DefaultHandler. Si nos detuviéramos ahí, ningún evento del analizador sería procesado, pero nuestro programa sería sintácticamente correcto. En la práctica, se pasa como parámetro al analizador un objeto derivado de la clase DefaultHandler, en el que se redefinen los métodos que manejan únicamente los eventos que nos interesan. Esto es lo que se hace aquí:
// se le indica al analizador el objeto que implementará los métodos
// startDocument, endDocument, startElement, endElement, caracteres
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// se le indica al analizador el documento XML que debe analizar
parser.parse(uri);
Le pasamos al analizador una instancia de la clase mySaxParser, que es nuestra clase y que se definió anteriormente mediante la declaración
y se inicia el análisis del documento, al que se le ha pasado URI como parámetro. A partir de ahí, comienza el análisis del documento XML. El analizador sintáctico genera eventos y, para cada uno de ellos, llama a un método específico del objeto encargado de procesar dichos eventos, en este caso nuestro objeto MySaxParser. Este procesa cinco eventos específicos, mientras que los demás se ignoran:
evento emitido por el analizador | método de procesamiento |
void startDocument() | |
void endDocument() | |
public void startElement(String uri, String localName, String qName, Attributes attributes) uri: ? localName: nombre del elemento analizado. Si el elemento encontrado es <simulations>, tendremos localName="simulaciones". qName: nombre calificado por un espacio de nombres del elemento analizado. Un documento XML puede definir un espacio de nombres, por ejemplo, XX. El nombre calificado de la etiqueta anterior sería entonces XX:simulations. attributes: lista de atributos de la etiqueta | |
public void characters(char[] ch, int start, int length) ch: matriz de caracteres start: índice del primer carácter que se utilizará en la matriz ch length: número de caracteres que se tomarán del array ch El método characters se puede invocar repetidamente. Para construir el valor de un elemento, se utiliza un búfer que:
| |
void endElement(String uri, String localName, String qName) los parámetros son los mismos que los del método startElement. |
El método startElement permite recuperar los atributos del elemento mediante el parámetro attributes de tipo Attributes:
- el número de atributos está disponible en attributes.getLength()
- El nombre del atributo i está disponible en attributes.getLocalName(i)
- el valor del atributo i está disponible en attributes.getValue(i)
- el valor del atributo de nombre localName en attributes.getValue(localName)
Una vez explicado esto, el programa anterior, junto con los ejemplos de ejecución, se entiende por sí solo. Se utilizó una expresión regular para recuperar los valores de los elementos, de modo que un texto como XML:
debe dar como valor asociado a la etiqueta <erreur> el texto «error 1», sin los espacios ni los saltos de línea que pudieran precederlo o seguirlo.
6.4. Aplicación de impuestos: versión 7
Ahora contamos con todos los elementos para desarrollar clientes programados para nuestro servicio de impuestos que genera XML. Utilizamos la versión 4 de nuestra aplicación para crear el cliente y mantenemos la versión 6 para el servidor. En esta aplicación cliente-servidor:
- el servicio de simulaciones del cálculo de impuestos lo realiza el servlet xmlsimulations. Por lo tanto, la respuesta del servidor está en formato XML, tal como lo vimos en la versión 6.
- el cliente ya no es un navegador, sino un cliente Java independiente. Su interfaz gráfica es la de la versión 4.
A continuación se muestran algunos ejemplos de ejecución. En primer lugar, un caso de error: el cliente consulta el servlet xmlsimulations cuando este no pudo inicializarse correctamente debido a que no se había iniciado el SGBD MySQL:

Iniciamos MySQL y realizamos algunas simulaciones:

El cliente de esta nueva versión solo se diferencia del cliente de la versión 4 en la forma en que procesa la respuesta del servidor. Nada más cambia. En la versión 4, el cliente recibía el código HTML, del cual extraía la información que le interesaba utilizando expresiones regulares. Aquí, el cliente recibe el código XML, del cual extrae la información que le interesa con ayuda de un analizador sintáctico XML.
Recordemos las líneas generales del procedimiento relacionado con el menú «Calcular» de la versión 4 de nuestro cliente, ya que es principalmente ahí donde se realizan los cambios:
void mnuCalculer_actionPerformed(ActionEvent e) {
....
try{
// se calcula el impuesto
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// se muestra el error
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{
// cálculo del impuesto
// urlImpots: URL de la autoridad fiscal
// casado: verdadero si está casado, falso en caso contrario
// nbEnfants: número de hijos
// salario: salario anual
// se extrae de urlImpots la información necesaria para conectarse al servidor de impuestos
....
try{
//: se establece la conexión con el servidor
....
// se crean los flujos de entrada y salida del cliente TCP
....
// se solicita el URL - envío de los encabezados HTTP
....
// se lee la primera línea de la respuesta
....
// se lee la respuesta hasta el final de los encabezados buscando una posible cookie
while((réponse=IN.readLine())!=null){
.... }//mientras
// se han terminado los encabezados HTTP: pasamos al código HTML
// para recuperar las simulaciones
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// ya está
....
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
....
}
Todo este código sigue siendo válido en la nueva versión. Solo el procesamiento de la respuesta HTML del servidor (parte enmarcada arriba) y su visualización deben ser reemplazados por el procesamiento de la respuesta XML del servidor y su visualización:
// ya no se usan las cabeceras HTTP; pasamos al código XML
// para recuperar las simulaciones o los errores
ImpotsSaxParser parseur=new ImpotsSaxParser(IN);
ArrayList listeErreurs=parseur.getErreurs();
ArrayList listeSimulations=parseur.getSimulations();
// cierre de la conexión con el servidor
client.close();
// limpieza de la lista de visualización
simulations.clear();
// errores
if(listeErreurs.size()!=0){
// se concatenan todos los errores
String msgErreur="Le serveur a signalé les erreurs suivantes :\n";
for(int i=0;i<listeErreurs.size();i++){
msgErreur+=" - "+(String)listeErreurs.get(i);
}
// visualización de errores
throw new Exception(msgErreur);
}//si
// simulaciones
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
return;
¿Qué hace el fragmento de código anterior?
- Crea un analizador XML y le pasa el flujo IN, que contiene el código XML enviado por el servidor. Este flujo también contenía los encabezados HTTP, pero estos ya se han leído y procesado. Por lo tanto, solo queda la parte XML de la respuesta. El analizador genera dos listas de cadenas de caracteres: la lista de errores, si los hubo; de lo contrario, la de simulaciones. Estas dos listas son mutuamente excluyentes.
- Si la lista de errores no está vacía, los mensajes contenidos en la lista se concatenan en un solo mensaje de error y se lanza una excepción con ese mensaje como parámetro. Esta excepción se muestra en el procedimiento mnuCalculer_actionPeformed que llamó a calculerImpots.
- Si la lista de simulaciones no está vacía, se muestra en el componente jList de la interfaz gráfica.
Ahora veamos el analizador de la respuesta XML del servidor, un analizador que se deriva directamente del estudio que realizamos anteriormente sobre cómo analizar un documento XML en Java:
import java.io.IOException;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.apache.xerces.parsers.SAXParser;
import java.util.regex.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
// la clase
public class ImpotsSaxParser extends DefaultHandler {
// valor de un elemento del árbol XML
private StringBuffer valeur=new StringBuffer();
// una expresión regular del valor de un elemento cuando se desea ignorar
// los «espacios en blanco» que lo preceden o lo siguen
private Pattern ptnValeur=null;
private Matcher résultats=null;
// las listas de elementos XML
private ArrayList listeSimulations=new ArrayList();
private ArrayList listeErreurs=new ArrayList();
// elementos XML
private ArrayList éléments=new ArrayList();
String élément="";
// -------- fabricante
public ImpotsSaxParser(BufferedReader IN) throws Exception{
// creación de un analizador XML (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// se le indica al analizador el objeto que implementará los métodos
// startDocument, endDocument, startElement, endElement, caracteres
parser.setContentHandler(this);
// se inicializa el valor predeterminado de un elemento
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// al inicio no hay ningún elemento XML activo
éléments.add("");
// se analiza el documento
parser.parse(new InputSource(IN));
}//generador
// -------- startElement
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// procedimiento llamado por el analizador cuando encuentra el inicio de una etiqueta
// URI: ¿URI del documento analizado?
// localName: nombre del elemento que se está analizando
// qName: lo mismo, pero «calificado» por un espacio de nombres, si lo hay
// atributos: lista de los atributos del elemento
// se indica el nombre del elemento
élément=localName.toLowerCase();
éléments.add(élément);
// ¿Tiene el elemento atributos?
if(élément.equals("simulation") && attributes.getLength()==4){
// es una simulación: se recuperan los atributos
String simulation=attributes.getValue("marie")+","+
attributes.getValue("enfants")+","+
attributes.getValue("salaire")+","+
attributes.getValue("impot");
// se agrega la simulación a la lista de simulaciones
listeSimulations.add(simulation);
}//si
}//startElement
// -------- caracteres
public void characters(char[] ch, int start, int length) throws SAXException {
// procedimiento llamado repetidamente por el analizador cuando encuentra texto
// entre dos etiquetas <etiqueta>texto</etiqueta>
// el texto se encuentra en ch a partir del carácter start y tiene una longitud de length caracteres
// el texto se agrega al búfer valor si se trata del elemento error
if (élément.equals("erreur"))
valeur.append(ch, start, length);
}//caracteres
// -------- endElement
public void endElement(String uri, String localName, String qName)
throws SAXException {
// procedimiento llamado por el analizador cuando encuentra el cierre de una etiqueta
// URI: ¿URI del documento analizado?
// localName: nombre del elemento que se está analizando
// qName: lo mismo, pero «calificado» por un espacio de nombres, si lo hay
// caso de error
if(élément.equals("erreur")){
//: se recupera el valor del elemento de error
String strValeur=valeur.toString();
// se eliminan los «espacios en blanco» innecesarios y se registra en la lista de
// errores si no está vacía
résultats=ptnValeur.matcher(strValeur);
if (résultats.find() && ! résultats.group(1).equals("")){
listeErreurs.add(résultats.group(1));
}//si
}
// se pone el valor del elemento en vacío
valeur.setLength(0);
// se reinicia el nombre del elemento
é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;
}
}//clase
- El generador recibe el flujo XML IN para analizarlo y realiza inmediatamente dicho análisis. Una vez finalizado el análisis, se ha creado el objeto, así como las listas (ArrayList) de errores (listeErreurs) y de simulaciones (listeSimulations). Ahora, al procedimiento que generó el objeto solo le queda recuperar las dos listas mediante los métodos getErreurs y getSimulations.
- Aquí solo nos interesan tres eventos generados por el analizador XML:
- el inicio de un elemento XML, evento que será procesado por el procedimiento startElement. Este procedimiento deberá procesar las etiquetas <simulation marie=".." hijos=".." salario=".." impuestos=".."> y <erreur>...</erreur>.
- valor de un elemento XML, evento que será procesado por el procedimiento characters.
- fin de un elemento XML, evento que será procesado por el procedimiento endElement.
- En el procedimiento startElement, si se trata del elemento <simulation marie=".." hijos=".." salario=".." impuestos="..">, se recuperan los cuatro atributos mediante attributes.getValue("nombre del atributo"). En todos los casos, se almacena el nombre del elemento en una variable «elemento» y se agrega a una lista (ArrayList) de elementos: elem1, elem2, ..., elemN. Esta lista se maneja como una pila cuyo último elemento es el elemento XML que se está analizando. Cuando se produce el evento «fin de elemento», se retira el último elemento de la lista y se modifica el nuevo elemento actual. Esto se realiza en el procedimiento endElement.
- El procedimiento «characters» es idéntico al que se había analizado en un ejemplo anterior. Simplemente nos aseguramos de verificar que el elemento actual sea efectivamente el elemento <erreur>, una precaución que normalmente no sería necesaria aquí. Este tipo de precaución también se había tomado en el procedimiento startElement para verificar que se trataba de un elemento <simulation>.
6.5. Conclusión
Gracias a su respuesta XML, la aplicación impots se ha vuelto más fácil de manejar tanto para su desarrollador como para los desarrolladores de las aplicaciones cliente.
- El diseño de la aplicación de servidor ahora puede confiarse a dos tipos de personas: el desarrollador Java del servlet y el diseñador gráfico que se encargará de la apariencia de la respuesta del servidor en los navegadores. A este último le basta con conocer la estructura de la respuesta XML del servidor para crear las hojas de estilo que la acompañarán. Recordemos que estas hojas de estilo son objeto de archivos XSL separados e independientes del servlet Java. Por lo tanto, el diseñador gráfico puede trabajar de manera independiente del desarrollador Java.
- Los diseñadores de las aplicaciones cliente también solo necesitan conocer la estructura de la respuesta XML del servidor. Las modificaciones que el diseñador gráfico pueda realizar en las hojas de estilo no tienen ninguna repercusión en esta respuesta XML, que siempre permanece igual. Esto representa una enorme ventaja.
- ¿Cómo puede el desarrollador hacer evolucionar su servlet de Java sin que nada se rompa? En primer lugar, mientras su respuesta XML no cambie, puede organizar su servlet como desee. También puede actualizar la respuesta XML siempre y cuando mantenga los elementos <error> y <simulación> que esperan sus clientes. De esta manera, puede agregar nuevas etiquetas a esta respuesta. El diseñador gráfico las tomará en cuenta en sus hojas de estilo y los navegadores podrán recibir las nuevas versiones de la respuesta. Los clientes programados, por su parte, seguirán funcionando con el modelo anterior, ya que las nuevas etiquetas simplemente se ignorarán. Para que esto sea posible, es necesario que en el análisis XML de la respuesta del servidor, las etiquetas buscadas estén bien identificadas. Esto es lo que se hizo en nuestro cliente XML de la aplicación de impuestos, donde en los procedimientos se especificaba que se procesaban las etiquetas <erreur> y <simulation>. De este modo, las demás etiquetas se ignoran.