Skip to content

5. XML und PHP

In diesem Kapitel geben wir eine Einführung in die Verwendung von XML-Dokumenten (eXtensible Markup Language) mit PHP. Wir tun dies im Zusammenhang mit der im vorigen Kapitel besprochenen Steueranwendung.

5.1. XML-Dateien und XSL-Stylesheets

Betrachten Sie die folgende XML-Datei, die die Ergebnisse von Simulationen darstellen könnte:

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

Bei der Anzeige mit IE 6 ergibt sich folgendes Ergebnis:

Image

IE6 erkennt, dass es sich um eine XML-Datei handelt (dank der Dateiendung .xml), und formatiert sie auf seine eigene Weise. Mit Netscape erhält man eine leere Seite. Wenn man sich jedoch den Quellcode ansieht (Ansicht/Quelltext), sieht man die ursprüngliche XML-Datei:

Image

Warum zeigt Netscape nichts an? Weil es ein Stylesheet benötigt, das ihm mitteilt, wie die XML-Datei in eine HTML-Datei umgewandelt werden soll, die es dann anzeigen kann. Es stellt sich heraus, dass IE 6 über ein Standard-Stylesheet verfügt, wenn die XML-Datei kein eigenes bereitstellt, was hier der Fall war.

Es gibt eine Sprache namens XSL (eXtensible StyleSheet Language), mit der man die Umwandlungen beschreiben kann, die erforderlich sind, um eine XML-Datei in eine beliebige Textdatei zu konvertieren. XSL unterstützt zahlreiche Anweisungen und ähnelt stark Programmiersprachen. Wir werden hier nicht ins Detail gehen, da dies Dutzende von Seiten in Anspruch nehmen würde. Wir werden lediglich zwei Beispiele für XSL-Stylesheets beschreiben. Das erste ist dasjenige, das die XML-Datei simulations.xml in HTML-Code umwandelt. Wir ändern letztere so, dass sie das Stylesheet angibt, das Browser verwenden können, um sie in ein HTML-Dokument umzuwandeln, das sie anzeigen können:

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

Der XML-Befehl

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

bezeichnet die Datei simulations.xsl als XML-Stylesheet vom Typ text/xsl, d. h. als Textdatei, die XSL-Code enthält. Dieses Stylesheet wird von Browsern verwendet, um den XML-Text in ein HTML-Dokument umzuwandeln. Hier ist das Ergebnis, das mit Netscape 7 beim Laden der XML-Datei simulations.xml erzielt wurde:

Image

Wenn wir den Quellcode des Dokuments anzeigen (Ansicht/Quelltext), sehen wir das ursprüngliche XML-Dokument und nicht das angezeigte HTML-Dokument:

Image

Netscape verwendete das Stylesheet „simulations.xsl“, um das obige XML-Dokument in ein anzeigbares HTML-Dokument umzuwandeln. Nun ist es an der Zeit, uns den Inhalt dieses Stylesheets anzusehen:

<?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>
  • Ein XSL-Stylesheet ist eine XML-Datei und folgt daher den XML-Regeln. Unter anderem muss es „wohlgeformt“ sein, was bedeutet, dass jeder öffnende Tag geschlossen werden muss.
  • Die Datei beginnt mit zwei XML-Direktiven, die in jedem XSL-Stylesheet unter Windows enthalten sein können:
<?xml version="1.0" encoding="windows-1252"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

Das Attribut „encoding=“windows-1252““ ermöglicht die Verwendung von Zeichen mit Akzenten im Stylesheet.

  • Das Tag <xsl:output method="html" indent="yes"/> teilt dem XSL-Interpreter mit, dass wir „eingerückten“ HTML-Code erzeugen möchten.
  • Das Tag <xsl:template match="element"> dient dazu, das Element im XML-Dokument zu definieren, auf das die Anweisungen zwischen <xsl:template ...> und </xsl:template> angewendet werden sollen.
    <xsl:template match="/">
................
        </xsl:template>

