5. XML e PHP
Neste capítulo, faremos uma introdução à utilização de documentos XML (eXtensible Markup Language) com o PHP. Faremos isso no contexto da aplicação de impostos analisada no capítulo anterior.
5.1. Ficheiros XML e folhas de estilo XSL
Consideremos o seguinte ficheiro 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 o visualizarmos com o IE 6, obtemos o seguinte resultado:

O
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 (View/Source), verifica-se que existe efetivamente o ficheiro XML original:

Por que razão o Netscape não exibe nada? Porque necessita de uma folha de estilo que lhe indique como transformar o ficheiro XML num ficheiro HTML, que poderá então exibir. Acontece que o IE 6 tem uma folha de estilo por predefinição, enquanto o ficheiro XML não a possui, o que era o caso aqui.
Existe uma linguagem chamada XSL (eXtensible StyleSheet Language) que permite descrever as transformações a efetuar para converter um ficheiro XML num ficheiro de texto qualquer. O XSL permite a utilização de numerosas instruções e assemelha-se muito às linguagens de programação. Não o iremos detalhar aqui, pois seriam necessárias várias dezenas de páginas. Vamos simplesmente descrever dois exemplos de folhas de estilo XSL. A primeira é aquela que irá transformar o ficheiro XML simulations.xml em código HTML. Alteramos este último para que indique a folha de estilo que os navegadores poderão utilizar para o transformar no documento HTML, que poderão apresentar:
<?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 ficheiro simulations.xsl como uma folha de estilo (xml-stylesheet) do tipo text/xsl c.a.d. Um ficheiro de texto contendo código XSL. Esta folha de estilo será utilizada pelos navegadores para transformar o texto XML num documento HTML. Eis o resultado obtido com o Netscape 7 ao carregar o ficheiro XML simulations.xml:

Quando analisamos o código-fonte do documento (View/Source), encontramos o documento XML inicial e não o documento HTML apresentado:

O Netscape utilizou a folha de estilo simulations.xsl para transformar o documento XML acima num documento HTML que pode ser visualizado. Chegou agora a altura de analisar o conteúdo desta 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 ficheiro XML e, como tal, segue as mesmas regras. Deve, entre outras coisas, estar «bem formado», ou seja, todas as balizas abertas devem ser fechadas.
- O ficheiro 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 utilizar caracteres acentuados na folha de estilo.
- A baliza <xsl:output method="html" indent="yes"/> indica ao interpretador XSL que se pretende produzir HTML «indentado».
- A baliza <xsl:template match="elemento"> serve para definir o elemento do documento XML ao qual serão aplicadas as instruções que se encontram entre <xsl:template ...> e </xsl:template>.
No exemplo acima, o elemento «/» designa a raiz do documento. Isto significa que, assim que for encontrado o início do documento XML, os comandos XSL situados entre as duas balizas serão executados.
- Tudo o que não for uma baliza XSL é inserido tal como está no fluxo de saída. As balizas XSL, por sua vez, são executadas. Algumas delas produzem um resultado no fluxo de saída. Vejamos 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>
Recorde-se 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 irá 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>
Note-se que no texto inicial aparecia <hr/> e não <hr>. No texto inicial não era possível escrever <hr>, que, embora seja uma baliza HTML válida, é uma baliza XML inválida. No entanto, estamos aqui perante um texto XML que deve ser «bem formado», ou seja, c.a.d, o que significa que todas as etiquetas devem ser fechadas. Escrevemos, portanto, <hr/> e, como escrevemos <xsl:output text="html ...">, o interpretador transformará o texto <hr/> em <hr>. A seguir a este texto, virá o texto produzido pelo comando XSL:
Veremos mais tarde qual é esse texto. Por fim, o interpretador irá adicionar o texto:
O comando <xsl:apply-templates select="/simulations/simulation"/> solicita a execução do «template» (modelo) do elemento /simulations/simulation. Será executada sempre que o interpretador XSL encontrar, no texto XML analisado, uma baliza <simulation>..</simulations> ou <simulation/> dentro de uma baliza <simulations>..</simulations>. Ao encontrar uma tag deste tipo, o interpretador executará as instruções do modelo seguinte:
<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-lhe as instruções que correspondem a este modelo. Irá encontrar o modelo <xsl:template match="simulation"> e executá-lo. Recorde-se que o que não for um comando XSL é reproduzido tal como está pelo interpretador XSL e que os comandos XSL são, por sua vez, 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 (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"/> | 22504 |
</td></tr> | </td></tr> |
No total, a linha XML
será transformada na linha HTML:
Todas estas explicações são um pouco rudimentares, mas agora deve ficar claro para o leitor que o texto XML seguinte:
<?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 da 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 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 seguinte
<?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>
lido por um navegador recente (neste caso, o Netscape 7) é então apresentado da seguinte forma:

5.2. Aplicação de impostos: versão 5
5.2.1. Os ficheiros XML e as folhas de estilo XSL da aplicação de impostos
Voltemos ao nosso ponto de partida, que era a aplicação web de impostos, e recordemos que pretendemos alterá-la para que a resposta enviada aos clientes seja no formato XML, em vez de HTML. Esta resposta HTML será acompanhada por uma folha de estilo XSL para que os navegadores a possam apresentar. No parágrafo anterior, apresentámos:
- o ficheiro simulations.xml, que é o protótipo de uma resposta XML contendo simulações de cálculos de impostos
- o ficheiro simulations.xsl, que constituirá a folha de estilo XSL que acompanhará esta resposta XML
Temos também de prever o caso de uma resposta com erros. O protótipo da resposta XML, neste caso, será o seguinte ficheiro 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 visualizar 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 introduz um comando XSL ainda não encontrado: <xsl:value-of select="."/>. Este comando produz como resultado o valor do nó analisado, neste caso um nó <erreur>texte</erreur>. O valor deste nó é o texto contido entre as duas balizas de abertura e de fecho, 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 ficheiro erreurs.xml, acompanhado da sua folha de estilo, é apresentado por um navegador da seguinte forma:

5.2.2. A aplicação xmlsimulations
Criamos um ficheiro xmlsimulations.html que colocamos no diretório da aplicação impots. A página visualizada é 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(){
// 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 os enviar para o 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
//está tudo bem – 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>
Note-se que os dados do formulário são enviados para o URL /poly/impots/7/xmlsimulations.php. O código da aplicação xmlsimulations.php é muito semelhante ao da aplicação impots.php. Recomenda-se ao leitor que consulte este último. Recordemos 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 da aplicação
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);
// recuperar os parâmetros da sessão
$session=$_SESSION["session"];
// sessão válida?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// inicia-se uma nova sessão
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// 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
// recuperam-se os parâmetros da troca em curso
$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];
// visualização do formulário
include "$formulaireImpots";
// fim
terminerSession($session);
...
As páginas HTML eram apresentadas pelas linhas «include "...». Pretende-se aqui gerar XML e não HTML. Basta escrever duas novas aplicações, impots_erreurs.php e impots_simulations.php, para que estas gerem XML em vez de HTML. O resto da aplicação não sofre alterações. O código passa então a ser o seguinte:
<?php
// processa o formulário de impostos
// bibliotecas
include "ImpotsDSN.php";
// início da sessão
session_start();
// configuração da aplicação
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);
// recuperar os parâmetros da sessão
$session=$_SESSION["session"];
// sessão válida?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// inicia-se uma nova sessão
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// Existem 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 curso
$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);
}//if
// 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, apresentámos e analisámos os dois tipos de resposta XML a fornecer, bem como as folhas de estilo que as devem acompanhar. O código da aplicação impots_simulations.php é o seguinte:
<?php
// gera o código XML da página de simulações da aplicação 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";
?>
Este código permite, a partir do dicionário $requête, gerar 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 irá transformar este código XML no código HTML.
O código da aplicação impots_erreurs.php é o seguinte:
<?php
// gera o código XML da página de erros da aplicação «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";
?>
Este código permite, a partir do dicionário $requête, gerar 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. Recebe-se 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>
Note-se que a nossa nova aplicação é mais simples de manter do que a anterior. Parte do trabalho foi transferida para as folhas de estilo XSL. A vantagem desta nova repartição de tarefas é que, uma vez definido o formato XML das respostas, o desenvolvimento das folhas de estilo é independente do da aplicação.
5.3. Análise de um documento XML em PHP
A próxima versão da nossa aplicação de impostos será um cliente programado a partir da aplicação anterior xmlsimulations. O nosso cliente irá, portanto, receber código XML, que terá de analisar para extrair as informações que lhe interessam. Vamos fazer aqui uma pausa nas 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
A aplicação xmlParser.php aceita um parâmetro: o URI (Uniform Resource Identifier) do documento XML a analisar. No nosso exemplo, este URI será simplesmente o nome de um ficheiro XML localizado no diretório da aplicação xmlParser.php. Consideremos dois exemplos de execução. No primeiro exemplo, o ficheiro XML analisado é o seguinte ficheiro 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 tínhamos explicado o que a aplicação xmlParser.php fazia, mas vemos aqui que ela apresenta a estrutura do documento XML analisado. O segundo exemplo analisa o seguinte 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 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
A aplicação xmlParser.php contém tudo o que precisamos na nossa aplicação de impostos, uma vez que conseguiu detetar tanto os erros como as simulações que o servidor web poderia enviar. Vamos analisar o seu código:
<?php
// sintaxe $0 fichierXML
// exibe a estrutura e o conteúdo do ficheiro 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 ficheiro 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 as funções a executar no início e no fim da baliza
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual a função a executar quando se encontram dados
xml_set_character_data_handler($xml_parser,"afficheData");
// abertura do ficheiro XML em modo de leitura
if (! ($fp=@fopen($file,"r"))){
fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
exit(2);
}//if
// análise do ficheiro XML em blocos de 4096 octetos
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 ficheiro foi analisado
// estão a ser libertados os recursos ocupados pelo analisador XML
xml_parser_free($xml_parser);
// fim
exit(0);
// -----------------------------------------------------------
// função chamada ao encontrar uma baliza 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 baliza 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 quando se encontra uma baliza de fim
function endElement($parser,$name){
// fim de baliza
// nível de indentação
global $depth;
$depth--;
// uma sequência de espaços (indentação)
for($i=0;$i<$depth;$i++){
echo " ";
}//for
// o nome da baliza
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 apresentados
$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 analisar o código relacionado com o XML. Para analisar um documento XML, a nossa aplicação necessita de um analisador de código XML, normalmente designado por «parser».
Quando o analisador analisar o documento XML, irá emitir eventos tais como: encontrei o início do documento, o início de uma baliza, um atributo de baliza, o conteúdo de uma baliza, o fim de uma baliza, o fim do documento, ... Transmite estes eventos a métodos que lhe temos de indicar:
<?php
...
// indica-se quais as funções a executar no início e no fim da baliza
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual a função a executar quando se encontram 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 de atributos da baliza na forma (ATTRIBUT,valor), em que ATTRIBUT é o nome da baliza em maiúsculas. | |
função afficheData($parser, $data) $parser: o analisador do documento $data: os dados da baliza | |
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 através do parâmetro $attributs. Este é um dicionário dos atributos da baliza. Assim, se tivermos a seguinte baliza:
o dicionário $attributs será o seguinte: array(marie=>sim, filhos=>2, salário=>200000, imposto=>22504)
Depois de definidos o analisador e os métodos anteriores, a análise de um documento é realizada pela função xml_parse:
<?php
...
// processamento do ficheiro 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
}//enquanto
função xml_parse($parser, $doc, $fin) O analisador $parser analisa o documento $doc. O $doc pode ser uma parte de um documento mais extenso. O parâmetro $fin indica se se trata da ú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 sempre que uma baliza começa e termina. A função definida por xml_set_character_data_handler é chamada sempre que o conteúdo de uma baliza for obtido. |
Durante a análise do documento XML, podem ocorrer erros, nomeadamente se o documento XML estiver «mal formado», por exemplo, devido à omissão de tags de fecho. Nesse caso, a função xml_parse devolve 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á a ser analisado pelo analisador |
Quando o documento tiver sido analisado, libertam-se os recursos atribuídos ao analisador:
função xml_free($parser) |
Uma vez explicado isto, o programa anterior, acompanhado dos exemplos de execução, torna-se autoexplicativo.
5.4. Aplicação fiscal: versão 6
Temos agora todos os elementos para escrever um cliente programado para o nosso serviço fiscal que gera o XML. Retomamos a versão 4 da nossa aplicação 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 pela aplicação xmlsimulations.php. A resposta do servidor está, portanto, no formato XML, tal como vimos na versão 5.
- o cliente já não é um navegador, mas sim um cliente PHP autónomo. A sua interface de consola é a da versão 4.
Convida-se o leitor a rever o código da aplicação cltImpots.php, que era o cliente programado da versão 4. Este recebia um documento $document do servidor. Este, por sua vez, era um documento HTML. Agora é um documento XML. O documento HTML $document era analisado pela seguinte função:
<?php
...
function getInfos($document){
// $document: documento HTML
// procura-se, ou 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 devolvia um dicionário $impots com dois atributos:
- erros: um tabuleiro de erros
- simulações: um tabuleiro de simulações, sendo cada simulação, por sua vez, um tabuleiro 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 ter em conta o facto de que agora se trata de um documento XML. A função getInfos passa então 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();
// etiquetas em curso
$balises=array();
// cria-se um objeto de análise de texto XML
$xml_parser=xml_parser_create();
// indica-se quais as funções a executar no início e no fim da baliza
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual a função a executar quando se encontram 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 baliza de início
function startElement($parser,$name,$attributs){
global $impots,$balise,$balises,$contenu;
// regista-se o nome da baliza e o seu conteúdo
$balise=strtolower($name);
$contenu="";
// adiciona-se à pilha de balizas
array_push($balises,$balise);
// trata-se de uma simulação?
if($balise=="simulation"){
// registam-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 baliza de erro?
if($balise=="erreur"){
// mais um erro
$impots[erreurs][]=trim($contenu);
}//if
}//endElement
// -----------------------------------------------------------
// a função de processamento do conteúdo de uma baliza
function getData($parser,$data){
// dados globais
global $balise,$contenu;
// trata-se de uma baliza de erro?
if($balise=="erreur"){
// acumula-se ao conteúdo da baliza atual
$contenu.=$data;
}//if
}//getData
Comentários:
- a função getInfos($document) começa por criar um analisador, depois configura-o 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-se quais as funções a executar no início e no fim da baliza
xml_set_element_handler($xml_parser,"startElement","endElement");
// indica-se qual a função a executar quando se encontram dados
xml_set_character_data_handler($xml_parser,"getData");
// realiza-se a análise de $document
xml_parse($xml_parser,$document);
- Após a conclusão da análise, libertam-se os recursos atribuídos ao analisador e devolve-se o dicionário $impots.
<?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 no início de cada baliza. Ela
- regista a baliza $name numa tabela de balizas $balises. Esta tabela é gerida como uma pilha: ao encontrar o símbolo de fim de baliza, a última baliza empilhada em $balises será retirada da pilha. A baliza atual é também registada em $balise. No dicionário $attributs, encontram-se os atributos da baliza encontrada, estando esses atributos em maiúsculas.
- regista os atributos no dicionário global $impots[simulations], caso se trate de uma baliza de simulação.
- a função getData($parser,$data) quando o conteúdo $data de uma baliza foi recuperado. Aqui, tomámos uma precaução. Em certas API, nomeadamente em Java, para o processamento de documentos XML, é indicado que esta função pode ser chamada repetidamente, c.a.d. que nem sempre se obtém o conteúdo $data de uma baliza de uma só vez. Aqui, a documentação PHP não indica esta restrição. Por precaução, acumula-se o valor $data obtido numa variável global $contenu. Só quando se encontrar o símbolo de fim de baliza é que se considerará ter obtido a totalidade do conteúdo da baliza. A única baliza abrangida por este tratamento é a baliza <erreur>.
- A função endElement($parser,$name) é chamada sempre que se chega ao fim de uma baliza. É utilizada aqui para alterar o nome da baliza atual, removendo a última baliza da pilha de balizas, e para adicionar o conteúdo da baliza <erreur>, que termina na matriz $impots[erreurs].
Eis alguns exemplos de execução, começando por 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)
Depois, 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, a aplicação de impostos tornou-se mais fácil de gerir, tanto para o seu criador como para os criadores das aplicações cliente.
- A conceção da aplicação de servidor pode agora ser confiada a dois tipos de pessoas: o programador PHP do servlet e o designer gráfico que irá gerir a 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 irão acompanhar. Recorde-se que estas são objeto de ficheiros XSL separados e independentes da aplicação PHP. O designer gráfico pode, portanto, trabalhar independentemente do programador.
- Os criadores das aplicações cliente também precisam apenas de conhecer a estrutura da resposta XML do servidor. As alterações que o designer gráfico possa introduzir nas folhas de estilo não têm qualquer repercussão nesta resposta XML, que permanece sempre a mesma. Trata-se de uma enorme vantagem.
- Como é que o programador pode fazer evoluir a sua aplicação PHP sem estragar tudo? Em primeiro lugar, desde que a sua resposta XML não mude, pode organizar a sua aplicação como quiser. Pode também atualizar a resposta XML, desde que mantenha os elementos <erro> e <simulação> esperados pelos seus clientes. Assim, pode adicionar novas balizas a esta resposta. O designer gráfico irá tê-las em conta nas suas folhas de estilo e os navegadores poderão receber as novas versões da resposta. Os clientes programados, por sua vez, continuarão a funcionar com o modelo antigo, sendo as novas balizas simplesmente ignoradas. Para que isso seja possível, é necessário que, na análise XML da resposta do servidor, as balizas procuradas sejam devidamente identificadas. Foi isso que se fez no nosso cliente XML da aplicação de impostos, onde, nos procedimentos, se especificava que se tratavam as balizas <erreur> e <simulation>. Assim, as outras balizas são ignoradas.