5. XML e PHP
Neste capítulo, faremos uma introdução ao uso de documentos XML (eXtensible Markup Language) com o PHP. Faremos isso no contexto do aplicativo de impostos estudado no capítulo anterior.
5.1. Arquivos XML e folhas de estilo XSL
Consideremos o seguinte arquivo XML, que poderia representar o resultado de simulações:
<?xml version="1.0" encoding="windows-1252"?>
<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 se trata, de fato, do arquivo XML original:

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 (eXtensible 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, que eles poderão exibir:
<?xml version="1.0" encoding="windows-1252"?>
<?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 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, que pode ser exibido. Agora é hora de examinar o conteúdo dessa folha de estilo:
<?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>
<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. Ela deve, entre outras coisas, 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 no Windows:
<?xml version="1.0" encoding="windows-1252"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
O atributo encoding="windows-1252" 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 geram um resultado 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="windows-1252"?>
<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 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 tal tag, 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 ele 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="windows-1252"?>
<?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="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>
<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 texto HTML a seguir:
<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 a seguir
<?xml version="1.0" encoding="windows-1252"?>
<?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>
exibido por um navegador recente (neste caso, o Netscape 7) é então exibido da seguinte forma:

5.2. Aplicativo de impostos: versão 5
5.2.1. Os arquivos XML e as folhas de estilo XSL do aplicativo de impostos
Voltemos ao nosso ponto de partida, que era o aplicativo web de impostos, e lembremos que queremos modificá-lo para que a resposta enviada aos clientes seja no formato XML, em vez de HTML. Essa resposta HTML 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:

5.2.2. O aplicativo xmlsimulations
Criamos um arquivo xmlsimulations.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="/poly/impots/7/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/poly/impots/7/xmlsimulations.php" 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>
Vale ressaltar que os dados do formulário são enviados para o URL /poly/impots/7/xmlsimulations.php. O código do aplicativo xmlsimulations.php é muito semelhante ao do aplicativo impots.php. Recomenda-se ao leitor que consulte este último. Vale lembrar o código de entrada:
<?php
// processar o formulário de impostos
// bibliotecas
include "ImpotsDSN.php";
// início da sessão
session_start();
// configuração do aplicativo
ini_set("register_globals","off");
ini_set("display_errors","off");
$formulaireImpots="impots_form.php";
$erreursImpots="impots_erreurs.php";
$bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
// recuperação dos parâmetros da sessão
$session=$_SESSION["session"];
// sessão válida?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// iniciando uma nova sessão
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// houve erros?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// exibição da página de erros
include $erreursImpots;
// fim
$session=array();
terminerSession($session);
}//se
}//se
// recuperamos os parâmetros da troca em andamento
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// temos todos os parâmetros?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// exibição do formulário vazio
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// fim
terminerSession($session);
}//if
// verificação dos parâmetros
$requête=vérifier($requête);
// há erros?
if(count($requête[erreurs])!=0){
// exibição do formulário
include "$formulaireImpots";
// fim
terminerSession($session);
}//se
// cálculo do imposto a pagar
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// mais uma simulação
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// exibição do formulário
include "$formulaireImpots";
// fim
terminerSession($session);
...
As páginas HTML eram exibidas pelas linhas include "...". Queremos aqui gerar XML e não HTML. Basta criar duas novas aplicações, impots_erreurs.php e impots_simulations.php, para que elas gerem XML em vez de HTML. O restante do aplicativo permanece inalterado. O código passa a ser o seguinte:
<?php
// processa o formulário de impostos
// bibliotecas
include "ImpotsDSN.php";
// início da sessão
session_start();
// configuração do aplicativo
ini_set("register_globals","off");
ini_set("display_errors","off");
$formulaireImpots="xmlsimulations.html";
$erreursImpots="impots_erreurs.php";
$simulationsImpots="impots_simulations.php";
$bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
// recuperação dos parâmetros da sessão
$session=$_SESSION["session"];
// sessão válida?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// iniciando uma nova sessão
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// houve erros?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// exibição da página de erros no formato XML
header("Content-type: text/xml");
include $erreursImpots;
// fim
$session=array();
terminerSession($session);
}//se
}//if
// recuperam-se os parâmetros da troca em andamento
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// temos todos os parâmetros?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// exibição do formulário vazio
include $formulaireImpots;
// fim
terminerSession($session);
}//se
// verificação dos parâmetros
$requête=vérifier($requête);
// há erros?
if(count($requête[erreurs])!=0){
// exibição de erros no formato XML
header("Content-type: text/xml");
include "$erreursImpots";
// fim
terminerSession($session);
}//se
// cálculo do imposto a pagar
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// mais uma simulação
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// exibição das simulações no formato XML
header("Content-type: text/xml");
include "$simulationsImpots";
// fim
terminerSession($session);
Anteriormente, apresentamos e analisamos os dois tipos de resposta XML a serem fornecidas, bem como as folhas de estilo que devem acompanhá-las. O código do aplicativo impots_simulations.php é o seguinte:
<?php
// gera o código XML da página de simulações do aplicativo de impostos
// algumas constantes
$xslSimulations="simulations.xsl";
// cabeçalhos XML
echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslSimulations\" ?>\n";
// as simulações
echo "<simulations>\n";
for ($i=0;$i<count($requête[simulations]);$i++){
// simulação $i
echo "<simulation marie=\"".$requête[simulations][$i][0]."\" ".
"enfants=\"".$requête[simulations][$i][1]."\" ".
"salaire=\"".$requête[simulations][$i][2]."\" ".
"impot=\"".$requête[simulations][$i][3]."\" />\n";
}//para
echo "</simulations>\n";
?>
Esse código permite, a partir do dicionário $requête, gerar o código XML semelhante ao seguinte:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="simulations.xsl" ?>
<simulations>
<simulation marie="non" enfants="3" salaire="200000" impot="22504" />
<simulation marie="oui" enfants="3" salaire="200000" impot="16400" />
<simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
</simulations>
A folha de estilo simulations.xsl transformará este código XML no código HTML.
O código do aplicativo impots_erreurs.php é o seguinte:
<?php
// gera o código XML da página de erros do aplicativo de impostos
// algumas constantes
$xslErreurs="erreurs.xsl";
// cabeçalhos XML
echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslErreurs\" ?>\n";
// os erros
echo "<erreurs>\n";
for ($i=0;$i<count($requête[erreurs]);$i++){
// erro $i
echo "<erreur>".$requête[erreurs][$i]."</erreur>";
}//for
echo "</erreurs>\n";
?>
Esse código permite, a partir do dicionário $requête, gerar o código XML semelhante ao seguinte:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl" ?>
<erreurs>
<erreur>Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)</erreur>
</erreurs>
A folha de estilo erreurs.xsl transformará este código XML no código HTML.
Vejamos um primeiro exemplo:

O SGBD MySQL não é executado. Recebemos, então, a seguinte resposta:

Se analisarmos o código-fonte recebido pelo navegador, temos o seguinte:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl" ?>
<erreurs>
<erreur>Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)</erreur>
</erreurs>
Agora, executamos o SGBD e o MySQL e realizamos várias simulações sucessivas. Obtemos a seguinte resposta:

O código recebido pelo navegador é o seguinte:
<?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" />
<simulation marie="non" enfants="3" salaire="200000" impot="22504" />
<simulation marie="oui" enfants="3" salaire="200000" impot="16400" />
</simulations>
Vale notar que nosso novo aplicativo é mais simples de manter do que o anterior. Parte do trabalho 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, o desenvolvimento das folhas de estilo passa a ser independente do desenvolvimento do aplicativo.
5.3. Análise de um documento XML no formato PHP
A próxima versão do nosso aplicativo de impostos será um cliente programado a partir do aplicativo anterior xmlsimulations. Nosso cliente receberá, portanto, o código XML, que deverá 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 PHP. Faremos isso a partir do seguinte exemplo:
dos>e:\php43\php.exe xmlParser.php
syntaxe : xmlParser.php fichierXML
O aplicativo xmlParser.php 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 xmlParser.php. Vejamos dois exemplos de execução. No primeiro exemplo, o arquivo XML analisado é o seguinte 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>e:\php43\php.exe xmlParser.php erreurs.xml
ERREURS
ERREUR
[erreur 1]
/ERREUR
ERREUR
[erreur 2]
/ERREUR
/ERREURS
Ainda não havíamos explicado o que o aplicativo xmlParser.php fazia, mas vemos aqui que ele exibe a estrutura do documento XML analisado. O segundo exemplo analisa o seguinte 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>e:\php43\php.exe xmlParser.php simulations.xml
SIMULATIONS
SIMULATION,(MARIE,oui) (ENFANTS,2) (SALAIRE,200000) (IMPOT,22504)
/SIMULATION
SIMULATION,(MARIE,non) (ENFANTS,2) (SALAIRE,200000) (IMPOT,33388)
/SIMULATION
/SIMULATIONS
O aplicativo xmlParser.php contém tudo o que precisamos em nosso aplicativo de impostos, pois foi capaz de identificar tanto os erros quanto as simulações que o servidor web poderia enviar. Vamos examinar seu código:
<?php
// sintaxe $0 fichierXML
// exibe a estrutura e o conteúdo do arquivo fichierXML
// verificação da chamada
if(count($argv)!=2){
// mensagem de erro
fwrite(STDERR,"syntaxe : $argv[0] fichierXML\n");
// fim
exit(1);
}//if
// inicializações
$file=$argv[1]; // o arquivo XML
$depth=0; // nível de indentação = profundidade na árvore
// o programa
// cria-se um objeto de análise de texto XML
$xml_parser=xml_parser_create();
// indica-se quais funções devem ser executadas no início e no fim da tag
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual função executar ao encontrar dados
xml_set_character_data_handler($xml_parser,"afficheData");
// abertura do arquivo XML para leitura
if (! ($fp=@fopen($file,"r"))){
fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
exit(2);
}//if
// processamento do arquivo XML em blocos de 4096 bytes
while($data=fread($fp,4096)){
// análise dos dados lidos
if (! xml_parse($xml_parser,$data,feof($fp))){
// ocorreu um erro
fprintf(STDERR,"erreur XML : %s à la ligne %d\n",
xml_error_string(xml_get_error_code($xml_parser)),
xml_get_current_line_number($xml_parser));
// fim
exit(3);
}//if
}//enquanto
// o arquivo foi analisado
// os recursos ocupados pelo analisador XML estão sendo liberados
xml_parser_free($xml_parser);
// fim
exit(0);
// -----------------------------------------------------------
// função chamada ao encontrar uma tag de início
function startElement($parser,$name,$attributs){
global $depth;
// uma sequência de espaços (indentação)
for($i=0;$i<$depth;$i++){
print " ";
}//for
// atributos
$précisions="";
while(list($attrib,$valeur)=each($attributs)){
$précisions.="($attrib,$valeur) ";
}
// exibe-se o nome da tag e os eventuais atributos
if($précisions)
print "$name,$précisions\n";
else print "$name\n";
// mais um nível na árvore
$depth++;
}//startElement
// -----------------------------------------------------------
// a função chamada ao encontrar uma tag de fechamento
function endElement($parser,$name){
// fim de tag
// nível de recuo
global $depth;
$depth--;
// uma sequência de espaços (indentação)
for($i=0;$i<$depth;$i++){
echo " ";
}//for
// o nome da tag
echo "/$name\n";
}//endElement
// -----------------------------------------------------------
// a função de exibição dos dados
function afficheData($parser,$data){
// nível de recuo
global $depth;
// os dados são exibidos
$data=trim($data);
if($data!=""){
// uma sequência de espaços (indentação)
for($i=0;$i<$depth;$i++){
echo " ";
}//for
echo "[$data]\n";
}//se
}//afficheData
?>
Vamos examinar o código relacionado a XML. Para analisar um documento XML, nosso aplicativo precisa de um analisador de código XML, geralmente chamado de “parser”.
Quando o analisador for 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 para métodos que devemos designar a ele:
<?php
...
// indica-se quais funções devem ser executadas no início e no final da tag
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual função executar ao encontrar dados
xml_set_character_data_handler($xml_parser,"afficheData");
evento emitido pelo analisador | método de processamento |
função startElement($parser, $name, $attributs) $parser: o analisador do documento $name: nome do elemento analisado. Se o elemento encontrado for <simulations>, teremos name="simulations". $attributs: lista dos atributos da tag na forma (ATTRIBUT,valor), em que ATTRIBUT é o nome da tag em letras maiúsculas. | |
função afficheData($parser, $data) $parser: o analisador do documento $data: os dados da tag | |
função endElement($parser, $name) os parâmetros são os do método startElement. |
A função startElement permite recuperar os atributos do elemento por meio do parâmetro $attributs. Este é um dicionário dos atributos da tag. Assim, se tivermos a seguinte tag:
o dicionário $attributs será o seguinte: array(marie=>sim, filhos=>2, salário=>200000, imposto=>22504)
Uma vez definidos o analisador e os métodos anteriores, a análise de um documento é feita pela função xml_parse:
<?php
...
// processamento do arquivo XML em blocos de 4096 octetos
while($data=fread($fp,4096)){
// análise dos dados lidos
if (! xml_parse($xml_parser,$data,feof($fp))){
...
}//if
}//while
função xml_parse($parser, $doc, $fin) O analisador $parser analisa o documento $doc. O $doc pode ser uma parte de um documento maior. O parâmetro $fin indica se essa é a última parte (true) ou não (false). Durante a análise do documento $doc, as funções definidas por xml_set_element_handler são chamadas a cada início e fim de tag. A função definida por xml_set_character_data_handler é chamada sempre que o conteúdo de uma tag for obtido. |
Durante a análise do documento XML, podem ocorrer erros, especialmente se o documento XML estiver “mal formado”, por exemplo, devido à omissão de tags de fechamento. Nesse caso, a função xml_parse retorna um valor avaliado como “falso”:
<?php
...
if (! xml_parse($xml_parser,$data,feof($fp))){
// ocorreu um erro
fprintf(STDERR,"erreur XML : %s à la ligne %d\n",
xml_error_string(xml_get_error_code($xml_parser)),
xml_get_current_line_number($xml_parser));
// fim
exit(3);
}//if
função xml_get_error_code($parser) retorna o código do último erro ocorrido -- função xml_error_string($code) retorna a mensagem de erro associada ao código passado como parâmetro -- função xml_get_current_line($parser) retorna o número da linha do documento XML que está sendo analisado pelo analisador |
Quando o documento for analisado, liberamos os recursos alocados ao analisador:
função xml_free($parser) |
Uma vez explicado isso, o programa anterior, acompanhado dos exemplos de execução, torna-se autoexplicativo.
5.4. Aplicativo de impostos: versão 6
Agora temos todos os elementos para escrever um cliente programado 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 5 para o servidor. Nesta aplicação cliente-servidor:
- o serviço de simulação do cálculo de impostos é realizado pelo aplicativo xmlsimulations.php. A resposta do servidor está, portanto, no formato XML, como vimos na versão 5.
- o cliente não é mais um navegador, mas um cliente PHP autônomo. Sua interface de console é a mesma da versão 4.
Recomenda-se ao leitor que releia o código do aplicativo cltImpots.php, que era o cliente programado da versão 4. Este recebia um documento $document do servidor. Naquela época, este era um documento HTML. Agora, trata-se de um documento XML. O documento HTML $document era analisado pela seguinte função:
<?php
...
function getInfos($document){
// $document: documento HTML
// procura-se a lista de erros
// ou a tabela de simulações
// preparação do resultado
$impots[erreurs]=array();
$impots[simulations]=array();
........
return $impots;
}//getInfos
A função recebia o documento HTML $document, analisava-o e retornava um dicionário $impots com dois atributos:
- erros: uma matriz de erros
- simulações: uma tabela de simulações, sendo que cada simulação é, por sua vez, uma tabela com quatro elementos (cônjuge, filhos, salário, imposto).
A aplicação cltImpots.php passa agora a ser cltXmlSimulations.php. Apenas a parte que processa o documento recebido do servidor deve ser alterada para levar em conta o fato de que agora se trata de um documento XML. A função getInfos passa a ser a seguinte:
<?php
...
// --------------------------------------------------------------
function getInfos($document){
// $document: documento XML
// procura-se a lista de erros
// ou a tabela de simulações
global $impots,$balises;
// preparação do resultado
$impots[erreurs]=array();
$impots[simulations]=array();
// tags em andamento
$balises=array();
// cria-se um objeto de análise de texto XML
$xml_parser=xml_parser_create();
// indica-se quais funções devem ser executadas no início e no final da baliza
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual função executar ao encontrar dados
xml_set_character_data_handler($xml_parser,"getData");
// realiza-se a análise de $document
xml_parse($xml_parser,$document);
// libera-se os recursos ocupados pelo analisador XML
xml_parser_free($xml_parser);
// fim
return $impots;
}//getInfos
// -----------------------------------------------------------
// função chamada ao encontrar uma tag de início
function startElement($parser,$name,$attributs){
global $impots,$balise,$balises,$contenu;
// registra-se o nome da tag e seu conteúdo
$balise=strtolower($name);
$contenu="";
// ela é adicionada à pilha de tags
array_push($balises,$balise);
// trata-se de uma simulação?
if($balise=="simulation"){
// registram-se os atributos da simulação
$impots[simulations][]=array($attributs[MARIE],$attributs[ENFANTS],$attributs[SALAIRE],$attributs[IMPOT]);
}//if
}//startElement
// -----------------------------------------------------------
// a função chamada ao encontrar uma baliza de fim
function endElement($parser,$name){
// recupera-se a baliza atual
global $impots,$balises,$contenu;
$balise=array_pop($balises);
// trata-se de uma tag de erro?
if($balise=="erreur"){
// mais um erro
$impots[erreurs][]=trim($contenu);
}//if
}//endElement
// -----------------------------------------------------------
// a função de processamento do conteúdo de uma tag
function getData($parser,$data){
// dados globais
global $balise,$contenu;
// trata-se de uma tag de erro?
if($balise=="erreur"){
// acrescenta-se ao conteúdo da tag atual
$contenu.=$data;
}//if
}//getData
Comentários:
- a função getInfos($document) começa criando um analisador, depois o configura e, por fim, inicia a análise do documento $document:
<?php
...
// cria-se um objeto de análise de texto XML
$xml_parser=xml_parser_create();
// indica quais funções devem ser executadas no início e no final da tag
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual função executar ao encontrar dados
xml_set_character_data_handler($xml_parser,"getData");
// é realizada a análise de $document
xml_parse($xml_parser,$document);
- Após a conclusão da análise, os recursos alocados ao analisador são liberados e o dicionário $impots é devolvido.
<?php
...
// libera-se os recursos ocupados pelo analisador XML
xml_parser_free($xml_parser);
// fim
return $impots;
- a função startElement($parser,$name,$attributs) é chamada a cada início de tag. Ela
- identifica a tag $name em uma tabela de tags $balises. Essa tabela é gerenciada como uma pilha: ao encontrar o símbolo de fim de tag, a última tag empilhada em $balises será retirada da pilha. A tag atual também é registrada em $balise. No dicionário $attributs, encontram-se os atributos da tag encontrada, estando esses atributos em maiúsculas.
- grava os atributos no dicionário global $impots[simulations], caso se trate de uma tag de simulação.
- a função getData($parser,$data) quando o conteúdo $data de uma tag foi recuperado. Aqui, tomamos uma precaução. Em certas API, especialmente em Java, para o processamento de documentos XML, é indicado que essa função pode ser chamada repetidamente, c.a.d. que nem sempre se obtém o conteúdo de uma baliza de uma única vez. Aqui, a documentação PHP não indica essa restrição. Por precaução, acumula-se o valor $data obtido em uma variável global $contenu. Somente ao encontrar o símbolo de fim de tag é que se considerará ter obtido a totalidade do conteúdo da tag. A única tag afetada por esse processamento é a tag <erreur>.
- A função endElement($parser,$name) é chamada a cada fim de tag. Ela é utilizada aqui para alterar o nome da tag atual, removendo a última tag da pilha de tags, e para adicionar o conteúdo da tag <erreur>, que termina na matriz $impots[erreurs].
Aqui estão alguns exemplos de execução, começando com um SGBD que não foi iniciado:
dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php sim 2 200000
Jeton de session=[e8c29ea12f79e4771960068d161229fd]
Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)
Em seguida, com o SGBD executado:
dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php sim 3 200000
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
dos >e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php sim 2 200000 69a54d79db10b70ed0a2d55d5026ac8b
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
[oui,2,200000,22504]
dos >e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php não 2 200000 69a54d79db10b70ed0a2d55d5026ac8b
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
[oui,2,200000,22504]
[non,2,200000,33388]
5.5. Conclusão
Graças à sua resposta XML, o aplicativo de impostos tornou-se mais fácil de gerenciar, tanto para seu desenvolvedor quanto para os desenvolvedores dos aplicativos clientes.
- O projeto do aplicativo servidor agora pode ser confiado a dois tipos de profissionais: o desenvolvedor PHP do servlet e o designer gráfico, que cuidará da aparência da resposta do navegador 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 aplicativo PHP. O designer gráfico pode, portanto, trabalhar independentemente do desenvolvedor.
- 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 fazer sua aplicação PHP evoluir sem causar erros? Em primeiro lugar, desde que sua resposta XML não mude, ele pode organizar seu aplicativo 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.