Im obigen Beispiel bezieht sich das Element „/“ auf die Wurzel des Dokuments. Das bedeutet, dass die zwischen den beiden Tags befindlichen XSL-Befehle ausgeführt werden, sobald der Anfang des XML-Dokuments erreicht ist.

  • Alles, was kein XSL-Tag ist, wird unverändert in den Ausgabestrom übernommen. Die XSL-Tags selbst werden ausgeführt. Einige von ihnen erzeugen ein Ergebnis im Ausgabestrom. Betrachten wir das folgende Beispiel:
    <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>

Beachten Sie, dass das zu analysierende XML-Dokument wie folgt lautet:

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

Ab dem Anfang des geparsten XML-Dokuments (match="/") gibt der XSL-Prozessor den Text

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

Beachten Sie, dass im Originaltext <hr/> stand und nicht <hr>. Im Originaltext konnten wir <hr> nicht schreiben, da es zwar ein gültiger HTML-Tag, aber ein ungültiger XML-Tag ist. Wir haben es hier jedoch mit XML-Text zu tun, der „wohlgeformt“ sein muss, was bedeutet, dass jeder Tag geschlossen werden muss. Wir schreiben daher <hr/>, und da wir <xsl:output text="html ...> geschrieben haben, wandelt der Interpreter den Text <hr/> in <hr> um. Auf diesen Text folgt der durch den XSL-Befehl erzeugte Text:

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

Wir werden später sehen, um welchen Text es sich dabei handelt. Schließlich fügt der Interpreter den Text hinzu:

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

Der Befehl <xsl:apply-templates select="/simulations/simulation"/> weist den Interpreter an, die „Vorlage“ auf das Element /simulations/simulation anzuwenden. Er wird jedes Mal ausgeführt, wenn der XSL-Interpreter innerhalb eines <simulations>..</simulations>-Tags im geparsten XML-Text auf ein <simulation>..</simulations>- oder <simulation/>-Tag stößt. Wenn der Interpreter auf ein solches Tag stößt, führt er die Anweisungen der folgenden Vorlage aus:

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

Betrachten Sie die folgenden XML-Zeilen:

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

Die Zeile <simulation ..> entspricht der Vorlage für die XSL-Anweisung <xsl:apply-templates select="/simulations/simulation">. Der XSL-Interpreter wird daher versuchen, die Anweisungen, die dieser Vorlage entsprechen, darauf anzuwenden. Er findet die Vorlage <xsl:template match="simulation"> und führt sie aus. Zur Erinnerung: Alles, was kein XSL-Befehl ist, wird vom XSL-Interpreter unverändert weitergeleitet, während XSL-Befehle durch das Ergebnis ihrer Ausführung ersetzt werden. Die XSL-Anweisung <xsl:value-of select="@champ"/> wird somit durch den Wert des Attributs „champ“ des geparsten Knotens (hier ein <simulation>-Knoten) ersetzt. Das Parsen der vorherigen XML-Zeile erzeugt die folgende Ausgabe:

XSL
Ausgabe
<tr><td>
<tr><td>
<xsl:value-of select="@marie"/>
ja
</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>

Insgesamt lautet die XML-Zeile

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

wird in die folgende HTML-Zeile umgewandelt:

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

All diese Erklärungen sind zwar etwas rudimentär, aber dem Leser sollte nun klar sein, dass der folgende XML-Text:

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

begleitet von dem folgenden XSL-Stylesheet 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>

erzeugt den folgenden HTML-Text:

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

Die folgende XML-Datei 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>

Bei der Anzeige in einem modernen Browser (hier Netscape 7) wird dies wie folgt dargestellt:

Image

5.2. Steueranwendung: Version 5

5.2.1. Die XML-Dateien und XSL-Stylesheets der Steueranwendung

Kehren wir zu unserem Ausgangspunkt zurück, nämlich der Steuer-Webanwendung, und erinnern wir uns daran, dass wir sie so ändern wollen, dass die an die Clients gesendete Antwort im XML-Format statt im HTML-Format vorliegt. Dieser HTML-Antwort wird ein XSL-Stylesheet beigefügt, damit Browser sie anzeigen können. Im vorigen Absatz haben wir Folgendes vorgestellt:

  • die Datei simulations.xml, die einen Prototyp einer XML-Antwort mit Simulationen zur Steuerberechnung enthält
  • die Datei simulations.xsl, die das dieser XML-Antwort beigefügte XSL-Stylesheet sein wird

