Skip to content

5. XML and PHP

In this chapter, we will provide an introduction to using XML documents (eXtensible Markup Language) with PHP. We will do this in the context of the tax application discussed in the previous chapter.

5.1. XML files and XSL style sheets

Consider the following XML file, which could represent the results of simulations:

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

If we view it with IE 6, we get the following result:

Image

IE6 recognizes that it is dealing with a XML file (thanks to the file’s .xml suffix) and formats it in its own way. With Netscape, you get a blank page. However, if you look at the source code (View/Source), you can see the original XML file:

Image

Why doesn’t Netscape display anything? Because it needs a stylesheet to tell it how to transform the XML file into a HTML file that it can then display. It turns out that IE 6 has a default stylesheet, whereas the XML file does not provide one, which was the case here.

There is a language called XSL (eXtensible StyleSheet Language) that allows you to describe the transformations needed to convert a XML file into any text file. XSL supports numerous instructions and closely resembles programming languages. We will not go into detail here, as it would take several dozen pages. We will simply describe two examples of XSL style sheets. The first is the one that will transform the XML simulations.xml file into HTML code. We modify the latter so that it specifies the style sheet that browsers can use to transform it into a HTML document that they can display:

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

The XML command

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

designates the file simulations.xsl as a stylesheet (xml-stylesheet) of type text/xsl c.a.d. a text file containing code XSL. This stylesheet will be used by browsers to transform the text XML into a document HTML. Here is the result obtained with Netscape 7 when loading the file XML simulations.xml:

Image

When we view the document’s source code (View/Source), we see the original XML document and not the displayed HTML document:

Image

Netscape used the simulations.xsl stylesheet to transform the XML document above into the displayable HTML document. It is now time to look at the contents of this stylesheet:

<?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'taxes</title>
        </head>
          <body>
            <center>
              <h3>Simulations de calculs d'taxes</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>
  • A XSL stylesheet is a XML file and therefore follows its rules. Among other things, it must be "well-formed," meaning that every open tag must be closed.
  • The file begins with two commands that can be included in any XSL stylesheet on Windows:
<?xml version="1.0" encoding="windows-1252"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

The encoding="windows-1252" attribute allows accented characters to be used in the stylesheet.

  • The <xsl:output method="html" indent="yes"/> tag tells the XSL interpreter to produce "indented" HTML.
  • The <xsl:template match="element"> tag is used to define the element in the XML document to which the instructions found between <xsl:template ...> and </xsl:template> will be applied.
    <xsl:template match="/">
................
        </xsl:template>

In the example above, the "/" element refers to the root of the document. This means that as soon as the start of the XML document is encountered, the XSL commands located between the two tags will be executed.

  • Anything that is not a XSL tag is included as-is in the output stream. The XSL tags are executed. Some of them produce a result in the output stream. Let’s examine the following example:
    <xsl:template match="/">
        <html>
          <head>
            <title>Simulations de calculs d'taxes</title>
        </head>
          <body>
            <center>
              <h3>Simulations de calculs d'taxes</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>

Note that the document XML analyzed is as follows:

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

From the beginning of the analyzed document XML (match="/"), the XSL interpreter will output the text

<html>
          <head>
            <title>Simulations de calculs d'taxes</title>
        </head>
          <body>
            <center>
              <h3>Simulations de calculs d'taxes</h3>
            <hr>
            <table border="1">
                    <th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>

Note that in the original text we had <hr/> and not <hr>. In the original text, we could not write <hr>, which, while a valid HTML tag, is an invalid XML tag. However, we are dealing here with a XML text that must be "well-formed," meaning that every tag must be closed. We therefore write <hr/>, and because we wrote <xsl:output text="html ...">, the interpreter will transform the text <hr/> into <hr>. Following this text will be the text produced by the command XSL:

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

We will see later what this text is. Finally, the interpreter will add the text:

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

The command <xsl:apply-templates select="/simulations/simulation"/> instructs the interpreter to apply the "template" to the /simulations/simulation element. It will be executed every time the XSL interpreter encounters a <simulation>..</simulations> or <simulation/> tag inside a <simulations>..</simulations> tag in the parsed XML text. Upon encountering such a tag, the interpreter will execute the instructions in the following 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>

