Skip to content

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:

Image

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:

Image

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

<?xml-stylesheet type="text/xsl" href="simulations.xsl"?>

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:

Image

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

Image

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>.
    <xsl:template match="/">
................
        </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:

      <xsl:apply-templates select="/simulations/simulation"/>

Veremos mais tarde qual é esse texto. Por fim, o interpretador irá adicionar o texto:

                    </table>
          </center>
        </body>
      </html>

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:

<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>

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

<simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>

será transformada na linha HTML:

<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>

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:

Image

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:

Image

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:

Image

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:

Image

O SGBD MySQL não é executado. Recebe-se então a seguinte resposta:

Image

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:

Image

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».

<?php
...
   // cria-se um objeto de análise de texto XML
  $xml_parser=xml_parser_create();

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
début d'un élément :<balise>
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.
valeur d'un élément :
<balise>valeur</balise>
função afficheData($parser, $data)
$parser: o analisador do documento
$data: os dados da baliza
fin d'un élément : </balise> ou <balise .../>
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:

  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>

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
analyse d'un
document $doc
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
gestion d'erreurs
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:

<?php
...
   // são libertados os recursos ocupados pelo analisador XML
  xml_parser_free($xml_parser);
libération des
ressources attribuées
au parseur $parser
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:

  1. erros: um tabuleiro de erros
  2. 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.