Wir müssen auch den Fall einer Antwort berücksichtigen, die Fehler enthält. Der Prototyp für die XML-Antwort in diesem Fall ist die folgende Datei errors.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>

Das Stylesheet „errors.xsl“, das zur Anzeige dieses XML-Dokuments in einem Browser verwendet wird, sieht wie folgt aus:

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

Dieses Stylesheet führt einen bisher noch nicht behandelten XSL-Befehl ein: <xsl:value-of select="."/>. Dieser Befehl gibt den Wert des analysierten Knotens aus, in diesem Fall eines <error>text</error>-Knotens. Der Wert dieses Knotens ist der Text zwischen dem öffnenden und dem schließenden Tag, in diesem Fall „text“.

Der Code in errors.xml wird durch das Stylesheet errors.xsl in das folgende HTML-Dokument umgewandelt:

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

Die Datei „errors.xml“ wird zusammen mit ihrem Stylesheet von einem Browser wie folgt angezeigt:

Image

5.2.2. Die Anwendung „xmlsimulations“

Wir erstellen eine Datei „xmlsimulations.html“ und legen sie im Anwendungsverzeichnis „imports“ ab. Die angezeigte Seite sieht wie folgt aus:

Image

Dieses HTML-Dokument ist ein statisches Dokument. Sein Code lautet wie folgt:

<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
      }//effacer

      function calculer(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // c'est bon - on envoie
          submit();
        }//with
      }//calculer        
        </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>

Beachten Sie, dass die Formulardaten an die URL /poly/impots/7/xmlsimulations.php gesendet werden. Der Code für die Anwendung xmlsimulations.php ist dem der Anwendung impots.php sehr ähnlich. Dem Leser wird empfohlen, sich letztere anzusehen. Hier noch einmal der Eingabecode:

<?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);
...

Die HTML-Seiten wurden mithilfe der „include“-Zeilen angezeigt. Hier möchten wir nun XML anstelle von HTML generieren. Erstellen Sie dazu einfach zwei neue Dateien, „impots_erreurs.php“ und „impots_simulations.php“, um XML anstelle von HTML zu generieren. Der Rest der Anwendung bleibt unverändert. Der Code sieht dann wie folgt aus:

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

   // calculation of 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);

Wir haben zuvor die beiden Arten von XML-Antworten, die bereitgestellt werden sollen, sowie die dazugehörigen Stylesheets vorgestellt und untersucht. Der Code für die Anwendung impots_simulations.php lautet wie folgt:

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

Dieser Code verwendet das $query-Dictionary, um XML-Code zu generieren, der in etwa wie folgt aussieht:


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

Das Stylesheet „simulations.xsl“ wandelt diesen XML-Code in HTML-Code um.

Der Code für die Anwendung impots_erreurs.php lautet wie folgt:

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

Dieser Code verwendet das $request-Dictionary, um XML-Code zu generieren, der in etwa wie folgt aussieht:

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

Das Stylesheet „errors.xsl“ wandelt diesen XML-Code in HTML-Code um.

Sehen wir uns ein erstes Beispiel an:

Image

Das MySQL-DBMS läuft nicht. Wir erhalten dann die folgende Antwort:

Image

Wenn wir uns den vom Browser empfangenen Quellcode ansehen, sehen wir Folgendes:

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

Nun starten wir das MySQL-DBMS und führen eine Reihe von Simulationen durch. Wir erhalten folgende Antwort:

Image

Der vom Browser empfangene Code lautet wie folgt:

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

Beachten Sie, dass unsere neue Anwendung einfacher zu warten ist als die vorherige. Ein Teil der Arbeit wurde auf die XSL-Stylesheets verlagert. Der Vorteil dieser neuen Aufgabenteilung besteht darin, dass die Entwicklung der Stylesheets, sobald das XML-Format der Antworten festgelegt ist, unabhängig von der Entwicklung der Anwendung erfolgt.

