Skip to content

5. XML y PHP

En este capítulo, presentaremos el uso de documentos XML (lenguaje de marcado eXtensible) con PHP. Lo haremos en el contexto de la aplicación de impuestos analizada en el capítulo anterior.

5.1. Archivos XML y hojas de estilo XSL

Consideremos el siguiente archivo XML, que podría representar el resultado de simulaciones:

<?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>

Si lo visualizamos con IE 6, obtenemos el siguiente resultado:

Image

IE6 reconoce que se trata de un archivo XML (gracias a la extensión .xml del archivo) y lo formatea a su manera. Con Netscape se obtiene una página en blanco. Sin embargo, si se revisa el código fuente (Ver/Fuente), se observa que sí está presente el archivo original XML:

Image

¿Por qué Netscape no muestra nada? Porque necesita una hoja de estilo que le indique cómo transformar el archivo XML en el archivo HTML, el cual sí podrá mostrar. Resulta que IE 6 sí cuenta con una hoja de estilo por defecto, mientras que el archivo XML no la incluye, como era el caso aquí.

Existe un lenguaje llamado XSL (eXtensible StyleSheet Language) que permite describir las transformaciones que se deben realizar para convertir un archivo XML en cualquier archivo de texto. XSL permite el uso de numerosas instrucciones y se asemeja mucho a los lenguajes de programación. No lo detallaremos aquí, ya que se necesitarían varias decenas de páginas. Simplemente describiremos dos ejemplos de hojas de estilo XSL. La primera es la que transformará el archivo XML simulations.xml en código HTML. Modificamos este último para que indique la hoja de estilo que podrán utilizar los navegadores para transformarlo en el documento HTML que podrán mostrar:

<?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>

El pedido XML

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

designa el archivo simulations.xsl como una hoja de estilo (xml-stylesheet) de tipo text/xsl c.a.d, un archivo de texto que contiene código XSL. Los navegadores utilizarán esta hoja de estilo para transformar el texto XML en un documento HTML. Este es el resultado obtenido con Netscape 7 al cargar el archivo XML simulations.xml:

Image

Al revisar el código fuente del documento (Ver/Fuente), encontramos el documento XML original y no el documento HTML que se muestra:

Image

Netscape utilizó la hoja de estilo simulations.xsl para transformar el documento XML anterior en el documento HTML que se puede visualizar. Ahora es el momento de revisar el contenido de esta hoja 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>
  • Una hoja de estilo XSL es un archivo XML y, por lo tanto, sigue las mismas reglas. Entre otras cosas, debe estar «bien formado», es decir, toda etiqueta abierta debe cerrarse.
  • El archivo comienza con dos comandos XML que se pueden conservar en cualquier hoja de estilo XSL en Windows:
<?xml version="1.0" encoding="windows-1252"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

El atributo encoding="windows-1252" permite utilizar caracteres acentuados en la hoja de estilo.

  • La etiqueta <xsl:output method="html" indent="yes"/> le indica al intérprete XSL que se desea generar HTML «sangrado».
  • La etiqueta <xsl:template match="elemento"> sirve para definir el elemento del documento XML al que se aplicarán las instrucciones que se encuentran entre <xsl:template ...> y </xsl:template>.
    <xsl:template match="/">
................
        </xsl:template>

En el ejemplo anterior, el elemento «/» designa la raíz del documento. Esto significa que, tan pronto como se encuentre el inicio del documento XML, se ejecutarán los comandos XSL ubicados entre las dos etiquetas.

  • Todo lo que no sea una etiqueta XSL se incluye tal cual en el flujo de salida. Las etiquetas XSL, por su parte, se ejecutan. Algunas de ellas producen un resultado en el flujo de salida. Analicemos el siguiente ejemplo:
    <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>

Recordemos que el documento XML analizado es el siguiente:

<?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 el inicio del documento XML analizado (match="/"), el intérprete XSL generará como salida el 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>

