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:

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:

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

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

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.
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:
We will see later what this text is. Finally, the interpreter will add the text:
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:
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
will be converted to line HTML:
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:

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:

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:

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:

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

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:

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."
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 |
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. | |
function displayData($parser, $data) $parser: the document parser $data: the tag data | |
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:
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
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
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:
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:
- errors: an array of errors
- 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.