Let's take the following lines from XML:

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

The line <simulation ..> corresponds to the template for the XSL instruction <xsl:apply-templates select="/simulations/simulation">. The XSL interpreter will therefore attempt to apply the instructions corresponding to this template to it. It will find the template <xsl:template match="simulation"> and execute it. Recall that anything that is not a XSL command is passed through unchanged by the XSL interpreter, and that XSL commands are replaced by the result of their execution. The instruction XSL <xsl:value-of select="@champ"/> is thus replaced by the value of the "champ" attribute of the parsed node (here a <simulation> node). Parsing the preceding line XML will produce the following output:

XSL
output
<tr><td>
<tr><td>
<xsl:value-of select="@marie"/>
yes
</td><td>
</td><td>
<xsl:value-of select="@children"/>
2
</td><td>
</td><td>
<xsl:value-of select="@salary"/>
200000
</td><td>
</td><td>
<xsl:value-of select="@tax"/>
22504
</td></tr>
</td></tr>

In total, line XML

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

will be converted to line HTML:

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

All these explanations are a bit rudimentary, but it should now be clear to the reader that the following text 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>

accompanied by the following stylesheet 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'taxes</title>
        </head>
          <body>
            <center>
              <h3>Simulations de calculs d'taxes</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>

produces the following text: HTML:

<html>
<head>
<title>Simulations de calculs d'taxes</title>
</head>
<body>
<center>
<h3>Simulations de calculs d'taxes</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>

The following file: 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>

When viewed in a modern browser (here Netscape 7), it is displayed as follows:

Image

5.2. Tax application: version 5

5.2.1. The XML files and XSL style sheets of the tax application

Let’s return to our starting point, which was the tax web application, and recall that we want to modify it so that the response to clients is in the XML format rather than a HTML response. This HTML response will be accompanied by a XSL stylesheet so that browsers can display it. In the previous paragraph, we presented:

  • the simulations.xml file, which is the prototype of a XML response containing tax calculation simulations
  • the file simulations.xsl, which will serve as the XSL stylesheet accompanying this XML response

We must also account for the case of a response containing errors. The prototype for the response XML in this case will be the following errors.xml file:

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

The erreurs.xsl stylesheet used to display this XML document in a browser will be as follows:

<?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'taxes</title>
        </head>
          <body>
            <center>
              <h3>Simulations de calculs d'taxes</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>    

This stylesheet introduces a XSL command not yet encountered: <xsl:value-of select="."/>. This command outputs the value of the parsed node, in this case a <error>text</error> node. The value of this node is the text between the opening and closing tags, in this case "text".

The code errors.xml is transformed by the erreurs.xsl stylesheet into the following HTML document:

<html>
<head>
<title>Simulations de calculs d'impots</title>
</head>
<body>
<center>
<h3>Simulations de calculs d'taxes</h3>
</center>
<hr>
          Les erreurs suivantes se sont produites :
          <ul>
<li>erreur 1</li>
<li>erreur 2</li>
</ul>
</body>
</html>

The file errors.xml, along with its style sheet, is displayed by a browser as follows:

Image

5.2.2. The xmlsimulations application

We create a file named xmlsimulations.html and place it in the impots application directory. The displayed page is as follows:

Image

This HTML document is a static document. Its code is as follows:

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

      function calculer(){
           // check parameters before sending them to the server
        with(document.frmImpots){
          //no. of children
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // the model is not verified
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
           //salary
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // the model is not verified
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // that's it - we send
          submit();
        }//with
      }//calculate        
        </script>
  </head>

  <body background="/poly/impots/7/images/standard.jpg">
      <center>
        Calcul d'taxes
        <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'children</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 that the form data is posted to URL /poly/impots/7/xmlsimulations.php. The code for the xmlsimulations.php application is very similar to that of the impots.php application. Readers are encouraged to review the latter. Here is the input code:

<?php
     // processes the tax form

     // libraries
  include "ImpotsDSN.php";

   // session start
  session_start();

   // application configuration
  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);

     // retrieve session parameters
  $session=$_SESSION["session"];
   // valid session?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // start a new session
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // mistakes?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // error page display
        include $erreursImpots;
       // end
      $session=array();
      terminerSession($session);
    }//if
  }//if

  // retrieve the parameters of the current exchange
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // do we have all the parameters?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // empty form display
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // end
    terminerSession($session);
  }//if

   // checking parameters
  $requête=vérifier($requête);

   // mistakes?
  if(count($requête[erreurs])!=0){
       // form display
    include "$formulaireImpots";
     // end
    terminerSession($session);
  }//if

   // calculating tax payable
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // one more simulation
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // form display
  include "$formulaireImpots";

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

The HTML pages were displayed by the include lines "...". Here, we want to generate XML and not HTML. Simply write two new applications: impots_erreurs.php and impots_simulations.php so that they generate XML instead of HTML. The rest of the application remains unchanged. The code then becomes the following:

<?php
     // processes the tax form

     // libraries
  include "ImpotsDSN.php";

   // session start
  session_start();

   // application configuration
  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);

     // retrieve session parameters
  $session=$_SESSION["session"];
   // valid session?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // start a new session
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // mistakes?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // display error page in XML format
          header("Content-type: text/xml");      
        include $erreursImpots;
       // end
      $session=array();
      terminerSession($session);
    }//if
  }//if

   // retrieve the parameters of the current exchange
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // do we have all the parameters?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // empty form display
    include $formulaireImpots;
     // end
    terminerSession($session);
  }//if

   // checking parameters
  $requête=vérifier($requête);

   // mistakes?
  if(count($requête[erreurs])!=0){
       // display errors in XML format
      header("Content-type: text/xml");    
    include "$erreursImpots";
     // end
    terminerSession($session);
  }//if

   // calculating tax payable
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // one more simulation
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // display simulations in XML format
  header("Content-type: text/xml");
  include "$simulationsImpots";

   // end
  terminerSession($session);

We previously presented and examined the two types of XML responses to be provided, as well as the style sheets that must accompany them. The code for the impots_simulations.php application is as follows:

<?php
     // generates XML code for impots application simulations page

   // some constants
  $xslSimulations="simulations.xsl";

   // headers XML
  echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
  echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslSimulations\" ?>\n";    
  // simulations
  echo "<simulations>\n";
  for ($i=0;$i<count($requête[simulations]);$i++){
      // simulation $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";
  }//for
  echo "</simulations>\n";
?>

This code uses the $requête dictionary to generate XML code similar to the following:


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

The simulations.xsl style sheet will transform this XML code into HTML code.

The code for the impots_erreurs.php application is as follows:

<?php
     // generates code XML for impots application error page

   // some constants
  $xslErreurs="erreurs.xsl";

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

This code uses the $requête dictionary to generate XML code similar to the following:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl" ?>
<erreurs>
<erreur>Impossible d'open database DSN [mysql-dbimpots] (S1000)</error>
</erreurs>

The erreurs.xsl stylesheet will transform this XML code into HTML code.

Let’s look at a first example:

Image

The SGBD MySQL is not triggered. We then receive the following response:

Image

If we look at the source code received by the browser, we see the following:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl" ?>
<erreurs>
<erreur>Impossible d'open database DSN [mysql-dbimpots] (S1000)</error>
</erreurs>

Now, we run SGBD and MySQL and perform several successive simulations. We receive the following response:

Image

The code received by the browser is as follows:

<?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 that our new application is easier to maintain than the previous one. Part of the work has been transferred to the XSL style sheets. The advantage of this new division of tasks is that once the XML format of the responses has been established, the development of the style sheets is independent of that of the application.

5.3. Analysis of a XML document in PHP

The next version of our tax application will be a client programmed for the previous xmlsimulations application. Our client will therefore receive XML code that it will need to parse to extract the information it needs. We will now take a break from our various versions and learn how to parse a XML document into PHP. We will do this using the following example:


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

The xmlParser.php application accepts one parameter: the URI (Uniform Resource Identifier) of the XML document to be parsed. In our example, this URI will simply be the name of a XML file located in the directory of the xmlParser.php application. Let’s consider two examples of execution. In the first example, the XML file being parsed is the following errors.xml file:

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