Cabe señalar que en el texto original aparecía <hr/> y no <hr>. En el texto original no se podía escribir <hr>, ya que, si bien es una etiqueta HTML válida, es una etiqueta XML inválida. Sin embargo, aquí nos encontramos con un texto XML que debe estar «bien formado», es decir, c.a.d, lo que significa que todas las etiquetas deben estar cerradas. Por lo tanto, escribimos <hr/> y, como hemos escrito <xsl:output text="html ...">, el intérprete transformará el texto <hr/> en <hr>. A continuación de este texto, vendrá el texto generado por el comando XSL:

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

Más adelante veremos de qué texto se trata. Por último, el intérprete agregará el texto:

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

La instrucción <xsl:apply-templates select="/simulations/simulation"/> solicita que se ejecute la «plantilla» del elemento /simulations/simulation. Se ejecutará cada vez que el intérprete XSL encuentre en el texto XML analizado una etiqueta <simulation>..</simulations> o <simulation/> dentro de una etiqueta <simulations>..</simulations>. Al encontrar una etiqueta de este tipo, el intérprete ejecutará las instrucciones del siguiente modelo:

    <xsl:template match="simulation">
          <tr>
            <td><xsl:value-of select="@marie"/></td>
            <td><xsl:value-of select="@enfants"/></td>
            <td><xsl:value-of select="@salaire"/></td>
            <td><xsl:value-of select="@impot"/></td>
        </tr>
       </xsl:template>

Tomemos las siguientes líneas XML:

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

La línea <simulation ..> corresponde a la plantilla de la instrucción XSL <xsl:apply-templates select="/simulations/simulation>". Por lo tanto, el intérprete XSL buscará aplicar las instrucciones que correspondan a esta plantilla. Encontrará la plantilla <xsl:template match="simulation"> y la ejecutará. Recordemos que lo que no es un comando XSL es tomado tal cual por el intérprete XSL, y que los comandos XSL son reemplazados por el resultado de su ejecución. La instrucción XSL <xsl:value-of select="@champ"/> se reemplaza así por el valor del atributo «champ» del nodo analizado (en este caso, un nodo <simulation>). El análisis de la línea XML anterior producirá el siguiente resultado:

XSL
salida
<tr><td>
<tr><td>
<xsl:value-of select="@marie"/>
</td><td>
</td><td>
<xsl:value-of select="@hijos"/>
2
</td><td>
</td><td>
<xsl:value-of select="@salaire"/>
200000
</td><td>
</td><td>
<xsl:value-of select="@impot"/>
22504
</td></tr>
</td></tr>

En total, la línea XML

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

se transformará en la línea HTML:

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

Todas estas explicaciones son un poco rudimentarias, pero ahora debería quedar claro para el lector que el siguiente texto XML:

<?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>

acompañado de la siguiente hoja 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>

genera el siguiente 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>

El siguiente archivo XML simulations.xml

<?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>

leído por un navegador reciente (en este caso, Netscape 7) se muestra así:

Image

5.2. Aplicación de impuestos: versión 5

5.2.1. Los archivos XML y las hojas de estilo XSL de la aplicación de impuestos

Volvamos a nuestro punto de partida, que era la aplicación web de impuestos, y recordemos que queremos modificarla para que la respuesta que se envíe a los clientes sea en formato XML en lugar de HTML. Esta respuesta HTML irá acompañada de una hoja de estilo XSL para que los navegadores puedan mostrarla. En el párrafo anterior, presentamos:

  • el archivo simulations.xml, que es el prototipo de una respuesta XML con simulaciones de cálculos de impuestos
  • el archivo simulations.xsl, que será la hoja de estilo XSL que acompañará a esta respuesta XML

También debemos prever el caso de una respuesta con errores. El prototipo de la respuesta XML en este caso será el siguiente archivo 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>

La hoja de estilo erreurs.xsl que permite visualizar este documento XML en un navegador será la siguiente:

<?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 hoja de estilo introduce un comando XSL que aún no se ha encontrado: <xsl:value-of select="."/>. Este comando genera como salida el valor del nodo analizado, en este caso un nodo <erreur>texte</erreur>. El valor de este nodo es el texto que se encuentra entre las dos etiquetas de apertura y cierre, en este caso texte.

El código erreurs.xml es transformado por la hoja de estilo erreurs.xsl en el siguiente 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>

El archivo erreurs.xml, junto con su hoja de estilo, se muestra en un navegador de la siguiente manera:

Image

5.2.2. La aplicación xmlsimulations

Creamos un archivo xmlsimulations.html que colocamos en el directorio de la aplicación impots. La página que se visualiza es la siguiente:

Image

Este documento HTML es un documento estático. Su código es el siguiente:

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
           // borrar el formulario
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//con
      }//borrar

      function calculer(){
           // verificación de los parámetros antes de enviarlos al servidor
        with(document.frmImpots){
          //número de hijos
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // el modelo no se ha verificado
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//si
           //salario
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // el modelo no se ha verificado
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//si
          // está bien, se envía
          submit();
        }//con
      }//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>

Cabe señalar que los datos del formulario se envían a URL /poly/impots/7/xmlsimulations.php. El código de la aplicación xmlsimulations.php es muy similar al de la aplicación impots.php. Se invita al lector a revisar este último. Recordemos el código de entrada:

<?php
     // procesa el formulario de impuestos

     // bibliotecas
  include "ImpotsDSN.php";

   // inicio de sesión
  session_start();

   // configuración de la aplicación
  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);

     // se recuperan los parámetros de la sesión
  $session=$_SESSION["session"];
   // ¿Sesión válida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // se inicia una nueva sesión
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // ¿Hay errores?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // se muestra la página de errores
        include $erreursImpots;
       // fin
      $session=array();
      terminerSession($session);
    }//si
  }//si

  // se recuperan los parámetros del intercambio en curso
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // ¿Tenemos todos los parámetros?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // se muestra el formulario en blanco
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // fin
    terminerSession($session);
  }//si

   // verificación de parámetros
  $requête=vérifier($requête);

   // ¿Hay errores?
  if(count($requête[erreurs])!=0){
       // visualización del formulario
    include "$formulaireImpots";
     // fin
    terminerSession($session);
  }//si

   // cálculo del impuesto a pagar
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // otra simulación
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // visualización del formulario
  include "$formulaireImpots";

   // fin
  terminerSession($session);
...

Las páginas HTML se mostraban mediante las líneas «include "...». Aquí queremos generar XML y no HTML. Basta con escribir dos nuevas aplicaciones, impots_erreurs.php y impots_simulations.php, para que generen XML en lugar de HTML. El resto de la aplicación no cambia. El código queda entonces así:

<?php
     // procesa el formulario de impuestos

     // bibliotecas
  include "ImpotsDSN.php";

   // inicio de sesión
  session_start();

   // configuración de la aplicación
  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);

     // se recuperan los parámetros de la sesión
  $session=$_SESSION["session"];
   // ¿Sesión válida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // se inicia una nueva sesión
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // ¿Hay errores?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // se muestra la página de errores en formato XML
          header("Content-type: text/xml");      
        include $erreursImpots;
       // fin
      $session=array();
      terminerSession($session);
    }//si
  }//si

   // se recuperan los parámetros del intercambio en curso
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // ¿Tenemos todos los parámetros?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // se muestra el formulario en blanco
    include $formulaireImpots;
     // fin
    terminerSession($session);
  }//si

   // verificación de parámetros
  $requête=vérifier($requête);

   // ¿Hay errores?
  if(count($requête[erreurs])!=0){
       // visualización de errores en formato XML
      header("Content-type: text/xml");    
    include "$erreursImpots";
     // fin
    terminerSession($session);
  }//si

   // cálculo del impuesto a pagar
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // una simulación más
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // visualización de simulaciones en formato XML
  header("Content-type: text/xml");
  include "$simulationsImpots";

   // fin
  terminerSession($session);