5.3. Parsen eines XML-Dokuments in PHP

Die nächste Version unserer Steueranwendung wird ein Client-Programm für die vorherige Anwendung „xmlsimulations“ sein. Unser Client erhält daher XML-Code, den er parsen muss, um die benötigten Informationen zu extrahieren. Wir machen nun eine Pause von unseren verschiedenen Versionen und lernen, wie man ein XML-Dokument in PHP parst. Dazu verwenden wir das folgende Beispiel:


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

Die Anwendung xmlParser.php akzeptiert einen Parameter: die URI (Uniform Resource Identifier) des zu parsten XML-Dokuments. In unserem Beispiel ist diese URI einfach der Name einer XML-Datei, die sich im Verzeichnis der Anwendung xmlParser.php befindet. Betrachten wir zwei Ausführungsbeispiele. Im ersten Beispiel ist die zu parste XML-Datei die folgende Datei errors.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>

Die Analyse liefert folgende Ergebnisse:


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

Wir haben noch nicht beschrieben, was die Anwendung xmlParser.php macht, aber hier sehen wir, dass sie die Struktur des geparsten XML-Dokuments anzeigt. Das zweite Beispiel parst die folgende XML-Datei, 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>

Die Analyse liefert folgende Ergebnisse:


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

Die Anwendung xmlParser.php enthält alles, was wir für unsere Steueranwendung benötigen, da sie sowohl die Fehler als auch die Simulationen abrufen konnte, die der Webserver möglicherweise sendet. Sehen wir uns den Code an:

<?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 an xml text analysis object
  $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 xml file in read mode
  if (! ($fp=@fopen($file,"r"))){
    fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
    exit(2);
  }//if
   // xml file processing 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
   // free up resources occupied by the xml parser
  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
?>

Sehen wir uns den Code im Zusammenhang mit XML an. Um ein XML-Dokument zu parsen, benötigt unsere Anwendung einen XML-Parser.

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

Wenn der Parser das XML-Dokument analysiert, löst er Ereignisse aus wie: Ich bin auf den Anfang des Dokuments gestoßen, auf den Anfang eines Tags, auf ein Tag-Attribut, auf den Inhalt eines Tags, auf das Ende eines Tags, auf das Ende des Dokuments, ... Er leitet diese Ereignisse an Methoden weiter, die wir festlegen müssen:

<?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");
Ereignis, das vom
Parser
Verarbeitungsmethode
am Anfang eines Elements: <tag>
Funktion startElement($parser, $name, $attributes)
$parser: der Dokument-Parser
$name: Name des geparsten Elements. Wenn das gefundene Element <simulations> ist, lautet der Name „simulations“.
$attributes: Liste der Attribute des Tags in der Form (ATTRIBUTE,Wert), wobei ATTRIBUTE der Name des Tags in Großbuchstaben ist.
Wert eines Elements:
<Tag>Wert</Tag>
Funktion displayData($parser, $data)
$parser: der Dokument-Parser
$data: die Daten des Tags
Ende eines Elements: </tag> oder <tag .../>
Funktion endElement($parser, $name)
Die Parameter sind dieselben wie bei der Methode startElement.

Die Funktion startElement ruft die Attribute des Elements über den Parameter $attributes ab. Dieser Parameter ist ein Wörterbuch der Attribute des Tags. Wenn wir also das folgende Tag haben:

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

dann sieht das $attributes-Wörterbuch wie folgt aus: array(marie=>yes,children=>2,salary=>200000,tax=>22504)

Sobald der Parser und die vorangehenden Methoden definiert sind, wird ein Dokument mit der Funktion xml_parse geparst:

<?php
...
   // xml file processing in blocks of 4096 bytes
  while($data=fread($fp,4096)){
    // analysis of read data
    if (! xml_parse($xml_parser,$data,feof($fp))){
...
    }//if
  }//while
ein
Dokument $doc
Funktion xml_parse($parser, $doc, $end)
Der Parser $parser analysiert das Dokument $doc. $doc kann ein Fragment eines größeren Dokuments sein. Der Parameter $end gibt an, ob dies der letzte Teil ist (true) oder nicht (false). Beim Parsen des Dokuments $doc werden die durch xml_set_element_handler definierten Funktionen am Anfang und am Ende jedes Tags aufgerufen. Die durch xml_set_character_data_handler definierte Funktion wird jedes Mal aufgerufen, wenn der Inhalt eines Tags abgerufen wird.