The analysis yields the following results:


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

We hadn't yet described what the xmlParser.php application does, but here we can see that it displays the structure of the parsed XML document. The second example parses the following XML simulations.xml file:

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

The analysis yields the following results:


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

The xmlParser.php application contains everything we need in our tax application since it was able to retrieve both the errors and the simulations that the web server might send. Let’s examine its code:

<?php
   // syntax $0 fichierXML
   // displays the structure and contents of the fichierXML file

   // call verification
  if(count($argv)!=2){
       // error msg
    fwrite(STDERR,"syntaxe : $argv[0] fichierXML\n");
     // end
    exit(1);
  }//if

   // initializations
  $file=$argv[1];       // the xml file
  $depth=0;               // indentation level=depth in tree structure

   // the program

   // create a text analysis object xml
  $xml_parser=xml_parser_create();

   // indicate which functions to execute at the beginning and end of the tag
  xml_set_element_handler($xml_parser,"startElement","endElement");

   // indicates which function to execute when data is encountered
  xml_set_character_data_handler($xml_parser,"afficheData");

  // open file xml in read mode
  if (! ($fp=@fopen($file,"r"))){
    fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
    exit(2);
  }//if
   // exploitation of the xml file in blocks of 4096 bytes
  while($data=fread($fp,4096)){
    // analysis of read data
    if (! xml_parse($xml_parser,$data,feof($fp))){
      // an error has occurred
      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));
       // end
      exit(3);
    }//if
  }//while
  // the file has been browsed
   // release the resources occupied by the xml scanner
  xml_parser_free($xml_parser);
   // end
  exit(0);

    // -----------------------------------------------------------  
   // function called when a start tag is encountered
  function startElement($parser,$name,$attributs){
    global $depth;
     // a sequence of spaces (indentation)
    for($i=0;$i<$depth;$i++){
      print " ";
    }//for
     // attributes
    $précisions="";
    while(list($attrib,$valeur)=each($attributs)){
      $précisions.="($attrib,$valeur) ";
    }
     // displays the tag name and any attributes
    if($précisions)
      print "$name,$précisions\n";
    else print "$name\n";
     // an extra level of tree structure
    $depth++;
  }//startElement

    // -----------------------------------------------------------  
   // the function called when an end tag is encountered
  function endElement($parser,$name){
     // end of tag
     // indentation level
    global $depth;
    $depth--;
    // a sequence of spaces (indentation)
    for($i=0;$i<$depth;$i++){
      echo " ";
    }//for
     // tag name
    echo "/$name\n";
  }//endElement

    // -----------------------------------------------------------  
   // data display function
  function afficheData($parser,$data){
     // indentation level
    global $depth;
     // data are displayed
    $data=trim($data);
    if($data!=""){
       // a sequence of spaces (indentation)
      for($i=0;$i<$depth;$i++){
        echo " ";
      }//for
      echo "[$data]\n";
    }//if
  }//afficheData
?>

Let's examine the code related to XML. To parse a XML document, our application needs a XML parser, commonly referred to as a "parser."

<?php
...
   // create a text analysis object xml
  $xml_parser=xml_parser_create();

When the parser analyzes the XML document, it will trigger events such as: I have reached the start of the document, the start of a tag, a tag attribute, the content of a tag, the end of a tag, the end of the document, ... It passes these events to methods that we must specify:

<?php
...
   // indicate which functions to execute at the beginning and end of the tag
  xml_set_element_handler($xml_parser,"startElement","endElement");
   // indicates which function to execute when data is encountered
  xml_set_character_data_handler($xml_parser,"afficheData");
event emitted by the
parser
processing method
début d'an element :<beacon>
function startElement($parser, $name, $attributes)
$parser: the document parser
$name: name of the parsed element. If the encountered element is <simulations>, we will have name="simulations".
$attributs: list of the tag's attributes in the form (ATTRIBUT,value) where ATTRIBUT is the tag name in uppercase.
value of an element:
<balise>valeur</balise>
function displayData($parser, $data)
$parser: the document parser
$data: the tag data
fin d'an element: </beacon> or <beacon .../>
function endElement($parser, $name)
The parameters are those of the startElement method.