Anteriormente hemos presentado y analizado los dos tipos de respuesta XML que se deben proporcionar, así como las hojas de estilo que deben acompañarlas. El código de la aplicación impots_simulations.php es el siguiente:

<?php
     // genera el código XML de la página de simulaciones de la aplicación de impuestos

   // algunas constantes
  $xslSimulations="simulations.xsl";

   // encabezados XML
  echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
  echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslSimulations\" ?>\n";    
  // las simulaciones
  echo "<simulations>\n";
  for ($i=0;$i<count($requête[simulations]);$i++){
      // simulación $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 del diccionario $requête, generar código XML similar al siguiente:


<?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>

La hoja de estilo simulations.xsl transformará este código XML en el código HTML.

El código de la aplicación impots_erreurs.php es el siguiente:

<?php
     // genera el código XML de la página de errores de la aplicación de impuestos

   // algunas constantes
  $xslErreurs="erreurs.xsl";

   // encabezados XML
  echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
  echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslErreurs\" ?>\n";
   // los errores
  echo "<erreurs>\n";
  for ($i=0;$i<count($requête[erreurs]);$i++){
      // error $i
    echo "<erreur>".$requête[erreurs][$i]."</erreur>";
  }//para
  echo "</erreurs>\n";
?>

Este código permite, a partir del diccionario $requête, generar código XML similar al siguiente:

<?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>

La hoja de estilo erreurs.xsl transformará este código XML en el código HTML.

Veamos un primer ejemplo:

Image

El SGBD MySQL no se ejecuta. Entonces recibimos la siguiente respuesta:

Image

Si observamos el código fuente recibido por el navegador, vemos lo siguiente:

<?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>

Ahora ejecutamos el SGBD y el MySQL y realizamos varias simulaciones sucesivas. Obtenemos la siguiente respuesta:

Image

El código recibido por el navegador es el siguiente:

<?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>

Cabe destacar que nuestra nueva aplicación es más fácil de mantener que la anterior. Parte del trabajo se ha trasladado a las hojas de estilo XSL. La ventaja de esta nueva distribución de tareas es que, una vez establecido el formato XML de las respuestas, el desarrollo de las hojas de estilo es independiente del de la aplicación.

5.3. Análisis de un documento XML en PHP

La próxima versión de nuestra aplicación de impuestos será un cliente programado a partir de la aplicación anterior xmlsimulations. Por lo tanto, nuestro cliente recibirá código XML que deberá analizar para extraer la información que le interesa. Aquí haremos una pausa en nuestras diferentes versiones y aprenderemos cómo se puede analizar un documento XML en PHP. Lo haremos a partir del siguiente ejemplo:


dos>e:\php43\php.exe xmlParser.php
syntaxe : xmlParser.php fichierXML

La aplicación xmlParser.php admite un parámetro: el URI (Identificador Uniforme de Recursos) del documento XML que se va a analizar. En nuestro ejemplo, este URI será simplemente el nombre de un archivo XML ubicado en el directorio de la aplicación xmlParser.php. Veamos dos ejemplos de ejecución. En el primer ejemplo, el archivo XML analizado es el siguiente archivo 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>

El análisis arroja los siguientes resultados:


dos>e:\php43\php.exe xmlParser.php erreurs.xml
ERREURS
 ERREUR
  [erreur 1]
 /ERREUR
 ERREUR
  [erreur 2]
 /ERREUR
/ERREURS

Aún no habíamos explicado qué hacía la aplicación xmlParser.php, pero aquí se puede ver que muestra la estructura del documento XML analizado. El segundo ejemplo analiza el siguiente archivo 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>

El análisis arroja los siguientes 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

La aplicación xmlParser.php contiene todo lo que necesitamos en nuestra aplicación de impuestos, ya que ha sido capaz de detectar tanto los errores como las simulaciones que pudiera enviar el servidor web. Analicemos su código:

<?php
   // sintaxis $0 fichierXML
   // muestra la estructura y el contenido del archivo fichierXML

   // verificación de llamada
  if(count($argv)!=2){
       // mensaje de error
    fwrite(STDERR,"syntaxe : $argv[0] fichierXML\n");
     // fin
    exit(1);
  }//si

   // inicializaciones
  $file=$argv[1];       // el archivo xml
  $depth=0;               // nivel de sangría = profundidad en el árbol

   // el programa

   // se crea un objeto de análisis de texto XML
  $xml_parser=xml_parser_create();

   // se indican las funciones que se deben ejecutar al inicio y al final de la etiqueta
  xml_set_element_handler($xml_parser,"startElement","endElement");

   // se indica qué función ejecutar al encontrar datos
  xml_set_character_data_handler($xml_parser,"afficheData");

  // se abre el archivo XML en modo de lectura
  if (! ($fp=@fopen($file,"r"))){
    fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
    exit(2);
  }//if
   // procesamiento del archivo XML en bloques de 4096 bytes
  while($data=fread($fp,4096)){
    // análisis de los datos leídos
    if (! xml_parse($xml_parser,$data,feof($fp))){
      // Se produjo un error
      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));
       // fin
      exit(3);
    }//si
  }//mientras
  // se ha explorado el archivo
   // se liberan los recursos ocupados por el analizador XML
  xml_parser_free($xml_parser);
   // fin
  exit(0);

    // -----------------------------------------------------------  
   // función llamada al encontrar una etiqueta de inicio
  function startElement($parser,$name,$attributs){
    global $depth;
     // una secuencia de espacios (sangría)
    for($i=0;$i<$depth;$i++){
      print " ";
    }//for
     // atributos
    $précisions="";
    while(list($attrib,$valeur)=each($attributs)){
      $précisions.="($attrib,$valeur) ";
    }
     // se muestra el nombre de la etiqueta y los posibles atributos
    if($précisions)
      print "$name,$précisions\n";
    else print "$name\n";
     // un nivel más en el árbol
    $depth++;
  }//startElement

    // -----------------------------------------------------------  
   // la función que se invoca al encontrar una etiqueta de cierre
  function endElement($parser,$name){
     // etiqueta de cierre
     // nivel de sangría
    global $depth;
    $depth--;
    // una secuencia de espacios (sangría)
    for($i=0;$i<$depth;$i++){
      echo " ";
    }//for
     // el nombre de la etiqueta
    echo "/$name\n";
  }//endElement

    // -----------------------------------------------------------  
   // la función de visualización de datos
  function afficheData($parser,$data){
     // nivel de sangría
    global $depth;
     // se muestran los datos
    $data=trim($data);
    if($data!=""){
       // una secuencia de espacios (sangría)
      for($i=0;$i<$depth;$i++){
        echo " ";
      }//for
      echo "[$data]\n";
    }//si
  }//afficheData