Während des Parsens des XML-Dokuments können Fehler auftreten, insbesondere wenn das XML-Dokument „fehlformatiert“ ist, beispielsweise wenn schließende Tags fehlen. In diesem Fall gibt die Funktion xml_parse den Wert „false“ zurück:

<?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
Fehlerbehandlung
function xml_get_error_code($parser)
gibt den Code des zuletzt aufgetretenen Fehlers zurück
--
Funktion xml_error_string($code)
gibt die Fehlermeldung zurück, die dem als Parameter übergebenen Code zugeordnet ist
--
Funktion xml_get_current_line($parser)
gibt die Zeilennummer des XML-Dokuments zurück, das gerade geparst wird

Sobald das Dokument geparst wurde, werden die dem Parser zugewiesenen Ressourcen freigegeben:

<?php
...
   // free up resources occupied by the xml parser
  xml_parser_free($xml_parser);
Freigabe
Ressourcen freigeben
dem $parser-Parser
Funktion xml_free($parser)

Mit dieser Erläuterung sind das vorangegangene Programm und seine Ausführungsbeispiele selbsterklärend.

5.4. Steueranwendung: Version 6

Wir verfügen nun über alle Elemente, um ein Client-Programm für unseren Steuerdienst zu schreiben, der XML liefert. Wir werden Version 4 unserer Anwendung für den Client verwenden und Version 5 für den Server beibehalten. In dieser Client-Server-Anwendung:

  • wird der Dienst zur Simulation der Steuerberechnung von der Anwendung xmlsimulations.php übernommen. Die Antwort des Servers liegt daher im XML-Format vor, wie wir es in Version 5 gesehen haben.
  • Der Client ist kein Browser mehr, sondern ein eigenständiger PHP-Client. Seine Konsolenoberfläche entspricht der von Version 4.

Der Leser ist eingeladen, sich den Code der Anwendung cltImpots.php anzusehen, die das Client-Programm für Version 4 war. Sie empfing ein $document vom Server. Dies war damals ein HTML-Dokument. Jetzt ist es ein XML-Dokument. Das HTML-Dokument $document wurde von der folgenden Funktion geparst:

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

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

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

Die Funktion hat das HTML-Dokument $document empfangen, es geparst und ein Dictionary $taxes mit zwei Attributen zurückgegeben:

  1. errors: ein Array mit Fehlern
  2. simulations: ein Array von Simulationen, wobei jede Simulation selbst ein Array mit vier Elementen ist (Ehepartner, Kinder, Gehalt, Steuer).

Die Anwendung cltImpots.php wird nun in cltXmlSimulations.php umbenannt. Es muss lediglich der Teil geändert werden, der das vom Server empfangene Dokument verarbeitet, um der Tatsache Rechnung zu tragen, dass es sich nun um ein XML-Dokument handelt. Die Funktion getInfos sieht dann wie folgt aus:

<?php
...
// --------------------------------------------------------------
  function getInfos($document){
       // $document : document XML
     // we look 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 an xml text analysis object
    $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);
     // free up resources occupied by the xml parser
    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

Kommentare:

  • Die Funktion getInfos($document) erstellt zunächst einen Parser, konfiguriert diesen anschließend und beginnt schließlich mit der Analyse des $document:
<?php
...
     // create an xml text analysis object
    $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);
  • Geben Sie nach dem Parsen die dem Parser zugewiesenen Ressourcen frei und geben Sie das $imports-Wörterbuch zurück.
