6. XML e Java
Neste capítulo, apresentamos a utilização de documentos XML com Java. Faremos isso no contexto da aplicação fiscal estudada no capítulo anterior.
6.1. Ficheiros XML e folhas de estilo XSL
Considere o seguinte ficheiro XML, simulations.xml, que poderia representar os resultados de simulações de cálculo de impostos:
<?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>
Quando visualizado com o IE 6, obtém-se o seguinte resultado:

O IE6 reconhece que se trata de um ficheiro XML (graças à extensão .xml do ficheiro) e formata-o à sua maneira. Com o Netscape, obtém-se uma página em branco. No entanto, se se observar o código-fonte (Ver/Fonte), é possível ver o ficheiro XML original:

Por que é que o Netscape não exibe nada? Porque precisa de uma folha de estilo para lhe indicar como transformar o ficheiro XML num ficheiro HTML que possa então exibir. Acontece que o IE 6 tem uma folha de estilo predefinida quando o ficheiro XML não fornece nenhuma, o que era o caso aqui.
Existe uma linguagem chamada XSL (eXtended StyleSheet Language) que permite descrever as transformações necessárias para converter um ficheiro XML em qualquer ficheiro de texto. O XSL suporta inúmeras instruções e assemelha-se muito às linguagens de programação. Não entraremos em detalhes aqui, pois isso levaria dezenas de páginas. Vamos simplesmente descrever dois exemplos de folhas de estilo XSL. O primeiro é aquele que transformará o ficheiro XML simulations.xml em código HTML. Modificamos este último para que especifique a folha de estilo que os navegadores podem usar para o transformar num documento HTML, que podem então apresentar:
<?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>
O comando XML
designa o ficheiro simulations.xsl como uma folha de estilo XML do tipo text/xsl, ou seja, um ficheiro de texto que contém código XSL. Esta folha de estilo será utilizada pelos navegadores para transformar o texto XML num documento HTML. Aqui está o resultado obtido com o Netscape 7 ao carregar o ficheiro XML simulations.xml:

Quando visualizamos o código-fonte do documento (Ver/Fonte), vemos o documento XML original em vez do documento HTML apresentado:

O Netscape utilizou a folha de estilo simulations.xsl para transformar o documento XML acima num documento HTML exibível. Chegou a hora de analisar o conteúdo desta folha 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>
- Uma folha de estilo XSL é um ficheiro XML e, por isso, segue as regras do XML. Entre outras coisas, deve ser «bem-formada», o que significa que todas as tags de abertura devem ser fechadas.
- O ficheiro começa com duas diretivas XML que podem ser incluídas em qualquer folha 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">
O atributo encoding="ISO-8859-1" permite que caracteres acentuados sejam utilizados na folha de estilo.
- A tag <xsl:output method="html" indent="yes"/> indica ao interpretador XSL que pretende produzir HTML «indentado».
- A tag <xsl:template match="element"> é utilizada para definir o elemento no documento XML ao qual serão aplicadas as instruções encontradas entre <xsl:template ...> e </xsl:template>.
No exemplo acima, o elemento "/" denota a raiz do documento. Isto significa que, assim que o início do documento XML for encontrado, os comandos XSL localizados entre as duas tags serão executados.
- Tudo o que não for uma tag XSL é incluído tal como está no fluxo de saída. As próprias tags XSL são executadas. Algumas delas produzem um resultado que é incluído no fluxo de saída. Vamos examinar o seguinte exemplo:
<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>
Note que o documento XML que está a ser analisado é o seguinte:
<?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 partir do início do documento XML analisado (match="/"), o processador XSL irá produzir o 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>
Note-se que no texto original tínhamos <hr/> e não <hr>. No texto original, não podíamos escrever <hr>, que, embora seja uma tag HTML válida, é uma tag XML inválida. No entanto, estamos aqui a lidar com texto XML que deve ser «bem-formado», o que significa que todas as tags devem ser fechadas. Por isso, escrevemos <hr/> e, como escrevemos <xsl:output text="html ...>>, o processador XSL transformará o texto <hr/> em <hr>. A seguir a este texto, estará o texto produzido pelo comando XSL:
Veremos mais tarde o que é este texto. Por fim, o interpretador irá adicionar o texto:
A diretiva <xsl:apply-templates select="/simulations/simulation"/> instrui o processador XSL a aplicar o «modelo» ao elemento /simulations/simulation. Será executada sempre que o interpretador XSL encontrar uma tag <simulation>..</simulations> ou <simulation/> dentro de uma tag <simulations>..</simulations> no texto XML analisado. Ao encontrar a tag <simulation>, o interpretador executará as instruções do seguinte modelo:
<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>
Considere as seguintes linhas XML:
A linha <simulation ..> corresponde ao modelo para a instrução XSL <xsl:apply-templates select="/simulations/simulation">. O interpretador XSL tentará, portanto, aplicar as instruções que correspondam a este modelo. Ele encontrará o modelo <xsl:template match="simulation"> e executá-lo-á. Recorde-se que tudo o que não seja um comando XSL é passado sem alterações pelo interpretador XSL, enquanto os comandos XSL são substituídos pelo resultado da sua execução. A instrução XSL <xsl:value-of select="@champ"/> é, assim, substituída pelo valor do atributo «champ» do nó analisado (aqui, um nó <simulation>). A análise da linha XML anterior produzirá o seguinte resultado:
XSL | saída |
<tr><td> | <tr><td> |
<xsl:value-of select="@marie"/> | sim |
</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> |
No total, a linha XML
será convertido na seguinte linha HTML:
Todas estas explicações são um pouco rudimentares, mas agora deve ficar claro para o leitor que o seguinte 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>
acompanhado pela seguinte folha 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>
produz o seguinte 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>
O ficheiro XML simulations.xml, juntamente com a folha de estilo simulations.xsl, quando visualizado num navegador moderno (neste caso, o Netscape 7), é apresentado da seguinte forma:

6.2. Aplicação de cálculo de impostos: versão 6
6.2.1. Os ficheiros XML e as folhas de estilo XSL da aplicação de cálculo de impostos
Voltemos à aplicação web de impostos e modifiquemo-la para que a resposta enviada aos clientes seja em formato XML, em vez de HTML. Esta resposta XML será acompanhada por uma folha de estilo XSL para que os navegadores possam exibi-la. Na secção anterior, apresentámos:
- o ficheiro simulations.xml, que é um protótipo de uma resposta XML contendo simulações de cálculo de impostos
- o ficheiro simulations.xsl, que será a folha de estilo XSL que acompanha esta resposta XML
Temos também de ter em conta o caso de uma resposta que contenha erros. O protótipo para a resposta XML, neste caso, será o seguinte ficheiro 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>
A folha de estilo errors.xsl utilizada para apresentar este documento XML num navegador será a seguinte:
<?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 folha de estilo apresenta um comando XSL ainda não abordado: <xsl:value-of select="."/>. Este comando devolve o valor do nó analisado, neste caso um nó <error>text</error>. O valor deste nó é o texto entre as tags de abertura e de fecho, neste caso «text».
O código errors.xml é transformado pela folha de estilo errors.xsl no seguinte 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>
O ficheiro errors.xml, juntamente com a sua folha de estilo, é apresentado por um navegador da seguinte forma:

6.2.2. O servlet xmlsimulations
Criamos um ficheiro index.html e colocamo-lo no diretório da aplicação impots. A página apresentada é a seguinte:

Este documento HTML é um documento estático. O seu código é o seguinte:
<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>
Note que os dados do formulário são enviados para o URL /impots/xmlsimulations. Esta aplicação é um servlet Java configurado da seguinte forma no ficheiro web.xml da aplicação 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>
- O servlet chama-se xmlsimulations e baseia-se na classe xmlsimulations.class.
- Os seus parâmetros são DSNimpots, admimpots e mdpimpots, que são necessários para aceder à base de dados fiscal. Além disso, aceita dois outros parâmetros:
- xslSimulations, que é o nome do ficheiro de folha de estilo que deve acompanhar a resposta XML contendo as simulações
- xslErrors, que é o nome da folha de estilo que deve acompanhar a resposta XML contendo quaisquer erros
- Tem um alias, xmlsimulations, que o torna acessível através do URL http://localhost:8080/impots/xmlsimulations.
A estrutura do servlet xmlsimulations é semelhante à do servlet simulations já discutido. A principal diferença é que este deve gerar XML em vez de HTML. Isto resultará na remoção dos ficheiros JSP utilizados em aplicações anteriores. A sua principal função era melhorar a legibilidade do código HTML gerado, evitando que este ficasse enterrado no código Java do servlet. Esta função já não é necessária. O servlet tem dois tipos de código XML para gerar:
- um para as simulações
- um para os erros
Apresentámos e analisámos anteriormente os dois tipos de respostas XML a fornecer nestes dois casos, bem como as folhas de estilo que as devem acompanhar. O código do servlet é o seguinte:
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);
}
}
Vamos analisar as principais novidades deste código em comparação com o que já conhecíamos:
- O procedimento init recupera novos parâmetros do ficheiro de configuração web.xml: os nomes das duas folhas de estilo XSL que devem acompanhar a resposta são armazenados nas variáveis xslSimulations e xslErrors. Estas duas folhas de estilo são os ficheiros simulations.xsl e errors.xsl discutidos anteriormente. Estão localizados no diretório da aplicação 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
- O procedimento GET começa por verificar se ocorreu algum erro durante a inicialização. Se for o caso, chama o procedimento sendErrors, que gera a resposta XML adequada para essa situação e, em seguida, termina. A resposta XML inclui uma tag que especifica a folha de estilo a ser utilizada.
- Se não ocorreram erros, o procedimento GET analisa os parâmetros do pedido do cliente. Se encontrar algum erro, reporta-o utilizando o procedimento sendErrors. Caso contrário, calcula a nova simulação, adiciona-a às anteriores armazenadas na sessão atual e termina enviando a sua resposta XML através do procedimento sendSimulations. Este último procede de forma análoga ao procedimento sendErrors.
- Note-se que o servlet declara a sua resposta como do tipo text/xml:
Aqui estão alguns exemplos de execução. O formulário inicial é preenchido da seguinte forma:

A base de dados MySQL não foi iniciada, tornando impossível construir o objeto impots no procedimento init do servlet. A resposta do servlet é, portanto, a seguinte:

O código recebido pelo navegador (Ver/Fonte) é o seguinte:

Se agora executarmos mais duas simulações após iniciar a base de dados MySQL, obtemos o seguinte resultado:

Desta vez, o navegador recebeu o seguinte código:

Note-se que a nossa nova aplicação é mais simples do que antes devido à remoção dos ficheiros JSP. Parte do trabalho anteriormente realizado por estas páginas foi transferido para as folhas de estilo XSL. A vantagem da nossa nova divisão de tarefas é que, uma vez estabelecido o formato XML das respostas do servlet, o desenvolvimento das folhas de estilo é independente do desenvolvimento do servlet.
6.3. Análise de um documento XML em Java
As versões 7 e 8 da nossa aplicação impots serão clientes programados para o servlet xmlsimulations anterior. Estes clientes receberão código XML que terão de analisar para extrair a informação de que necessitam. Vamos agora fazer uma pausa nas nossas várias versões para aprender a analisar um documento XML em Java. Faremos isto utilizando um exemplo incluído no JBuilder 7 chamado MySaxParser. O programa é chamado da seguinte forma:
A aplicação MySaxParser aceita um parâmetro: o URI (Uniform Resource Identifier) do documento XML a ser analisado. No nosso exemplo, este URI será simplesmente o nome de um ficheiro XML localizado no diretório da aplicação MySaxParser. Vamos considerar dois exemplos de execução. No primeiro exemplo, o ficheiro XML a ser analisado é 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>
A análise produz os seguintes 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
Ainda não explicámos o que a aplicação MySaxParser faz, mas aqui podemos ver que ela apresenta a estrutura do documento XML analisado. O segundo exemplo analisa o ficheiro 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>
A análise produz os seguintes 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
A classe MySaxParser contém tudo o que precisamos na nossa aplicação fiscal, uma vez que conseguiu recuperar tanto os erros como as simulações que o servidor web poderia enviar. Vamos examinar o seu 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.*;
// 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
Primeiro, vamos definir uma sigla que aparece frequentemente na análise de documentos XML: SAX, que significa Simple API for XML. Trata-se de um conjunto de classes Java que facilitam o trabalho com documentos XML. Existem duas versões da API: SAX1 e SAX2. A aplicação acima utiliza a API SAX2.
A aplicação importa vários pacotes:
Os dois primeiros vêm com o JDK 1.4, mas o terceiro não. O pacote xerces.jar está disponível no site do Apache Web Server. Vem com o JBuilder 7, bem como com o Tomcat 4.x:

Portanto, se quiser compilar a aplicação anterior fora do JBuilder 7 e tiver o JDK 1.4 e o Tomcat 4.x, pode escrever:
Ao executar a aplicação, faça o mesmo:
dos>java -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\xerces.jar" MySaxParser simulations.xml
A classe MySaxParser estende a classe DefaultHandler. Voltaremos a isso mais tarde. Vamos examinar o código do procedimento principal:
// 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();
}
Para analisar um documento XML, a nossa aplicação necessita de um analisador de código XML.
O analisador XML utilizado é o fornecido pelo pacote xerces.jar. O objeto devolvido é do tipo XMLReader. XMLReader é uma interface, e utilizamos aqui dois dos seus métodos:
indica ao analisador qual o objeto ContentHandler que irá tratar os eventos que este gera durante a análise do documento XML | |
inicia a análise do documento XML passado como parâmetro |
Quando o analisador analisa o documento XML, emite eventos como: «Encontrei o início do documento, o início de uma tag, um atributo de tag, o conteúdo de uma tag, o fim de uma tag, o fim do documento, ...». Passa estes eventos para o objeto ContentHandler que lhe foi fornecido. ContentHandler é uma interface que define os métodos a implementar para tratar todos os eventos que o analisador XML pode gerar. DefaultHandler é uma classe que fornece uma implementação padrão destes métodos. Os métodos implementados em DefaultHandler não fazem nada, mas existem. Quando precisamos de indicar ao analisador qual o objeto que irá tratar os eventos que este gera, utilizando a instrução
, é conveniente passar um objeto do tipo DefaultHandler como parâmetro. Se parássemos por aí, nenhum evento do analisador seria tratado, mas o nosso programa estaria sintaticamente correto. Na prática, passamos um objeto derivado da classe DefaultHandler para o analisador como parâmetro, no qual os métodos que tratam apenas dos eventos que nos interessam são redefinidos. É isso que é feito aqui:
// 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);
Passamos ao analisador uma instância da classe mySaxParser, que é a nossa classe e foi definida anteriormente pela declaração
e começamos a analisar o documento cujo URI foi passado como parâmetro. A partir daí, inicia-se a análise do documento XML. O analisador emite eventos e, para cada um deles, chama um método específico do objeto responsável por lidar com esses eventos — neste caso, o nosso objeto MySaxParser. Este objeto lida com cinco eventos específicos; os restantes são ignorados:
evento emitido pelo analisador | método de tratamento |
void startDocument() | |
void endDocument() | |
public void startElement(String uri, String localName, String qName, Attributes attributes) uri: ? localName: nome do elemento analisado. Se o elemento encontrado for <simulations>, localName será "simulations". qName: nome qualificado pelo namespace do elemento analisado. Um documento XML pode definir um namespace, como XX. O nome qualificado da tag anterior seria, então, XX:simulations. attributes: lista dos atributos da tag | |
public void characters(char[] ch, int start, int length) ch: matriz de caracteres start: índice do primeiro caractere a utilizar na matriz ch length: número de caracteres a retirar da matriz ch O método characters pode ser chamado repetidamente. Para construir o valor de um elemento, usamos um buffer que:
| |
void endElement(String uri, String localName, String qName) Os parâmetros são os mesmos do método startElement. |
O método startElement permite-lhe recuperar os atributos do elemento utilizando o parâmetro attributes do tipo Attributes:
- O número de atributos está disponível em attributes.getLength()
- O nome do atributo i está disponível em attributes.getLocalName(i)
- O valor do atributo i está disponível em attributes.getValue(i)
- o valor do atributo localName em attributes.getValue(localName)
Com isto explicado, o programa anterior e os seus exemplos de execução são autoexplicativos. Foi utilizada uma expressão regular para recuperar os valores dos elementos, de modo a que um texto XML como:
retorna o texto "erro 1" como o valor associado à tag <error>, sem quaisquer espaços e quebras de linha que possam precedê-lo e/ou segui-lo.
6.4. Aplicação de cálculo de impostos: Versão 7
Temos agora todos os elementos para escrever clientes para o nosso serviço fiscal que fornece XML. Utilizaremos a versão 4 da nossa aplicação para o cliente e manteremos a versão 6 para o servidor. Nesta aplicação cliente-servidor:
- O serviço de simulação do cálculo de impostos é gerido pelo servlet xmlsimulations. A resposta do servidor é, portanto, em formato XML, tal como vimos na versão 6.
- O cliente já não é um navegador, mas sim um cliente Java autónomo. A sua interface gráfica é a da versão 4.
Aqui estão alguns exemplos da aplicação em ação. Primeiro, um cenário de erro: o cliente consulta o servlet xmlsimulations mesmo que não tenha conseguido inicializar corretamente porque o SGBD MySQL não estava a funcionar:

Iniciamos o MySQL e executamos algumas simulações:

O cliente nesta nova versão difere do cliente da versão 4 apenas na forma como processa a resposta do servidor. Nada mais muda. Na versão 4, o cliente recebia código HTML, do qual extraía as informações necessárias utilizando expressões regulares. Aqui, o cliente recebe código XML, do qual recupera as informações necessárias utilizando um analisador XML.
Vamos rever os principais passos do procedimento associado ao menu «Calcular» na versão 4 do nosso cliente, uma vez que é aí que as alterações ocorrem principalmente:
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{
....
}
Todo este código permanece válido na nova versão. Apenas o processamento da resposta HTML do servidor (secção em caixa acima) e a sua apresentação precisam de ser substituídos pelo processamento da resposta XML do servidor e a sua apresentação:
// 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;
O que faz o trecho de código acima?
- Cria um analisador XML e passa-lhe o fluxo IN, que contém o código XML enviado pelo servidor. Este fluxo também continha os cabeçalhos HTTP, mas estes já foram lidos e processados. Por conseguinte, apenas permanece a parte XML da resposta. O analisador produz duas listas de cadeias de caracteres: a lista de erros, caso existam, ou a lista de simulações. Estas duas listas são mutuamente exclusivas.
- Se a lista de erros não estiver vazia, as mensagens na lista são concatenadas numa única mensagem de erro, e é lançada uma exceção com essa mensagem como parâmetro. Esta exceção é exibida no procedimento mnuCalculer_actionPerformed que chamou calculerImpots.
- Se a lista de simulações não estiver vazia, é apresentada no componente jList da interface gráfica do utilizador.
Vamos agora explorar o analisador da resposta XML do servidor, um analisador que decorre diretamente do nosso estudo anterior sobre como analisar um documento XML em 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
- O construtor recebe o fluxo IN XML a ser analisado e executa imediatamente essa análise. Assim que esta estiver concluída, o objeto foi construído e as listas (ArrayList) de erros (errorList) e simulações (simulationList) foram criadas. Tudo o que resta para o procedimento que construiu o objeto é recuperar as duas listas utilizando os métodos getErrors e getSimulations.
- Apenas três eventos gerados pelo analisador XML são relevantes aqui:
- o início de um elemento XML, um evento que será tratado pelo procedimento startElement. Este procedimento tratará as tags <simulation marie=".." enfants=".." salaire=".." impot=".."> e <erreur>...</erreur>.
- O valor de um elemento XML, um evento que será tratado pelo procedimento characters.
- O fim de um elemento XML, um evento que será tratado pelo procedimento endElement.
- No procedimento startElement, se estivermos a lidar com o elemento <simulation marie=".." enfants=".." salaire=".." impot="..">, recuperamos os quatro atributos utilizando attributes.getValue("nome do atributo"). Em todos os casos, armazenamos o nome do elemento numa variável element e adicionamo-lo a uma lista (ArrayList) de elementos: elem1, elem2, ..., elemN. Esta lista é gerida como uma pilha, onde o último elemento é o elemento XML atualmente a ser analisado. Quando ocorre o evento «fim do elemento», o último elemento da lista é removido e o novo elemento atual é definido. Isto é feito no procedimento endElement.
- O procedimento characters é idêntico ao estudado num exemplo anterior. Limitamo-nos a verificar se o elemento atual é de facto o elemento <error>, uma precaução que normalmente é desnecessária aqui. Este tipo de precaução também foi tomada no procedimento startElement para verificar se estávamos a lidar com um elemento <simulation>.
6.5. Conclusão
Graças à sua resposta XML, a aplicação impots tornou-se mais fácil de gerir, tanto para o seu criador como para os criadores de aplicações cliente.
- O design da aplicação de servidor pode agora ser confiado a dois tipos de pessoas: o programador Java do servlet e o designer gráfico que irá gerir a aparência da resposta do servidor nos navegadores. Este último precisa apenas de conhecer a estrutura da resposta XML do servidor para criar as folhas de estilo que a acompanharão. Note-se que estas estão contidas em ficheiros XSL separados, independentes do servlet Java. O designer da interface do utilizador pode, portanto, trabalhar independentemente do programador Java.
- Os designers de aplicações cliente também precisam apenas de conhecer a estrutura da resposta XML do servidor. Quaisquer alterações que o designer gráfico possa fazer nas folhas de estilo não têm impacto nesta resposta XML, que permanece sempre a mesma. Esta é uma enorme vantagem.
- Como é que o programador pode atualizar o seu servlet Java sem causar erros? Em primeiro lugar, desde que a resposta XML permaneça inalterada, pode organizar o servlet como quiser. Também pode atualizar a resposta XML, desde que mantenha os elementos <error> e <simulation> esperados pelos seus clientes. Assim, pode adicionar novas tags a esta resposta. O programador front-end irá ter em conta essas tags nas suas folhas de estilo, e os navegadores poderão receber as novas versões da resposta. Os clientes programáticos, no entanto, continuarão a funcionar com o modelo antigo, uma vez que as novas tags são simplesmente ignoradas. Para que isto funcione, as tags que estão a ser procuradas devem ser claramente identificadas na análise XML da resposta do servidor. Foi isto que foi feito no nosso cliente XML para a aplicação fiscal, onde os procedimentos indicavam especificamente que estávamos a processar as tags <error> e <simulation>. Como resultado, as outras tags são ignoradas.