?>

Analicemos el código relacionado con XML. Para analizar un documento XML, nuestra aplicación necesita un analizador de código XML, comúnmente llamado «parser».

<?php
...
   // se crea un objeto de análisis de texto XML
  $xml_parser=xml_parser_create();

Cuando el analizador analice el documento XML, generará eventos tales como: he encontrado el inicio del documento, el inicio de una etiqueta, un atributo de etiqueta, el contenido de una etiqueta, el final de una etiqueta, el final del documento, etc. Transmite estos eventos a los métodos que se le indiquen:

<?php
...
   // se indican las funciones que se deben ejecutar al inicio y al final de la etiqueta
  xml_set_element_handler($xml_parser,"startElement","endElement");
   // se indica qué función ejecutar al encontrar datos
  xml_set_character_data_handler($xml_parser,"afficheData");
evento emitido por el
analizador
método de procesamiento
début d'un élément :<balise>
función startElement($parser, $name, $attributs)
$parser: el analizador del documento
$name: nombre del elemento analizado. Si el elemento encontrado es <simulations>, se obtendrá name="simulations".
$attributs: lista de atributos de la etiqueta en el formato (ATTRIBUT,valor), donde ATTRIBUT es el nombre en mayúsculas de la etiqueta.
valeur d'un élément :
<balise>valeur</balise>
función muestraDatos($analizador, $datos)
$parser: el analizador del documento
$data: los datos de la etiqueta
fin d'un élément : </balise> ou <balise .../>
función endElement($parser, $name)
Los parámetros son los mismos que los del método startElement.