<?php
...
     // free up resources occupied by the xml parser
    xml_parser_free($xml_parser);
     // end
    return $impots;
  • Die Funktion startElement($parser, $name, $attributes) wird am Anfang jedes Tags aufgerufen. Sie
    • fügt das Tag $name zu einem Tag-Array $tags hinzu. Dieses Array wird als Stapel verwaltet: Wenn das Symbol für das Tag-Ende auftritt, wird das zuletzt in $tags eingefügte Tag entfernt. Das aktuelle Tag wird zudem in $tag gespeichert. Das $attributes-Wörterbuch enthält die Attribute des gefundenen Tags, wobei diese Attribute in Großbuchstaben geschrieben sind.
    • speichert die Attribute im globalen Wörterbuch $impots[simulations], wenn es sich um ein Simulations-Tag handelt.
  • Die Funktion getData($parser,$data) wird aufgerufen, wenn der Inhalt $data eines Tags abgerufen wurde. Hier wurde eine Vorsichtsmaßnahme getroffen. Bei bestimmten APIs zur Verarbeitung von XML-Dokumenten, insbesondere in Java, ist zu beachten, dass diese Funktion wiederholt aufgerufen werden kann, d. h., der Inhalt $data eines Tags ist nicht unbedingt auf einmal verfügbar. Die PHP-Dokumentation erwähnt diese Einschränkung nicht. Als Vorsichtsmaßnahme speichern wir den erhaltenen $data-Wert in einer globalen Variablen $content. Erst wenn wir auf das End-of-Tag-Symbol stoßen, gehen wir davon aus, dass wir den gesamten Inhalt des Tags erhalten haben. Das einzige Tag, das von dieser Verarbeitung betroffen ist, ist das <error>-Tag.
  • Die Funktion endElement($parser, $name) wird am Ende jedes Tags aufgerufen. Sie wird hier verwendet, um den Namen des aktuellen Tags zu ändern, indem das letzte Tag aus dem Tag-Stack entfernt wird, und um den Inhalt des schließenden <error>-Tags dem Array $impots[errors] hinzuzufügen.

Hier sind einige Ausführungsbeispiele, zunächst mit einem DBMS, das noch nicht gestartet wurde:

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

Anschließend, während das DBMS läuft:


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

5.5. Fazit

Dank der XML-Antwort ist die Steueranwendung sowohl für den Entwickler als auch für die Entwickler von Client-Anwendungen einfacher zu verwalten.

  • Die Gestaltung der Serveranwendung kann nun zwei Personengruppen anvertraut werden: dem PHP-Entwickler des Servlets und dem Grafikdesigner, der das Erscheinungsbild der Browser-Antwort in den Browsern gestaltet. Letzterer muss lediglich die Struktur der XML-Antwort des Servers kennen, um die dazugehörigen Stylesheets zu erstellen. Beachten Sie, dass diese in separaten XSL-Dateien enthalten sind, die unabhängig von der PHP-Anwendung sind. Der Frontend-Designer kann daher unabhängig vom Entwickler arbeiten.
  • Auch die Entwickler von Client-Anwendungen müssen lediglich die Struktur der XML-Antwort des Servers kennen. Etwaige Änderungen, die der Frontend-Designer an den Stylesheets vornimmt, haben keinen Einfluss auf diese XML-Antwort, die stets unverändert bleibt. Dies ist ein enormer Vorteil.
  • Wie kann der Entwickler seine PHP-Anwendung aktualisieren, ohne alles kaputt zu machen? Zunächst einmal kann er seine Anwendung beliebig organisieren, solange die XML-Antwort unverändert bleibt. Er kann die XML-Antwort auch aktualisieren, solange er die von seinen Clients erwarteten Elemente <error> und <simulation> beibehält. Er kann dieser Antwort somit neue Tags hinzufügen. Der Frontend-Entwickler wird diese in seinen Stylesheets berücksichtigen, und Browser können die aktualisierte Version der Antwort anzeigen. Programmatische Clients hingegen werden weiterhin nach dem alten Modell funktionieren, wobei die neuen Tags einfach ignoriert werden. Damit dies funktioniert, müssen die gesuchten Tags bei der XML-Analyse der Serverantwort eindeutig identifiziert werden. Genau das wurde in unserem XML-Client für die Steueranwendung getan, wo in den Prozeduren ausdrücklich festgelegt wurde, dass wir die Tags <error> und <simulation> verarbeiten. Infolgedessen werden die anderen Tags ignoriert.