The startElement function retrieves the element's attributes using the $attributs parameter. This parameter is a dictionary of the tag's attributes. For example, if we have the following tag:

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

the dictionary $attributs will be as follows: array(marie=>yes,children=>2,salary=>200000,tax=>22504)

Once the parser and the preceding methods have been defined, document analysis is performed by the xml_parse function:

<?php
...
   // exploitation of the xml file in blocks of 4096 bytes
  while($data=fread($fp,4096)){
    // analysis of read data
    if (! xml_parse($xml_parser,$data,feof($fp))){
...
    }//if
  }//while
analyse d'a
document $doc
function xml_parse($parser, $doc, $end)
The parser $parser parses the document $doc. $doc may be a fragment of a larger document. The parameter $fin indicates whether this is the last part (true) or not (false). When parsing the document $doc, the functions defined by xml_set_element_handler are called at the start and end of each tag. Is the function defined by xml_set_character_data_handler called every time the content of a tag is retrieved?

During the analysis of document XML, errors may occur, particularly if document XML is "malformed," for example, due to missing closing tags. In this case, the xml_parse function returns a value evaluated as "false":

<?php
...
    if (! xml_parse($xml_parser,$data,feof($fp))){
      // an error has occurred
      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));
      // end
      exit(3);
    }//if
gestion d'errors
function xml_get_error_code($parser)
returns the code of the last error that occurred
--
function xml_error_string($code)
returns the error message associated with the code passed as a parameter
--
function xml_get_current_line($parser)
returns the line number of the document XML currently being parsed

Once the document has been parsed, the resources allocated to the parser are released:

<?php
...
   // free the resources occupied by the xml scanner
  xml_parser_free($xml_parser);
freeing
ressources attribuées
to the $parser parser
function xml_free($parser)

With this explained, the previous program along with the execution examples is self-explanatory.

5.4. Tax application: version 6

We now have all the elements to write a clients program for our tax service that generates XML. We reuse the version 4 from our application to act as the client and keep the version 5 for the server. In this client-server application:

  • the tax calculation simulation service is handled by the xmlsimulations.php application. The server’s response is therefore in the XML format, as we saw in version 5.
  • the client is no longer a browser but a standalone php client. Its console interface is that of version 4.

The reader is invited to review the code for the cltImpots.php application, which was the client program for version 4. This client received a $document document from the server. That document was then a HTML document. It is now a XML document. The HTML and $document documents were parsed by the following function:

<?php
...
  function getInfos($document){
       // $document : document HTML
     // search for either the list of errors
     // or the simulation table

     // preparing the result
    $impots[erreurs]=array();
    $impots[simulations]=array();

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

The function received the document HTML $document, analyzed it, and returned a dictionary $impots with two attributes:

  1. errors: an array of errors
  2. simulations: an array of simulations, each simulation being itself an array with four elements (spouse, children, salary, tax).

The application cltImpots.php is now renamed cltXmlSimulations.php. Only the part that processes the document received from the server needs to be modified to account for the fact that it is now a XML document. The getInfos function then becomes the following:

<?php
...
// --------------------------------------------------------------
  function getInfos($document){
       // $document : document XML
     // search for either the list of errors
     // or the simulation table

    global $impots,$balises;

     // preparing the result
    $impots[erreurs]=array();
    $impots[simulations]=array();
     // beacons in progress
    $balises=array();

     // create a text analysis object xml
    $xml_parser=xml_parser_create();
     // indicate which functions to execute at the beginning and end of the tag
    xml_set_element_handler($xml_parser,"startElement","endElement");
     // indicates which function to execute when data is encountered
    xml_set_character_data_handler($xml_parser,"getData");
    // we analyze $document
    xml_parse($xml_parser,$document);
     // release the resources occupied by the xml scanner
    xml_parser_free($xml_parser);
     // end
    return $impots;
  }//getInfos

    // -----------------------------------------------------------  
  // function called when a start tag is encountered
  function startElement($parser,$name,$attributs){
    global $impots,$balise,$balises,$contenu;

     // note the tag name and content
    $balise=strtolower($name);
    $contenu="";
     // we add it to the stack of
    array_push($balises,$balise);    
    // is it a simulation?
    if($balise=="simulation"){   
         // simulation attributes are noted
      $impots[simulations][]=array($attributs[MARIE],$attributs[ENFANTS],$attributs[SALAIRE],$attributs[IMPOT]);
    }//if
  }//startElement

    // -----------------------------------------------------------  
   // the function called when an end tag is encountered
  function endElement($parser,$name){
       // retrieve the current tag
    global $impots,$balises,$contenu;
    $balise=array_pop($balises);
    // is this an error tag?
    if($balise=="erreur"){
         // one more mistake
      $impots[erreurs][]=trim($contenu);
    }//if
  }//endElement

    // -----------------------------------------------------------  
   // the tag content processing function
  function getData($parser,$data){
      // global data
    global $balise,$contenu;
     // is this an error tag?
    if($balise=="erreur"){
         // is added to the content of the current tag
      $contenu.=$data;
    }//if
  }//getData

Comments:

  • The getInfos($document) function begins by creating a parser, then configures it, and finally starts parsing the $document document:
<?php
...
     // create a text analysis object xml
    $xml_parser=xml_parser_create();
     // indicate which functions to execute at the beginning and end of the tag
    xml_set_element_handler($xml_parser,"startElement","endElement");
     // indicates which function to execute when data is encountered
    xml_set_character_data_handler($xml_parser,"getData");
    // we analyze $document
    xml_parse($xml_parser,$document);
  • Upon completion of parsing, release the resources allocated to the parser and return the dictionary $impots.
<?php
...
     // release the resources occupied by the xml scanner
    xml_parser_free($xml_parser);
     // end
    return $impots;
  • The function startElement($parser,$name,$attributs) is called at the start of each tag. It
    • adds the tag $name to a tag array $balises. This array is managed as a stack: upon encountering the end-of-tag symbol, the last tag pushed onto $balises will be popped. The current tag is also recorded in $balise. In the dictionary $attributs, the attributes of the encountered tag are stored, with these attributes in uppercase.
    • saves the attributes in the global dictionary $impots[simulations], if it is a simulation tag.
  • the function getData($parser,$data) when the content $data of a tag has been retrieved. Here, a precaution has been taken. In certain API document processing libraries, notably Java, it is stated that this function can be called repeatedly, XML. that the content of a tag is not necessarily available all at once. Here, the documentation does not mention this restriction. As a precaution, we store the obtained value in a global variable. Only upon encountering the end-of-tag symbol will we consider that we have obtained the entire content of the tag. The only tag affected by this processing is the <error> tag.
  • The function endElement($parser,$name) is called at the end of each tag. It is used here to change the name of the current tag by removing the last tag from the tag stack and to add the content of the <error> tag, which ends in the array $impots[erreurs].

Here are a few examples of execution, first with a SGBD that has not been launched:

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

Then, with SGBD launched:


dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php yes 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 yes 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. Conclusion

Thanks to its XML response, the tax application has become easier to manage for both its developer and the developers of client applications.

  • The design of the server application can now be entrusted to two types of people: the PHP servlet developer and the graphic designer who will manage the appearance of the server response in browsers. The latter simply needs to know the structure of the server's response to build the accompanying style sheets. Note that these are contained in separate XSL files that are independent of the PHP application. The graphic designer can therefore work independently of the developer.
  • Client application designers, too, simply need to know the structure of the server’s XML response. Any changes the graphic designer might make to the style sheets have no impact on this XML response, which always remains the same. This is a huge advantage.
  • How can the developer update their PHP application without breaking anything? First of all, as long as their XML response remains unchanged, they can organize their application however they like. They can also update the XML response as long as they keep the <error> and <simulation> elements expected by their clients. This allows them to add new tags to this response. The web designer will account for them in their style sheets, and browsers will be able to receive the new versions of the response. The pre-programmed clientss will continue to function with the old model, with the new tags simply being ignored. For this to be possible, the tags being searched for must be clearly identified in the XML analysis of the server’s response. This is what was done in our XML client for the tax application, where the procedures specifically stated that we were processing the <error> and <simulation> tags. As a result, the other tags are ignored.