La función startElement permite recuperar los atributos del elemento gracias al parámetro $attributs. Este es un diccionario de los atributos de la etiqueta. Así, si tenemos la siguiente etiqueta:

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

el diccionario $attributs será el siguiente: array(marie=>sí, hijos=>2, salario=>200000, impuestos=>22504)

Una vez definidos el analizador y los métodos anteriores, el análisis de un documento se realiza mediante la función xml_parse:

<?php
...
   // procesamiento del archivo XML en bloques de 4096 bytes
  while($data=fread($fp,4096)){
    // análisis de los datos leídos
    if (! xml_parse($xml_parser,$data,feof($fp))){
...
    }//if
  }//while
analyse d'un
document $doc
función xml_parse($parser, $doc, $fin)
El analizador $parser analiza el documento $doc. $doc puede ser una parte de un documento más extenso. El parámetro $fin indica si se trata de la última parte (true) o no (false). Al analizar el documento $doc, las funciones definidas por xml_set_element_handler se invocan al inicio y al final de cada etiqueta. ¿Se invoca la función definida por xml_set_character_data_handler cada vez que se obtiene el contenido de una etiqueta?

Durante el análisis del documento XML, pueden producirse errores, especialmente si el documento XML está «mal formado», por ejemplo, si faltan etiquetas de cierre. En este caso, la función xml_parse devuelve un valor evaluado como «falso»:

<?php
...
    if (! xml_parse($xml_parser,$data,feof($fp))){
      // Se produjo un error
      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));
      // fin
      exit(3);
    }//si
gestion d'erreurs
función xml_get_error_code($parser)
devuelve el código del último error ocurrido
--
función xml_error_string($código)
devuelve el mensaje de error asociado al código pasado como parámetro
--
función xml_get_current_line($parser)
devuelve el número de la línea del documento XML que el analizador está procesando actualmente

Una vez que se ha analizado el documento, se liberan los recursos asignados al analizador:

<?php
...
   // se liberan los recursos ocupados por el analizador XML
  xml_parser_free($xml_parser);
libération des
ressources attribuées
au parseur $parser
función xml_free($parser)

Una vez explicado esto, el programa anterior, junto con los ejemplos de ejecución, se entiende por sí solo.

5.4. Aplicación de impuestos: versión 6

Ahora contamos con todos los elementos para desarrollar un cliente programado para nuestro servicio de impuestos que genere XML. Utilizamos la versión 4 de nuestra aplicación para crear el cliente y mantenemos la versión 5 para el servidor. En esta aplicación cliente-servidor:

  • el servicio de simulaciones del cálculo de impuestos lo realiza la aplicación xmlsimulations.php. Por lo tanto, la respuesta del servidor está en formato XML, tal como lo vimos en la versión 5.
  • el cliente ya no es un navegador, sino un cliente PHP independiente. Su interfaz de consola es la de la versión 4.

Se invita al lector a volver a leer el código de la aplicación cltImpots.php, que era el cliente programado de la versión 4. Este recibía un documento $document del servidor. Este, a su vez, era un documento HTML. Ahora es un documento XML. El documento HTML $document era analizado por la siguiente función:

<?php
...
  function getInfos($document){
       // $document: documento HTML
     // se busca, ya sea la lista de errores
     // o la tabla de simulaciones

     // preparación del resultado
    $impots[erreurs]=array();
    $impots[simulations]=array();

........
    return $impots;
    }//getInfos

