6. XML e JAVA
Neste capítulo, apresentamos o uso de documentos XML com Java. Faremos isso no contexto do aplicativo de impostos estudado no capítulo anterior.
6.1. Arquivos XML e folhas de estilo XSL
Consideremos o seguinte arquivo XML simulations.xml, que poderia representar o resultado de simulações de cálculos 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>
Se visualizarmos com o IE 6, obtemos o seguinte resultado:

O IE6 reconhece que se trata de um arquivo XML (graças à extensão .xml do arquivo) e o formata de maneira própria. Com o Netscape, obtém-se uma página em branco. No entanto, se observarmos o código-fonte (Exibir/Fonte), vemos que está presente o arquivo original XML:

Por que o Netscape não exibe nada? Porque ele precisa de uma folha de estilo que indique como transformar o arquivo XML no arquivo HTML, que ele poderá então exibir. Acontece que o IE 6 possui uma folha de estilo padrão, enquanto o arquivo XML não oferece nenhuma, o que era o caso aqui.
Existe uma linguagem chamada XSL (eXtended StyleSheet Language) que permite descrever as transformações a serem realizadas para converter um arquivo XML em qualquer arquivo de texto. O XSL permite o uso de diversas instruções e se assemelha bastante às linguagens de programação. Não vamos detalhá-lo aqui, pois isso exigiria várias dezenas de páginas. Vamos simplesmente descrever dois exemplos de folhas de estilo XSL. A primeira é aquela que transformará o arquivo XML simulations.xml em código HTML. Modificamos este último para que ele indique a folha de estilo que os navegadores poderão usar para transformá-lo no documento HTML, documento que eles poderão exibir:
<?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 pedido XML
designa o arquivo simulations.xsl como uma folha de estilo (xml-stylesheet) do tipo text/xsl c.a.d. Um arquivo de texto contendo o código XSL. Essa folha de estilo será utilizada pelos navegadores para transformar o texto XML em um documento HTML. Aqui está o resultado obtido com o Netscape 7 ao carregar o arquivo XML simulations.xml:

Ao verificarmos o código-fonte do documento (Exibir/Fonte), encontramos o documento XML original e não o documento HTML exibido:

O Netscape utilizou a folha de estilo simulations.xsl para transformar o documento XML acima no documento HTML exibível. Agora é hora de examinar o conteúdo dessa 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 arquivo XML e, portanto, segue as mesmas regras. Entre outras coisas, ela deve estar “bem formada”, ou seja, toda tag aberta deve ser fechada.
- O arquivo começa com dois comandos XML que podem ser mantidos 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 o uso de caracteres acentuados na folha de estilo.
- A tag <xsl:output method="html" indent="yes"/> indica ao interpretador XSL que se deseja gerar HTML “indentado”.
- A tag <xsl:template match="elemento"> serve para definir o elemento do documento XML ao qual serão aplicadas as instruções contidas entre <xsl:template ...> e </xsl:template>.
No exemplo acima, o elemento “/” designa a raiz do documento. Isso 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 é inserido tal como está no fluxo de saída. As tags XSL, por sua vez, são executadas. Algumas delas produzem um resultado que é inserido no fluxo de saída. Vejamos o exemplo a seguir:
<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>
Vale lembrar que o documento XML 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>
Desde o início do documento XML analisado (match="/"), o interpretador XSL produzirá como saída 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>
Observe-se que, no texto inicial, havia <hr/> e não <hr>. No texto inicial, não era possível escrever <hr>, pois, embora seja uma tag HTML válida, é uma tag XML inválida. No entanto, estamos lidando aqui com um texto XML que deve ser “bem formado”, ou seja, c.a.d, o que significa que toda tag deve ser fechada. Portanto, escrevemos <hr/> e, como escrevemos <xsl:output text="html ...">, o interpretador XSL transformará o texto <hr/> em <hr>. Após esse texto, virá o texto produzido pelo comando XSL:
Veremos mais adiante qual é esse texto. Por fim, o interpretador adicionará o texto:
O comando <xsl:apply-templates select="/simulations/simulation"/> solicita que seja executado o “template” (modelo) do elemento /simulations/simulation. Ela será executada sempre que o interpretador XSL encontrar, no texto XML analisado, uma tag <simulation>..</simulations> ou <simulation/> dentro de uma tag <simulations>..</simulations>. Ao encontrar a tag <simulation>, o interpretador executará as instruções do modelo a seguir:
<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 as seguintes linhas XML:
A linha <simulation ..> corresponde ao modelo da instrução XSL <xsl:apply-templates select="/simulations/simulation>". O interpretador XSL irá, portanto, procurar aplicar a ela as instruções que correspondem a esse modelo. Ele encontrará o modelo <xsl:template match="simulation"> e o executará. Vale lembrar que o que não for um comando XSL é reproduzido tal como está pelo interpretador XSL, e que os comandos XSL são substituídos pelo resultado de sua execução. A instrução XSL <xsl:value-of select="@champ"/> é, assim, substituída pelo valor do atributo “champ” do nó analisado (neste caso, 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="@enfants"/> | 2 |
</td><td> | </td><td> |
<xsl:value-of select="@salário"/> | 200000 |
</td><td> | </td><td> |
<xsl:value-of select="@impot"/> | 22.504 |
</td></tr> | </td></tr> |
No total, a linha XML
será transformada na linha HTML:
Todas essas explicações são um pouco rudimentares, mas agora deve ficar claro para o leitor que o texto XML a seguir:
<?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>
gera 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 arquivo XML simulations.xml, acompanhado da folha de estilo simulations.xsl, quando aberto em um navegador recente (neste caso, o Netscape 7), é exibido da seguinte forma:

6.2. Aplicativo de impostos: versão 6
6.2.1. Os arquivos XML e as folhas de estilo XSL do aplicativo de impostos
Voltemos ao aplicativo web de impostos e modifiquemo-lo para que a resposta enviada aos clientes seja no formato XML, em vez de HTML. Essa resposta XML será acompanhada por uma folha de estilo XSL para que os navegadores possam exibi-la. No parágrafo anterior, apresentamos:
- o arquivo simulations.xml, que é o protótipo de uma resposta XML contendo simulações de cálculos de impostos
- o arquivo simulations.xsl, que será a folha de estilo XSL que acompanhará essa resposta XML
Também precisamos prever o caso de uma resposta com erros. O protótipo da resposta XML, nesse caso, será o seguinte arquivo 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>
A folha de estilo erreurs.xsl, que permite exibir este documento XML em um 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 introduz um comando XSL ainda não encontrado: <xsl:value-of select="."/>. Esse comando gera como saída o valor do nó analisado, neste caso, um nó <erreur>texte</erreur>. O valor desse nó é o texto contido entre as duas tags de abertura e fechamento, neste caso, texte.
O código erreurs.xml é transformado pela folha de estilo erreurs.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 arquivo erreurs.xml, acompanhado de sua folha de estilo, é exibido por um navegador da seguinte maneira:

6.2.2. O servlet xmlsimulations
Criamos um arquivo index.html, que colocamos no diretório do aplicativo impots. A página exibida é a seguinte:

Este documento HTML é um documento estático. Seu código é o seguinte:
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// Limpar o formulário
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//com
}//apagar
function calculer(){
// verificação dos parâmetros antes de enviá-los ao servidor
with(document.frmImpots){
//número de filhos
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// o modelo não foi verificado
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//se
//salário
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// o modelo não foi verificado
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//se
// tudo certo — vamos enviar
submit();
}//com
}//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>
Observe que os dados do formulário são enviados para o URL /impostos/xmlsimulações. Essa aplicação é um servlet Java configurado da seguinte maneira no arquivo 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>
- A servlet se chama xmlsimulations e se baseia na classe xmlsimulations.class.
- Ela possui como parâmetros os parâmetros DSNimpots, admimpots e mdpimpots, necessários para acessar o banco de dados de impostos. Além disso, ela aceita dois outros parâmetros:
- xslSimulations, que é o nome do arquivo de estilo que deve acompanhar a resposta XML contendo as simulações
- xslErreurs, que é o nome do arquivo de estilo que deve acompanhar a resposta XML, contendo eventuais erros
- ela possui um alias xmlsimulations que a torna acessível por meio do URL http://localhost:8080/impots/xmlsimulations.
A estrutura da servlet xmlsimulations é semelhante à da servlet simulations já estudada. A principal diferença reside no fato de que ela deve gerar XML em vez de HTML. Isso implicará na remoção dos arquivos JSP utilizados nas aplicações anteriores. Sua principal função era melhorar a legibilidade do código HTML gerado, evitando que ele ficasse perdido no código Java da servlet. Essa função agora não tem mais razão de ser. A servlet tem dois tipos de código XML a serem gerados:
- aquele para as simulações
- aquele para erros
Anteriormente, apresentamos e estudamos os dois tipos de resposta XML a serem fornecidas nesses dois casos, bem como as folhas de estilo que devem acompanhá-las. O código da 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{
// variáveis de instância
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{
// recuperamos o fluxo de gravação para o cliente
PrintWriter out=response.getWriter();
// especifica-se o tipo da resposta
response.setContentType("text/xml");
// a lista de erros
ArrayList erreurs=new ArrayList();
// a inicialização ocorreu corretamente?
if(msgErreur!=null){
// Concluído — enviamos a resposta com erros ao servidor
erreurs.add(msgErreur);
sendErreurs(out,xslErreurs,erreurs);
// concluído
return;
}
// recuperando as simulações anteriores da sessão
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// recuperando os parâmetros da consulta atual
String optMarie=request.getParameter("optMarie"); // estado civil
String txtEnfants=request.getParameter("txtEnfants"); // número de filhos
String txtSalaire=request.getParameter("txtSalaire"); // salário anual
// estão todos os parâmetros esperados?
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// faltam parâmetros
// enviamos a resposta com erros
erreurs.add("Demande incomplète. Il manque des paramètres");
sendErreurs(out,xslErreurs,erreurs);
// concluído
return;
}
// temos todos os parâmetros — estamos verificando-os
// estado civil
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// erro
erreurs.add("Etat marital incorrect");
}
// número de filhos
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// erro
erreurs.add("Nombre d'enfants incorrect");
}
// salário
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// erro
erreurs.add("Salaire incorrect");
}
if(erreurs.size()!=0){
// se houver erros, eles serão sinalizados
sendErreurs(out,xslErreurs,erreurs);
}else{
// sem erros
try{
// é possível calcular o imposto a pagar
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
String txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// adiciona-se o resultado atual às simulações anteriores
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// envia-se a resposta com as simulações
sendSimulations(out,xslSimulations,simulations);
}catch(Exception ex){}
}//if-else
// reinsere-se a lista de simulações na sessão
session.setAttribute("simulations",simulations);
}//GET
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}//POST
//-------- INIT
public void init(){
// recuperando os parâmetros de inicialização
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 corretos?
if(xslSimulations==null || DSNimpots==null || admimpots==null || mdpimpots==null){
msgErreur="Configuration incorrecte";
return;
}
// criando uma instância de impotsJDBC
try{
impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){
msgErreur=ex.getMessage();
}
}//inicialização
//-------- 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";
// envia-se a resposta
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++){
// simulação 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";
// envia-se a resposta
out.println(réponse);
}
}
Vamos detalhar as principais novidades desse código em relação ao que já conhecíamos:
- o procedimento `init` recupera novos parâmetros do arquivo 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 xslErreurs. Essas duas folhas de estilo são os arquivos simulations.xsl e erreurs.xsl analisados anteriormente. Elas estão localizadas no diretório do aplicativo 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 verificando se houve algum erro durante a inicialização. Se houver, ele chama o procedimento sendErreurs, que gera a resposta XML adequada a esse caso e, em seguida, encerra-se. Nessa resposta, é inserida a instrução que indica a folha de estilo a ser utilizada.
- Se não houver erros, o procedimento GET analisa os parâmetros da solicitação do cliente. Se encontrar algum erro, ele o sinaliza utilizando, também nesse caso, o procedimento sendErreurs. Caso contrário, ela calcula a nova simulação, adiciona-a às anteriores armazenadas na sessão atual e conclui enviando sua resposta XML por meio do procedimento sendSimulations. Este último procede de maneira análoga ao procedimento sendErreurs.
- Observe-se que o servlet anuncia sua resposta como sendo do tipo text/xml:
Aqui estão alguns exemplos de execução. O formulário inicial é preenchido da seguinte maneira:

O banco de dados MySQL não foi iniciado, impossibilitando a construção do objeto impots no procedimento init do servlet. A resposta deste é, então, a seguinte:

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

Se agora repetirmos duas simulações após ter iniciado o banco de dados MySQL, obtemos o seguinte resultado:

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

Observe-se que nosso novo aplicativo está mais simples do que antes, devido à remoção dos arquivos JSP. Parte do trabalho realizado por essas páginas foi transferida para as folhas de estilo XSL. A vantagem dessa nova divisão de tarefas é que, uma vez definido o formato XML das respostas do servlet, o desenvolvimento das folhas de estilo passa a ser independente do desenvolvimento do servlet.
6.3. Análise de um documento XML em Java
As versões 7 e 8 do nosso aplicativo de impostos serão clientes programados do servlet anterior xmlsimulations. Esses clientes receberão o código XML, que deverão analisar para extrair as informações de seu interesse. Vamos fazer aqui uma pausa em nossas diferentes versões e aprender como se pode analisar um documento XML em Java. Faremos isso a partir de um exemplo fornecido com o JBuilder 7, chamado MySaxParser. O programa tem o seguinte nome:
A aplicação MySaxParser aceita um parâmetro: o URI (Uniform Resource Identifier) do documento XML a ser analisado. No nosso exemplo, esse URI será simplesmente o nome de um arquivo XML localizado no diretório do aplicativo MySaxParser. Vejamos dois exemplos de execução. No primeiro exemplo, o arquivo XML analisado é o arquivo 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>
A análise apresenta 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 havíamos explicado o que o aplicativo MySaxParser fazia, mas vemos aqui que ele exibe a estrutura do documento XML analisado. O segundo exemplo analisa o arquivo 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 apresenta 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 em nosso aplicativo de impostos, pois ela foi capaz de identificar tanto os erros quanto as simulações que o servidor web poderia enviar. Vamos examinar 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.*;
// a classe
public class MySaxParser extends DefaultHandler {
// valor de um elemento da árvore XML
private StringBuffer valeur=new StringBuffer();
// uma expressão regular do valor de um elemento quando se deseja ignorar
// os “espaços” que o precedem ou seguem
private static Pattern ptnValeur=null;
private static Matcher résultats=null;
// -------- principal
public static void main(String[] argv) {
// verificação do número de parâmetros
if (argv.length != 1) {
System.out.println("Usage: java MySaxParser [URI]");
System.exit(0);
}
// recupera-se o URI do arquivo XML a ser analisado
String uri = argv[0];
try {
// criação de um analisador XML (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// indica-se ao analisador o objeto que implementará os métodos
// startDocument, endDocument, startElement, endElement, caracteres
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// inicializa-se o modelo de valor de um elemento
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// indica-se ao analisador o documento XML a ser analisado
parser.parse(uri);
}
catch(Exception ex) {
// erro
System.err.println("Erreur : " + ex);
// log
ex.printStackTrace();
}
}//main
// -------- startDocument
public void startDocument() throws SAXException {
// procedimento chamado quando o analisador encontra o início do documento
System.out.println("Début du document");
}//startDocument
// -------- endDocument
public void endDocument() throws SAXException {
// procedimento chamado quando o analisador encontra o fim do documento
System.out.println("Fin du document");
}//endDocument
// -------- startElement
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// procedimento chamado pelo analisador quando ele encontra o início de uma tag
// URI: URI do documento analisado?
// localName: nome do elemento que está sendo analisado
// qName: o mesmo, mas “qualificado” por um namespace, caso exista
// atributos: lista dos atributos do elemento
// rastreamento
System.out.println("Début élément <"+localName+">");
// o elemento possui 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 {
// procedimento chamado repetidamente pelo analisador quando ele encontra texto
// entre duas tags <tag>texto</tag>
// o texto está em ch a partir do caractere start por length caracteres
// o texto é adicionado ao buffer valor
valeur.append(ch, start, length);
}//caracteres
// -------- endElement
public void endElement(String uri, String localName, String qName)
throws SAXException {
// procedimento chamado pelo analisador quando ele encontra o fim de uma tag
// URI: URI do documento analisado?
// localName: nome do elemento que está sendo analisado
// qName: o mesmo, mas “qualificado” por um namespace, caso exista
// exibe-se o valor do 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)+"]");
}//se
// define-se o valor do elemento como vazio
valeur.setLength(0);
// seguido de
System.out.println("Fin élément <"+localName+">");
}//endElement
}//classe
Vamos, antes de mais nada, definir uma sigla que aparece com frequência 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 do API: SAX1 e SAX2. O aplicativo acima utiliza o API SAX2.
O aplicativo importa uma série de pacotes:
Os dois primeiros vêm com o JDK 1.4, o terceiro não. O pacote xerces.jar está disponível no site do servidor Web Apache. Ele vem com o JBuilder 7, mas também com o Tomcat 4.x:

Portanto, se quisermos compilar a aplicação anterior fora do JBuilder 7 e tivermos o JDK 1.4 e o Tomcat 4.x, poderemos escrever:
Na execução, faremos o mesmo:
dos>java -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\xerces.jar" MySaxParser simulations.xml
A classe MySaxParser deriva da classe DefaultHandler. Voltaremos a esse assunto. Vamos analisar o código da procedimento main:
// recupera-se o URI do arquivo XML a ser analisado
String uri = argv[0];
try {
// criação de um analisador XML (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// indica-se ao analisador o objeto que implementará os métodos
// startDocument, endDocument, startElement, endElement, caracteres
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// inicializa-se o modelo de valor de um elemento
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// indica-se ao analisador o documento XML a ser analisado
parser.parse(uri);
}
catch(Exception ex) {
// erro
System.err.println("Erreur : " + ex);
// log
ex.printStackTrace();
}
Para analisar um documento XML, nosso aplicativo precisa de um analisador de código XML chamado “parser”.
O analisador XML utilizado é aquele fornecido pelo pacote xerces.jar. O objeto recuperado é do tipo XMLReader. XMLReader é uma interface da qual utilizamos aqui dois métodos:
indica ao analisador o objeto do tipo ContentHandler que irá processar os eventos que ele irá gerar durante a análise do documento XML | |
inicia a análise do documento XML passado como parâmetro |
Quando o analisador analisar o documento XML, ele emitirá 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, ...”. Ele transmite esses eventos ao objeto ContentHandler que lhe foi fornecido. ContentHandler é uma interface que define os métodos a serem implementados para processar todos os eventos que o analisador XML pode gerar. DefaultHandler é uma classe que fornece uma implementação padrão desses métodos. Os métodos implementados em DefaultHandler não realizam nenhuma ação, mas existem. Quando é necessário indicar ao analisador qual objeto irá processar os eventos que ele irá gerar por meio da instrução
, é prático passar como parâmetro um objeto do tipo DefaultHandler. Se ficássemos por aí, nenhum evento do analisador seria tratado, mas nosso programa seria sintaticamente correto. Na prática, passa-se como parâmetro ao analisador um objeto derivado da classe DefaultHandler, no qual são redefinidos os métodos que tratam apenas dos eventos que nos interessam. É isso que é feito aqui:
// indica-se ao analisador o objeto que implementará os métodos
// startDocument, endDocument, startElement, endElement, caracteres
MySaxParser MySaxParserInstance = new MySaxParser();
parser.setContentHandler(MySaxParserInstance);
// indica-se ao analisador o documento XML a ser analisado
parser.parse(uri);
Passamos ao analisador uma instância da classe mySaxParser, que é a nossa classe e que foi definida anteriormente pela declaração
e iniciamos a análise do documento, passando o URI como parâmetro. A partir daí, começa 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 processar esses eventos, neste caso, nosso objeto MySaxParser. Este processa cinco eventos específicos, sendo os demais ignorados:
evento emitido pelo analisador | método de processamento |
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>, teremos localName="simulações". qName: nome qualificado por um espaço de nomes do elemento analisado. Um documento XML pode definir um espaço de nomes, como XX, por exemplo. O nome qualificado da tag anterior seria, então, XX:simulations. attributes: lista de atributos da tag | |
public void characters(char[] ch, int start, int length) ch: matriz de caracteres start: índice do primeiro caractere a ser utilizado na matriz ch length: número de caracteres a serem retirados da matriz ch O método characters pode ser chamado repetidamente. Para construir o valor de um elemento, utiliza-se então 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 recuperar os atributos do elemento por meio do 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 de nome localName em attributes.getValue(localName)
Uma vez explicado isso, o programa anterior, acompanhado dos exemplos de execução, torna-se autoexplicativo. Foi utilizada uma expressão regular para recuperar os valores dos elementos, de modo que um texto XML como:
resulte no valor associado à tag <erreur>, que é o texto “erro 1”, sem os espaços e quebras de linha que possam precedê-lo e/ou segui-lo.
6.4. Aplicativo de impostos: versão 7
Agora temos todos os elementos para escrever clientes programados para nosso serviço de impostos, que gera o XML. Retomamos a versão 4 de nosso aplicativo para criar o cliente e mantemos a versão 6 para o servidor. Nesta aplicação cliente-servidor:
- o serviço de simulações do cálculo de impostos é realizado pelo servlet xmlsimulations. A resposta do servidor está, portanto, no formato XML, como vimos na versão 6.
- o cliente não é mais um navegador, mas um cliente Java autônomo. Sua interface gráfica é a da versão 4.
Aqui estão alguns exemplos de execução. Primeiro, um caso de erro: o cliente consulta o servlet xmlsimulations, embora este não tenha conseguido se inicializar corretamente, pois o SGBD MySQL não havia sido iniciado:

Iniciamos o MySQL e realizamos algumas simulações:

O cliente desta 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 o código HTML, no qual buscava as informações de seu interesse usando expressões regulares. Aqui, o cliente recebe o código XML, no qual ele recupera as informações de seu interesse com a ajuda de um analisador XML.
Vamos relembrar as principais etapas do procedimento relacionado ao menu “Calcular” da versão 4 do nosso cliente, já que é principalmente ali que as alterações são feitas:
void mnuCalculer_actionPerformed(ActionEvent e) {
....
try{
// calcula-se o imposto
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// exibe-se o erro
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 do imposto
// urlImpots: URL da Receita Federal
// casado: true se for casado, false caso contrário
// nbEnfants: número de filhos
// salário: salário anual
// retira-se de urlImpots as informações necessárias para a conexão com o servidor da Receita Federal
....
try{
// conecta-se ao servidor
....
// criam-se os fluxos de entrada e saída do cliente TCP
....
// solicita-se o URL - envio dos cabeçalhos HTTP
....
// lê-se a primeira linha da resposta
....
// lê-se a resposta até o final dos cabeçalhos, procurando por um eventual cookie
while((réponse=IN.readLine())!=null){
.... }//enquanto
// os cabeçalhos terminaram HTTP — passa-se para o código HTML
// para recuperar as simulações
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// acabou
....
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
....
}
Todo esse código permanece válido na nova versão. Apenas o processamento da resposta HTML do servidor (parte destacada acima) e sua exibição devem ser substituídos pelo processamento da resposta XML do servidor e sua exibição:
// concluído para os cabeçalhos HTTP — passamos para o código XML
// para recuperar as simulações ou os erros
ImpotsSaxParser parseur=new ImpotsSaxParser(IN);
ArrayList listeErreurs=parseur.getErreurs();
ArrayList listeSimulations=parseur.getSimulations();
// encerramento da conexão com o servidor
client.close();
// limpeza da lista de exibição
simulations.clear();
// erros
if(listeErreurs.size()!=0){
// concatenamos todos os erros
String msgErreur="Le serveur a signalé les erreurs suivantes :\n";
for(int i=0;i<listeErreurs.size();i++){
msgErreur+=" - "+(String)listeErreurs.get(i);
}
// exibição de erros
throw new Exception(msgErreur);
}//if
// simulações
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
return;
O que faz o trecho de código acima?
- Ela cria um analisador XML e passa a ele o fluxo IN, que contém o código XML enviado pelo servidor. Esse fluxo também continha os cabeçalhos HTTP, mas estes já foram lidos e processados. Portanto, resta apenas a parte XML da resposta. O analisador gera duas listas de cadeias de caracteres: a lista de erros, caso haja algum, ou, caso contrário, a lista de simulações. Essas duas listas são mutuamente exclusivas.
- Se a lista de erros não estiver vazia, as mensagens contidas na lista são concatenadas em uma única mensagem de erro e uma exceção é lançada com essa mensagem como parâmetro. Essa exceção é exibida no procedimento mnuCalculer_actionPeformed, que chamou o calculerImpots.
- Se a lista de simulações não estiver vazia, ela é exibida no componente jList da interface gráfica.
Vamos agora conhecer o analisador da resposta XML do servidor, analisador que decorre diretamente do estudo que realizamos anteriormente 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.*;
// a classe
public class ImpotsSaxParser extends DefaultHandler {
// valor de um elemento da árvore XML
private StringBuffer valeur=new StringBuffer();
// uma expressão regular do valor de um elemento quando se deseja ignorar
// os “espaços” que o precedem ou o seguem
private Pattern ptnValeur=null;
private Matcher résultats=null;
// as 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{
// criação de um analisador XML (parser)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// indica-se ao analisador o objeto que implementará os métodos
// startDocument, endDocument, startElement, endElement, caracteres
parser.setContentHandler(this);
// inicializa-se o modelo de valor de um elemento
ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// inicialmente, não há nenhum elemento XML ativo
éléments.add("");
// analisa-se o documento
parser.parse(new InputSource(IN));
}//construtor
// -------- startElement
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// procedimento chamado pelo analisador quando ele encontra o início de uma tag
// URI: URI do documento analisado?
// localName: nome do elemento que está sendo analisado
// qName: o mesmo, mas “qualificado” por um namespace, caso exista
// attributes: lista dos atributos do elemento
// observa-se o nome do elemento
élément=localName.toLowerCase();
éléments.add(élément);
// o elemento possui atributos?
if(élément.equals("simulation") && attributes.getLength()==4){
// trata-se de uma simulação — recuperam-se os atributos
String simulation=attributes.getValue("marie")+","+
attributes.getValue("enfants")+","+
attributes.getValue("salaire")+","+
attributes.getValue("impot");
// adiciona-se a simulação à lista de simulações
listeSimulations.add(simulation);
}//if
}//startElement
// -------- caracteres
public void characters(char[] ch, int start, int length) throws SAXException {
// procedimento chamado repetidamente pelo analisador quando ele encontra texto
// entre duas tags <tag>texto</tag>
// o texto está em ch a partir do caractere start por length caracteres
// o texto é adicionado ao buffer valor se for o elemento erro
if (élément.equals("erreur"))
valeur.append(ch, start, length);
}//caracteres
// -------- endElement
public void endElement(String uri, String localName, String qName)
throws SAXException {
// procedimento chamado pelo analisador quando ele encontra o fim de uma tag
// URI: URI do documento analisado?
// localName: nome do elemento que está sendo analisado
// qName: o mesmo, mas “qualificado” por um namespace, caso exista
// caso de erro
if(élément.equals("erreur")){
// recuperamos o valor do elemento de erro
String strValeur=valeur.toString();
// removemos os “espaços” desnecessários e o registramos na lista de
// erros, caso ela não esteja vazia
résultats=ptnValeur.matcher(strValeur);
if (résultats.find() && ! résultats.group(1).equals("")){
listeErreurs.add(résultats.group(1));
}//if
}
// coloca-se o valor do elemento como vazio
valeur.setLength(0);
// reinicializa-se o nome do 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;
}
}//classe
- O construtor recebe o fluxo XML IN para análise e realiza essa análise imediatamente. Concluída a análise, o objeto foi criado, assim como as listas (ArrayList) de erros (listeErreurs) e de simulações (listeSimulations). Resta agora ao procedimento que criou o objeto recuperar as duas listas por meio dos métodos getErreurs e getSimulations.
- Apenas três eventos gerados pelo analisador XML nos interessam aqui:
- início de um elemento XML, evento que será processado pela rotina startElement. Este procedimento deverá processar as tags <simulation marie=".." enfants=".." salaire=".." impot=".."> e <erreur>...</erreur>.
- valor de um elemento XML, evento que será processado pela rotina characters.
- fim de um elemento XML, evento que será processado pelo procedimento endElement.
- no procedimento startElement, se estivermos lidando com o elemento <simulation marie=".." enfants=".." salaire=".." impot="..">, recuperamos os quatro atributos por meio de attributes.getValue("nome do atributo"). Em todos os casos, armazena-se o nome do elemento em uma variável `elemento` e adiciona-se a uma lista (ArrayList) de elementos: elem1, elem2, ..., elemn. Essa lista é gerenciada como uma pilha, cujo último elemento é o elemento XML que está sendo analisado. Quando ocorre o evento “fim de elemento”, o último elemento da lista é removido e o novo elemento atual é modificado. Isso é feito no procedimento endElement.
- O procedimento characters é idêntico ao que foi analisado em um exemplo anterior. Basta apenas verificar se o elemento atual é realmente o elemento <erreur>, precaução que normalmente seria desnecessária neste caso. Esse tipo de precaução também havia sido tomado no procedimento startElement para verificar se se tratava de um elemento <simulation>.
6.5. Conclusão
Graças à sua resposta XML, o aplicativo impots tornou-se mais fácil de gerenciar tanto para seu desenvolvedor quanto para os desenvolvedores dos aplicativos clientes.
- A concepção do aplicativo servidor agora pode ser confiada a dois tipos de profissionais: o desenvolvedor Java do servlet e o designer gráfico, que irá gerenciar a aparência da resposta do servidor nos navegadores. Basta que este último conheça a estrutura da resposta XML do servidor para criar as folhas de estilo que a acompanharão. Vale lembrar que essas folhas de estilo são arquivos XSL separados e independentes do servlet Java. O designer gráfico pode, portanto, trabalhar independentemente do desenvolvedor Java.
- Os desenvolvedores de aplicativos clientes também precisam apenas conhecer a estrutura da resposta XML do servidor. As alterações que o designer gráfico possa fazer nas folhas de estilo não têm qualquer repercussão nessa resposta XML, que permanece sempre a mesma. Essa é uma enorme vantagem.
- Como o desenvolvedor pode atualizar seu servlet Java sem causar problemas? Em primeiro lugar, desde que sua resposta XML não mude, ele pode organizar seu servlet como quiser. Ele também pode atualizar a resposta XML, desde que mantenha os elementos <erro> e <simulação> esperados por seus clientes. Assim, ele pode adicionar novas tags a essa resposta. O web designer levará isso em consideração em suas folhas de estilo, e os navegadores poderão receber as novas versões da resposta. Já os clientes programados continuarão funcionando com o modelo antigo, sendo que as novas tags serão simplesmente ignoradas. Para que isso seja possível, é necessário que, na análise XML da resposta do servidor, as tags procuradas sejam devidamente identificadas. Foi isso que foi feito em nosso cliente XML do aplicativo de impostos, onde, nos procedimentos, especificava-se que se tratavam as tags <erreur> e <simulation>. Assim, as demais tags são ignoradas.