La función recibía el documento HTML $document, lo analizaba y devolvía un diccionario $impots con dos atributos:

  1. errores: una tabla de errores
  2. simulaciones: una tabla de simulaciones, siendo cada simulación a su vez una tabla de cuatro elementos (esposa, hijos, salario, impuestos).

La aplicación cltImpots.php pasa a ser ahora cltXmlSimulations.php. Solo debe modificarse la parte que procesa el documento recibido del servidor para tener en cuenta que ahora se trata de un documento XML. La función getInfos queda entonces así:

<?php
...
// --------------------------------------------------------------
  function getInfos($document){
       // $document: documento XML
     // se busca, ya sea la lista de errores
     // o la tabla de simulaciones

    global $impots,$balises;

     // preparación del resultado
    $impots[erreurs]=array();
    $impots[simulations]=array();
     // etiquetas en curso
    $balises=array();

     // se crea un objeto de análisis de texto XML
    $xml_parser=xml_parser_create();
     // se indican qué funciones ejecutar al inicio y al final de la etiqueta
    xml_set_element_handler($xml_parser,"startElement","endElement");
     // se indica qué función ejecutar al encontrar datos
    xml_set_character_data_handler($xml_parser,"getData");
    // se realiza el análisis de $document
    xml_parse($xml_parser,$document);
     // se liberan los recursos ocupados por el analizador XML
    xml_parser_free($xml_parser);
     // fin
    return $impots;
  }//getInfos

    // -----------------------------------------------------------  
  // función que se invoca al encontrar una etiqueta de inicio
  function startElement($parser,$name,$attributs){
    global $impots,$balise,$balises,$contenu;

     // se registra el nombre de la etiqueta y su contenido
    $balise=strtolower($name);
    $contenu="";
     // se agrega a la pila de etiquetas
    array_push($balises,$balise);    
    // ¿Se trata de una simulación?
    if($balise=="simulation"){   
         // se registran los atributos de la simulación
      $impots[simulations][]=array($attributs[MARIE],$attributs[ENFANTS],$attributs[SALAIRE],$attributs[IMPOT]);
    }//if
  }//startElement

    // -----------------------------------------------------------  
   // la función que se invoca al encontrar una etiqueta de fin
  function endElement($parser,$name){
       // se recupera la etiqueta actual
    global $impots,$balises,$contenu;
    $balise=array_pop($balises);
    // ¿Se trata de una etiqueta de error?
    if($balise=="erreur"){
         // otro error
      $impots[erreurs][]=trim($contenu);
    }//si
  }//endElement

    // -----------------------------------------------------------  
   // la función de procesamiento del contenido de una etiqueta
  function getData($parser,$data){
      // datos globales
    global $balise,$contenu;
     // ¿Se trata de una etiqueta de error?
    if($balise=="erreur"){
         // se suma al contenido de la etiqueta actual
      $contenu.=$data;
    }//if
  }//getData

Comentarios:

  • La función getInfos ($document) comienza creando un analizador, luego lo configura y, por último, inicia el análisis del documento $document:
<?php
...
     // se crea un objeto de análisis de texto XML
    $xml_parser=xml_parser_create();
     // se indican las funciones que se deben ejecutar al inicio y al final de la etiqueta
    xml_set_element_handler($xml_parser,"startElement","endElement");
     // se indica qué función ejecutar al encontrar datos
    xml_set_character_data_handler($xml_parser,"getData");
    // se realiza el análisis de $document
    xml_parse($xml_parser,$document);
  • Al finalizar el análisis, se liberan los recursos asignados al analizador y se devuelve el diccionario $impots.
<?php
...
     // se liberan los recursos ocupados por el analizador XML
    xml_parser_free($xml_parser);
     // fin
    return $impots;
  • La función startElement($parser,$name,$attributs) se invoca al inicio de cada etiqueta. Ella
    • detecta la etiqueta $name en una tabla de etiquetas $balises. Esta tabla se maneja como una pila: al encontrar el símbolo de fin de etiqueta, la última etiqueta apilada en $balises se desapilará. La etiqueta actual también se registra en $balise. En el diccionario $attributs se encuentran los atributos de la etiqueta encontrada, los cuales están en mayúsculas.
    • registra los atributos en el diccionario global $impots[simulations], si se trata de una etiqueta de simulación.
  • la función getData($parser,$data) cuando se ha recuperado el contenido $data de una etiqueta. Aquí se ha tomado una precaución. En algunos API, especialmente en Java, para el procesamiento de documentos XML, se indica que esta función puede llamarse repetidamente, c.a.d. que no siempre se obtiene el contenido de una etiqueta de una sola vez. En este caso, la documentación PHP no indica esta restricción. Como medida de precaución, se acumula el valor $data obtenido en una variable global $contenu. Solo al encontrar el símbolo de fin de etiqueta se considerará que se ha obtenido la totalidad del contenido de la etiqueta. La única etiqueta afectada por este procesamiento es la etiqueta <erreur>.
  • La función endElement($parser,$name) se invoca al final de cada etiqueta. Aquí se utiliza para cambiar el nombre de la etiqueta actual al eliminar la última etiqueta de la pila de etiquetas y para agregar el contenido de la etiqueta <erreur>, que termina en la matriz $impots[erreurs].

A continuación se muestran algunos ejemplos de ejecución, primero con un SGBD que no se ha iniciado:

dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php sí 2 200000
Jeton de session=[e8c29ea12f79e4771960068d161229fd]
Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)

Luego, con el SGBD ejecutado:


dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php sí 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 sí 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 no 2 200000 69a54d79db10b70ed0a2d55d5026ac8b
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
[oui,2,200000,22504]
[non,2,200000,33388]

5.5. Conclusión

Gracias a su respuesta XML, la aplicación de impuestos se ha vuelto más fácil de manejar tanto para su desarrollador como para los desarrolladores de las aplicaciones cliente.

  • El diseño de la aplicación de servidor ahora puede confiarse a dos tipos de personas: el desarrollador PHP del servlet y el diseñador gráfico que se encargará de la apariencia de la respuesta del navegador en los navegadores. A este último le basta con conocer la estructura de la respuesta del servidor para crear las hojas de estilo que la acompañarán. Recordemos que estas se almacenan en archivos XSL separados e independientes de la aplicación PHP. Por lo tanto, el diseñador gráfico puede trabajar de manera independiente del desarrollador.
  • Los diseñadores de las aplicaciones cliente, por su parte, solo necesitan conocer la estructura de la respuesta XML del servidor. Las modificaciones que el diseñador gráfico pueda realizar en las hojas de estilo no tienen ningún impacto en esta respuesta XML, que siempre permanece igual. Esto representa una gran ventaja.
  • ¿Cómo puede el desarrollador hacer evolucionar su aplicación PHP sin que nada se rompa? En primer lugar, mientras su respuesta XML no cambie, puede organizar su aplicación como quiera. También puede actualizar la respuesta XML siempre y cuando mantenga los elementos <error> y <simulación> que esperan sus clientes. De esta manera, puede agregar nuevas etiquetas a esta respuesta. El diseñador gráfico las tomará en cuenta en sus hojas de estilo y los navegadores podrán recibir las nuevas versiones de la respuesta. Los clientes programados, por su parte, seguirán funcionando con el modelo anterior, ya que las nuevas etiquetas simplemente se ignorarán. Para que esto sea posible, es necesario que en el análisis XML de la respuesta del servidor, las etiquetas buscadas estén bien identificadas. Esto es lo que se hizo en nuestro cliente XML de la aplicación de impuestos, donde en los procedimientos se especificaba que se procesaban las etiquetas <erreur> y <simulation>. De este modo, las demás etiquetas se ignoran.