Skip to content

5. XML i PHP

W niniejszym rozdziale przedstawimy wprowadzenie do korzystania z dokumentów XML (eXtensible Markup Language) wraz z PHP. Zrobimy to w kontekście aplikacji podatkowej omówionej w poprzednim rozdziale.

5.1. Pliki XML i arkusze stylów XSL

Rozważmy następujący plik XML, który mógłby przedstawiać wyniki symulacji:

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

Jeśli wyświetlimy to za pomocą IE 6, otrzymamy następujący wynik:

Image

IE6 rozpoznaje, że ma do czynienia z plikiem XML (dzięki rozszerzeniu .xml pliku) i formatuje go na swój własny sposób. W przeglądarce Netscape wyświetla się pusta strona. Jednak po przejrzeniu kodu źródłowego (View/Source) widoczny jest oryginalny plik XML:

Image

Dlaczego Netscape nic nie wyświetla? Ponieważ potrzebuje arkusza stylów, który wskaże mu, jak przekształcić plik XML w plik HTML, który będzie mógł następnie wyświetlić. Okazuje się, że plik IE 6 posiada domyślny arkusz stylów, podczas gdy plik XML go nie zawiera, co miało miejsce w tym przypadku.

Istnieje język o nazwie XSL (eXtensible StyleSheet Language), który pozwala opisać transformacje niezbędne do przekształcenia pliku XML w dowolny plik tekstowy. XSL pozwala na stosowanie wielu instrukcji i bardzo przypomina języki programowania. Nie będziemy go tutaj szczegółowo omawiać, ponieważ zajęłoby to kilkadziesiąt stron. Przedstawimy jedynie dwa przykłady arkuszy stylów XSL. Pierwszy z nich służy do przekształcenia pliku XML simulations.xml w kod HTML. Modyfikujemy ten ostatni tak, aby wskazywał arkusz stylów, z którego przeglądarki będą mogły skorzystać w celu przekształcenia go w dokument HTML, który będą mogły wyświetlić:

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

Zlecenie XML

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

oznacza plik simulations.xsl jako arkusz stylów (xml-stylesheet) typu text/xsl c.a.d – plik tekstowy zawierający kod XSL. Ten arkusz stylów zostanie wykorzystany przez przeglądarki do przekształcenia tekstu XML w dokument HTML. Oto wynik uzyskany w przeglądarce Netscape 7 po załadowaniu pliku XML simulations.xml:

Image

Gdy przyjrzymy się kodowi źródłowemu dokumentu (View/Source), widzimy pierwotny dokument XML, a nie wyświetlany dokument HTML:

Image

Netscape wykorzystał arkusz stylów simulations.xsl do przekształcenia powyższego dokumentu XML w dokument HTML nadający się do wyświetlenia. Nadszedł czas, aby przyjrzeć się zawartości tego arkusza stylów:

<?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>
  • Arkusz stylów XSL jest plikiem typu XML i w związku z tym musi spełniać jego zasady. Musi być między innymi „poprawnie sformułowany”, co oznacza, że każdy otwarty tag musi zostać zamknięty.
  • Plik rozpoczyna się od dwóch poleceń XML, które można zachować w każdym arkuszu stylów XSL w systemie Windows:
<?xml version="1.0" encoding="windows-1252"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

Atrybut encoding="windows-1252" umożliwia stosowanie znaków akcentowanych w arkuszu stylów.

  • Tag <xsl:output method="html" indent="yes"/> informuje interpreter XSL, że chcemy wygenerować „wcięty” kod HTML.
  • Tag <xsl:template match="element"> służy do zdefiniowania elementu dokumentu XML, do którego będą miały zastosowanie instrukcje znajdujące się pomiędzy tagami <xsl:template ...> i </xsl:template>.
    <xsl:template match="/">
................
        </xsl:template>

W powyższym przykładzie element „/” oznacza katalog główny dokumentu. Oznacza to, że gdy tylko zostanie napotkany początek dokumentu XML, polecenia XSL znajdujące się pomiędzy tymi dwoma znacznikami zostaną wykonane.

  • Wszystko, co nie jest znacznikiem XSL, jest umieszczane w strumieniu wyjściowym bez zmian. Znaczniki XSL są natomiast wykonywane. Niektóre z nich generują wynik w strumieniu wyjściowym. Przyjrzyjmy się następującemu przykładowi:
    <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>

Przypomnijmy, że analizowany dokument XML to:

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

Od początku analizowanego dokumentu XML (match="/") interpreter XSL wygeneruje tekst

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

Warto zauważyć, że w tekście wyjściowym występował tag <hr/>, a nie <hr>. W tekście wyjściowym nie można było wpisać <hr>, który – choć jest poprawnym tagiem HTML – jest nieprawidłowym tagiem XML. Mamy tu jednak do czynienia z tekstem XML, który musi być „poprawnie sformułowany”, co oznacza, że każdy tag musi być zamknięty. Piszemy więc <hr/>, a ponieważ napisaliśmy <xsl:output text="html ...>>, interpreter przekształci tekst <hr/> na <hr>. Po tym tekście pojawi się tekst wygenerowany przez polecenie XSL:

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

Później zobaczymy, czym jest ten tekst. Na koniec interpreter doda tekst:

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

Polecenie <xsl:apply-templates select="/simulations/simulation"/> nakazuje wykonanie „szablonu” (template) elementu /simulations/simulation. Zostanie ona wykonana za każdym razem, gdy interpreter XSL napotka w analizowanym tekście XML tag <simulation>..</simulations> lub <simulation/> wewnątrz tagu <simulations>..</simulations>. Po napotkaniu takiego znacznika interpreter wykona instrukcje zawarte w następującym szablonie:

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

Weźmy następujące wiersze XML:

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

Wiersz <simulation ..> odpowiada szablonowi instrukcji XSL <xsl:apply-templates select="/simulations/simulation>". Interpreter XSL będzie zatem próbował zastosować do niego instrukcje odpowiadające temu szablonowi. Znajdzie szablon <xsl:template match="simulation"> i wykona go. Przypomnijmy, że elementy, które nie są poleceniami XSL, są przejmowane bez zmian przez interpreter XSL, natomiast polecenia XSL są zastępowane wynikiem ich wykonania. Instrukcja XSL <xsl:value-of select="@champ"/> zostaje zatem zastąpiona wartością atrybutu „champ” analizowanego węzła (w tym przypadku węzła <simulation>). Analiza poprzedniego wiersza XML da następujący wynik:

XSL
wynik
<tr><td>
<tr><td>
<xsl:value-of select="@marie"/>
tak
</td><td>
</td><td>
<xsl:value-of select="@enfants"/>
2
</td><td>
</td><td>
<xsl:value-of select="@salaire"/>
200000
</td><td>
</td><td>
<xsl:value-of select="@podatek"/>
22504
</td></tr>
</td></tr>

W sumie wiersz XML

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

zostanie przekształcona w wiersz HTML:

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

Wszystkie te wyjaśnienia są nieco uproszczone, ale czytelnik powinien teraz zrozumieć, że następujący tekst 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>

wraz z następującym arkuszem stylów XSL simulations.xsl:

<?xml version="1.0" encoding="windows-1252"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
        <html>
          <head>
            <title>Simulations de calculs d'impôts</title>
        </head>
          <body>
            <center>
              <h3>Simulations de calculs d'impôts</h3>
            <hr/>
            <table border="1">
                    <th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>
                          <xsl:apply-templates select="/simulations/simulation"/>
                        </table>
         </center>
       </body>
     </html>
        </xsl:template>


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

generuje następujący tekst: HTML:

<html>
<head>
<title>Simulations de calculs d'impôts</title>
</head>
<body>
<center>
<h3>Simulations de calculs d'impots</h3>
<hr>
<table border="1">
<th>marié</th><th>enfants</th><th>salaire</th><th>impôt</th>
<tr>
<td>oui</td><td>2</td><td>200000</td><td>22504</td>
</tr>
<tr>
<td>non</td><td>2</td><td>200000</td><td>33388</td>
</tr>
</table>
</center>
</body>
</html>

Plik XML simulations.xml poniżej

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

wyświetlany przez nowszą przeglądarkę (w tym przypadku Netscape 7) wygląda następująco:

Image

5.2. Aplikacja podatkowa: wersja 5

5.2.1. Pliki XML i arkusze stylów XSL aplikacji podatkowej

Wróćmy do naszego punktu wyjścia, którym była aplikacja internetowa do rozliczeń podatkowych, i przypomnijmy sobie, że chcemy ją zmodyfikować tak, aby odpowiedź wysyłana do klientów miała format XML zamiast HTML. Do tej odpowiedzi o formacie HTML dołączony zostanie arkusz stylów o formacie XSL, aby przeglądarki mogły ją wyświetlić. W poprzednim akapicie przedstawiliśmy:

  • plik simulations.xml, który jest prototypem odpowiedzi XML zawierającej symulacje obliczeń podatkowych
  • plik simulations.xsl, który będzie arkuszem stylów XSL dołączonym do tej odpowiedzi XML

Należy również uwzględnić przypadek odpowiedzi zawierającej błędy. Prototypem odpowiedzi XML w tym przypadku będzie następujący plik erreurs.xml:

<?xml version="1.0" encoding="windows-1252"?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl"?>
<erreurs>
    <erreur>erreur 1</erreur>
  <erreur>erreur 2</erreur>
</erreurs>

Arkusz stylów erreurs.xsl umożliwiający wyświetlenie tego dokumentu XML w przeglądarce będzie wyglądał następująco:

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

Ten arkusz stylów wprowadza nieznane dotąd polecenie XSL: <xsl:value-of select="."/>. Polecenie to generuje na wyjściu wartość analizowanego węzła, w tym przypadku węzła <erreur>texte</erreur>. Wartością tego węzła jest tekst zawarty między dwoma tagami otwierającym i zamykającym, w tym przypadku texte.

Kod erreurs.xml jest przekształcany przez arkusz stylów erreurs.xsl w następujący dokument HTML:

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

Plik erreurs.xml wraz z arkuszem stylów jest wyświetlany przez przeglądarkę w następujący sposób:

Image

5.2.2. Aplikacja xmlsimulations

Tworzymy plik xmlsimulations.html, który umieszczamy w katalogu aplikacji impots. Wyświetlana strona wygląda następująco:

Image

Ten dokument HTML jest dokumentem statycznym. Jego kod wygląda następująco:

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

      function calculer(){
           // sprawdzenie parametrów przed wysłaniem ich na serwer
        with(document.frmImpots){
          //liczba dzieci
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // szablon nie został zweryfikowany
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
           //wynagrodzenie
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // szablon nie został zweryfikowany
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//jeśli
          // w porządku – wysyłamy
          submit();
        }//z
      }//obliczyć        
        </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>

Należy zauważyć, że dane z formularza są przesyłane do URL /poly/impots/7/xmlsimulations.php. Kod aplikacji xmlsimulations.php jest bardzo podobny do kodu aplikacji impots.php. Zachęcamy czytelnika do zapoznania się z tym ostatnim. Przypomnijmy kod wejściowy:

<?php
     // przetwarzaj formularz podatkowy

     // biblioteki
  include "ImpotsDSN.php";

   // rozpoczęcie sesji
  session_start();

   // konfiguracja aplikacji
  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);

     // pobieranie parametrów sesji
  $session=$_SESSION["session"];
   // czy sesja jest ważna?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // rozpoczynamy nową sesję
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // czy wystąpiły błędy?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // wyświetlanie strony błędów
        include $erreursImpots;
       // koniec
      $session=array();
      terminerSession($session);
    }//if
  }//if

  // pobieramy parametry bieżącej wymiany
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // czy mamy wszystkie parametry?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // wyświetlenie pustego formularza
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // koniec
    terminerSession($session);
  }//if

   // sprawdzanie parametrów
  $requête=vérifier($requête);

   // czy są jakieś błędy?
  if(count($requête[erreurs])!=0){
       // wyświetlenie formularza
    include "$formulaireImpots";
     // koniec
    terminerSession($session);
  }//if

   // obliczenie podatku do zapłaty
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

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

   // wyświetlenie formularza
  include "$formulaireImpots";

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

Strony HTML były wyświetlane przez instrukcje include „...”. Chcemy tutaj wygenerować XML, a nie HTML. Wystarczy napisać dwie nowe aplikacje: impots_erreurs.php i impots_simulations.php, aby generowały XML zamiast HTML. Pozostała część aplikacji pozostaje bez zmian. Kod wygląda wówczas następująco:

<?php
     // przetwarza formularz podatkowy

     // biblioteki
  include "ImpotsDSN.php";

   // rozpoczęcie sesji
  session_start();

   // konfiguracja aplikacji
  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);

     // pobieranie parametrów sesji
  $session=$_SESSION["session"];
   // czy sesja jest ważna?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // rozpoczynamy nową sesję
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // czy wystąpiły błędy?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // wyświetlanie strony błędów w formacie XML
          header("Content-type: text/xml");      
        include $erreursImpots;
       // koniec
      $session=array();
      terminerSession($session);
    }//if
  }//if

   // pobieramy parametry bieżącej wymiany
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // czy mamy wszystkie parametry?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // wyświetlanie pustego formularza
    include $formulaireImpots;
     // koniec
    terminerSession($session);
  }//if

   // sprawdzanie parametrów
  $requête=vérifier($requête);

   // czy są błędy?
  if(count($requête[erreurs])!=0){
       // wyświetlanie błędów w formacie XML
      header("Content-type: text/xml");    
    include "$erreursImpots";
     // koniec
    terminerSession($session);
  }//if

   // obliczenie podatku do zapłaty
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

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

   // wyświetlanie symulacji w formacie XML
  header("Content-type: text/xml");
  include "$simulationsImpots";

   // koniec
  terminerSession($session);

Wcześniej przedstawiliśmy i przeanalizowaliśmy dwa rodzaje odpowiedzi XML, które należy dostarczyć, a także arkusze stylów, które powinny im towarzyszyć. Kod aplikacji impots_simulations.php jest następujący:

<?php
     // generuje kod XML strony symulacji aplikacji podatkowej

   // niektóre stałe
  $xslSimulations="simulations.xsl";

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

Kod ten umożliwia wygenerowanie na podstawie słownika $requête kodu XML podobnego do poniższego:


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

Arkusz stylów simulations.xsl przekształci ten kod XML w kod HTML.

Kod aplikacji impots_erreurs.php wygląda następująco:

<?php
     // generuje kod XML strony błędów aplikacji impots

   // niektóre stałe
  $xslErreurs="erreurs.xsl";

   // nagłówki XML
  echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
  echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslErreurs\" ?>\n";
   // błędy
  echo "<erreurs>\n";
  for ($i=0;$i<count($requête[erreurs]);$i++){
      // błąd $i
    echo "<erreur>".$requête[erreurs][$i]."</erreur>";
  }//for
  echo "</erreurs>\n";
?>

Kod ten umożliwia wygenerowanie na podstawie słownika $requête kodu XML podobnego do poniższego:

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

Arkusz stylów erreurs.xsl przekształci ten kod XML w kod HTML.

Spójrzmy na pierwszy przykład:

Image

SGBD MySQL nie został uruchomiony. Otrzymujemy wówczas następującą odpowiedź:

Image

Jeśli przyjrzymy się kodowi źródłowemu otrzymanemu przez przeglądarkę, widzimy następujący fragment:

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

Teraz uruchamiamy SGBD MySQL i przeprowadzamy kolejne symulacje. Otrzymujemy następującą odpowiedź:

Image

Kod otrzymany przez przeglądarkę jest następujący:

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

Warto zauważyć, że nasza nowa aplikacja jest łatwiejsza w utrzymaniu niż poprzednia. Część pracy została przeniesiona do arkuszy stylów XSL. Zaletą tego nowego podziału zadań jest to, że po ustaleniu formatu odpowiedzi XML tworzenie arkuszy stylów przebiega niezależnie od rozwoju aplikacji.

5.3. Analiza dokumentu XML w formacie PHP

Kolejna wersja naszej aplikacji podatkowej będzie klientem zaprogramowanym na podstawie poprzedniej aplikacji xmlsimulations. Nasz klient będzie zatem otrzymywał kod XML, który będzie musiał przeanalizować w celu wyodrębnienia interesujących go informacji. Zrobimy teraz przerwę w omawianiu poszczególnych wersji i nauczymy się, jak przeanalizować dokument XML w formacie PHP. Zrobimy to na podstawie następującego przykładu:


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

Aplikacja xmlParser.php przyjmuje jeden parametr: identyfikator URI (Uniform Resource Identifier) dokumentu XML, który ma zostać przeanalizowany. W naszym przykładzie ten identyfikator URI będzie po prostu nazwą pliku XML umieszczonego w katalogu aplikacji xmlParser.php. Rozważmy dwa przykłady działania. W pierwszym przykładzie analizowanym plikiem XML jest następujący plik erreurs.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="erreurs.xsl"?>
<erreurs>
    <erreur>erreur 1</erreur>
  <erreur>erreur 2</erreur>
</erreurs>

Analiza daje następujące wyniki:


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

Nie opisaliśmy jeszcze, do czego służy aplikacja xmlParser.php, ale widać tutaj, że wyświetla ona strukturę przeanalizowanego dokumentu XML. Drugi przykład analizuje następujący plik XML simulations.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="simulations.xsl"?>
<simulations>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
  <simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>

Analiza daje następujące wyniki:


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

Aplikacja xmlParser.php zawiera wszystko, czego potrzebujemy w naszej aplikacji podatkowej, ponieważ była w stanie wykryć zarówno błędy, jak i symulacje, które mógłby wysłać serwer WWW. Przyjrzyjmy się jej kodowi:

<?php
   // składnia $0 fichierXML
   // wyświetla strukturę i zawartość pliku fichierXML

   // weryfikacja wywołania
  if(count($argv)!=2){
       // komunikat o błędzie
    fwrite(STDERR,"syntaxe : $argv[0] fichierXML\n");
     // koniec
    exit(1);
  }//if

   // inicjalizacje
  $file=$argv[1];       // plik xml
  $depth=0;               // poziom wcięcia = głębokość w drzewie

   // program

   // tworzymy obiekt do analizy tekstu XML
  $xml_parser=xml_parser_create();

   // określa się, jakie funkcje mają być wykonywane na początku i na końcu znacznika
  xml_set_element_handler($xml_parser,"startElement","endElement");

   // określa się, jaką funkcję wykonać po napotkaniu danych
  xml_set_character_data_handler($xml_parser,"afficheData");

  // otwieranie pliku XML w trybie odczytu
  if (! ($fp=@fopen($file,"r"))){
    fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
    exit(2);
  }//if
   // przetwarzanie pliku XML w blokach po 4096 bajtów
  while($data=fread($fp,4096)){
    // analiza odczytanych danych
    if (! xml_parse($xml_parser,$data,feof($fp))){
      // wystąpił błąd
      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));
       // koniec
      exit(3);
    }//if
  }//while
  // plik został przeanalizowany
   // zwolniono zasoby zajęte przez analizator XML
  xml_parser_free($xml_parser);
   // koniec
  exit(0);

    // -----------------------------------------------------------  
   // funkcja wywoływana po napotkaniu znacznika początkowego
  function startElement($parser,$name,$attributs){
    global $depth;
     // sekwencja spacji (wcięcie)
    for($i=0;$i<$depth;$i++){
      print " ";
    }//for
     // atrybuty
    $précisions="";
    while(list($attrib,$valeur)=each($attributs)){
      $précisions.="($attrib,$valeur) ";
    }
     // wyświetlana jest nazwa tagu i ewentualne atrybuty
    if($précisions)
      print "$name,$précisions\n";
    else print "$name\n";
     // dodatkowy poziom drzewa
    $depth++;
  }//startElement

    // -----------------------------------------------------------  
   // funkcja wywoływana po napotkaniu znacznika końcowego
  function endElement($parser,$name){
     // koniec znacznika
     // poziom wcięcia
    global $depth;
    $depth--;
    // sekwencja spacji (wcięcie)
    for($i=0;$i<$depth;$i++){
      echo " ";
    }//for
     // nazwa tagu
    echo "/$name\n";
  }//endElement

    // -----------------------------------------------------------  
   // funkcja wyświetlania danych
  function afficheData($parser,$data){
     // poziom wcięcia
    global $depth;
     // dane są wyświetlane
    $data=trim($data);
    if($data!=""){
       // seria spacji (wcięcie)
      for($i=0;$i<$depth;$i++){
        echo " ";
      }//for
      echo "[$data]\n";
    }//if
  }//afficheData
?>

Przyjrzyjmy się kodowi związanemu z XML. Aby przeanalizować dokument XML, nasza aplikacja potrzebuje analizatora kodu XML, zwanego potocznie „parserem”.

<?php
...
   // tworzy się obiekt do analizy tekstu XML
  $xml_parser=xml_parser_create();

Gdy parser będzie analizował dokument XML, będzie generował zdarzenia takie jak: napotkano początek dokumentu, początek tagu, atrybut tagu, zawartość tagu, koniec tagu, koniec dokumentu... Przekazuje te zdarzenia do metod, które należy mu wskazać:

<?php
...
   // określa się, jakie funkcje mają być wykonywane na początku i na końcu znacznika
  xml_set_element_handler($xml_parser,"startElement","endElement");
   // określa się, jaką funkcję wykonać po napotkaniu danych
  xml_set_character_data_handler($xml_parser,"afficheData");
zdarzenie wygenerowane przez
parser
metody przetwarzania
début d'un élément :<balise>
funkcja startElement($parser, $name, $attributs)
$parser: parser dokumentu
$name: nazwa analizowanego elementu. Jeśli napotkanym elementem jest <simulations>, otrzymamy name="simulations".
$attributs: lista atrybutów tagu w postaci (ATTRIBUT,wartość), gdzie ATTRIBUT to nazwa tagu zapisana wielkimi literami.
valeur d'un élément :
<balise>valeur</balise>
function wyświetlDane($parser, $dane)
$parser: parser dokumentu
$data: dane tagu
fin d'un élément : </balise> ou <balise .../>
function endElement($parser, $name)
parametry są takie same jak w metodzie startElement.

Funkcja startElement pozwala pobrać atrybuty elementu za pomocą parametru $attributs. Jest to słownik atrybutów tagu. Jeśli więc mamy następujący tag:

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

słownik $attributs będzie wyglądał następująco: array(marie=>tak,dzieci=>2,wynagrodzenie=>200000,podatek=>22504)

Po zdefiniowaniu parsera i powyższych metod analiza dokumentu odbywa się za pomocą funkcji xml_parse:

<?php
...
   // przetwarzanie pliku XML w blokach po 4096 bajtów
  while($data=fread($fp,4096)){
    // analiza odczytanych danych
    if (! xml_parse($xml_parser,$data,feof($fp))){
...
    }//if
  }//while
analyse d'un
document $doc
function xml_parse($parser, $doc, $fin)
Parser $parser analizuje dokument $doc. $doc może stanowić fragment większego dokumentu. Parametr $fin wskazuje, czy jest to ostatni fragment (true), czy nie (false). Podczas analizy dokumentu $doc funkcje zdefiniowane przez xml_set_element_handler są wywoływane na początku i na końcu każdego tagu. Czy funkcja zdefiniowana przez xml_set_character_data_handler jest wywoływana za każdym razem, gdy uzyskano zawartość tagu?

Podczas analizy dokumentu XML mogą wystąpić błędy, zwłaszcza jeśli dokument XML jest „nieprawidłowo sformułowany”, na przykład w przypadku pominięcia tagów zamykających. W takim przypadku funkcja xml_parse zwraca wartość „fałsz”:

<?php
...
    if (! xml_parse($xml_parser,$data,feof($fp))){
      // wystąpił błąd
      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));
      // koniec
      exit(3);
    }//if
gestion d'erreurs
function xml_get_error_code($parser)
zwraca kod ostatniego wystąpionego błędu
--
function xml_error_string($code)
zwraca komunikat o błędzie powiązany z kodem przekazanym jako parametr
--
function xml_get_current_line($parser)
zwraca numer linii dokumentu XML, który jest aktualnie analizowany przez parser

Po zakończeniu analizy dokumentu zwalnia się zasoby przydzielone parserowi:

<?php
...
   // zwolniono zasoby zajęte przez analizator XML
  xml_parser_free($xml_parser);
libération des
ressources attribuées
au parseur $parser
function xml_free($parser)

Po tym wyjaśnieniu poprzedni program wraz z przykładami działania jest zrozumiały sam w sobie.

5.4. Aplikacja podatkowa: wersja 6

Mamy teraz wszystkie elementy potrzebne do napisania klienta dla naszego serwisu podatkowego, który generuje plik XML. Wykorzystujemy wersję 4 naszej aplikacji do stworzenia klienta, a wersję 5 zachowujemy dla serwera. W tej aplikacji klient-serwer:

  • usługa symulacji obliczeń podatkowych jest realizowana przez aplikację xmlsimulations.php. Odpowiedź serwera ma zatem format XML, tak jak widzieliśmy w wersji 5.
  • klientem nie jest już przeglądarka, lecz samodzielny klient PHP. Jego interfejs konsoli jest taki sam jak w wersji 4.

Zachęcamy czytelnika do ponownego zapoznania się z kodem aplikacji cltImpots.php, która była zaprogramowanym klientem w wersji 4. Otrzymywała ona z serwera dokument o nazwie $document. Dokument ten był wówczas plikiem o nazwie HTML. Obecnie jest to dokument o nazwie XML. Dokumenty HTML i $document były analizowane przez następującą funkcję:

<?php
...
  function getInfos($document){
       // $document: dokument HTML
     // szukamy albo listy błędów
     // albo tabeli symulacji

     // przygotowanie wyniku
    $impots[erreurs]=array();
    $impots[simulations]=array();

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

Funkcja pobierała dokument HTML $document, analizowała go i zwracała słownik $impots zawierający dwa atrybuty:

  1. błędy: tablica błędów
  2. symulacje: tablica symulacji, przy czym każda symulacja jest z kolei tablicą zawierającą cztery elementy (małżonek, dzieci, wynagrodzenie, podatek).

Aplikacja cltImpots.php zmienia się teraz na cltXmlSimulations.php. Zmianie musi ulec jedynie część zajmująca się przetwarzaniem dokumentu otrzymanego z serwera, aby uwzględnić fakt, że jest to teraz dokument XML. Funkcja getInfos przyjmuje wówczas następujący kształt:

<?php
...
// --------------------------------------------------------------
  function getInfos($document){
       // $document: dokument XML
     // szukamy albo listy błędów
     // albo tabeli symulacji

    global $impots,$balises;

     // przygotowanie wyniku
    $impots[erreurs]=array();
    $impots[simulations]=array();
     // bieżące znaczniki
    $balises=array();

     // tworzy się obiekt do analizy tekstu XML
    $xml_parser=xml_parser_create();
     // określamy, jakie funkcje mają być wykonywane na początku i na końcu znacznika
    xml_set_element_handler($xml_parser,"startElement","endElement");
     // określa się, jaką funkcję wykonać po napotkaniu danych
    xml_set_character_data_handler($xml_parser,"getData");
    // przeprowadzamy analizę $document
    xml_parse($xml_parser,$document);
     // zwalnia zasoby zajęte przez analizator XML
    xml_parser_free($xml_parser);
     // koniec
    return $impots;
  }//getInfos

    // -----------------------------------------------------------  
  // funkcja wywoływana po napotkaniu znacznika początkowego
  function startElement($parser,$name,$attributs){
    global $impots,$balise,$balises,$contenu;

     // zapisuje się nazwę tagu i jego zawartość
    $balise=strtolower($name);
    $contenu="";
     // dodaje się ją do stosu znaczników
    array_push($balises,$balise);    
    // czy jest to symulacja?
    if($balise=="simulation"){   
         // zapisuje się atrybuty symulacji
      $impots[simulations][]=array($attributs[MARIE],$attributs[ENFANTS],$attributs[SALAIRE],$attributs[IMPOT]);
    }//if
  }//startElement

    // -----------------------------------------------------------  
   // funkcja wywoływana po napotkaniu znacznika końcowego
  function endElement($parser,$name){
       // pobieramy bieżący znacznik
    global $impots,$balises,$contenu;
    $balise=array_pop($balises);
    // czy jest to znacznik błędu?
    if($balise=="erreur"){
         // kolejny błąd
      $impots[erreurs][]=trim($contenu);
    }//if
  }//endElement

    // -----------------------------------------------------------  
   // funkcja przetwarzania treści znacznika
  function getData($parser,$data){
      // dane globalne
    global $balise,$contenu;
     // czy jest to tag błędu?
    if($balise=="erreur"){
         // dodajemy do zawartości bieżącego tagu
      $contenu.=$data;
    }//if
  }//getData

Uwagi:

  • funkcja getInfos($document) rozpoczyna się od utworzenia parsera, następnie go konfiguruje, a na koniec uruchamia analizę dokumentu $document:
<?php
...
     // tworzy się obiekt analizy tekstu XML
    $xml_parser=xml_parser_create();
     // określa się, jakie funkcje mają być wykonywane na początku i na końcu znacznika
    xml_set_element_handler($xml_parser,"startElement","endElement");
     // określa się, jaką funkcję wykonać po napotkaniu danych
    xml_set_character_data_handler($xml_parser,"getData");
    // przeprowadza się analizę $document
    xml_parse($xml_parser,$document);
  • Po zakończeniu analizy zwalnia się zasoby przydzielone parserowi i zwraca słownik $impots.
<?php
...
     // zwalnia zasoby zajęte przez analizator XML
    xml_parser_free($xml_parser);
     // koniec
    return $impots;
  • Funkcja startElement($parser,$name,$attributs) jest wywoływana przy każdym początku znacznika. Ona
    • Znacznik $name znajduje się w tablicy znaczników $balises. Tablica ta jest zarządzana jako stos: po napotkaniu symbolu końca znacznika ostatni znacznik umieszczony na stosie w $balises zostanie z niego usunięty. Bieżący tag jest również zapisywany w $balise. W słowniku $attributs znajdują się atrybuty napotkanego tagu, przy czym atrybuty te są zapisane wielkimi literami.
    • zapisuje atrybuty w globalnym słowniku $impots[simulations], jeśli jest to tag symulacyjny.
  • funkcja getData($parser,$data) po pobraniu zawartości $data z tagu. W tym przypadku zastosowano środek ostrożności. W niektórych środowiskach przetwarzania dokumentów, w szczególności w Javie, wskazano, że funkcja ta może być wywoływana wielokrotnie. że niekoniecznie otrzymuje się zawartość znacznika za jednym razem. W tym przypadku dokumentacja PHP nie wskazuje na to ograniczenie. Na wszelki wypadek wartość $data, którą uzyskaliśmy, sumujemy z zmienną globalną $contenu. Dopiero po napotkaniu symbolu końca znacznika uznamy, że uzyskaliśmy całą zawartość znacznika. Jedynym znacznikiem, którego dotyczy ta operacja, jest znacznik <erreur>.
  • Funkcja endElement($parser,$name) jest wywoływana przy każdym zakończeniu tagu. Jest ona tutaj wykorzystywana do zmiany nazwy bieżącego tagu poprzez usunięcie ostatniego tagu ze stosu tagów oraz do dodania zawartości tagu <erreur>, który kończy się na tablicy $impots[erreurs].

Oto kilka przykładów działania, najpierw z tagiem SGBD, który nie został uruchomiony:

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

Następnie z uruchomionym SGBD:


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

5.5. Wniosek

Dzięki rozwiązaniu XML aplikacja podatkowa stała się łatwiejsza w zarządzaniu zarówno dla jej twórcy, jak i dla projektantów aplikacji klienckich.

  • Projektowanie aplikacji serwerowej można teraz powierzyć dwóm grupom osób: programiście serwletu PHP oraz grafikowi, który zajmie się wyglądem odpowiedzi serwera w przeglądarkach. Wystarczy, że ten ostatni zna strukturę odpowiedzi serwera XML, aby stworzyć arkusze stylów, które będą jej towarzyszyć. Należy pamiętać, że arkusze te są przechowywane w oddzielnych plikach XSL, niezależnych od samej aplikacji PHP. Projektant graficzny może zatem pracować niezależnie od programisty.
  • Projektanci aplikacji klienckich również muszą jedynie znać strukturę odpowiedzi serwera XML. Zmiany, jakie grafik może wprowadzić w arkuszach stylów, nie mają żadnego wpływu na tę odpowiedź XML, która pozostaje zawsze taka sama. To ogromna zaleta.
  • W jaki sposób programista może rozwijać swoją aplikację PHP bez powodowania błędów? Po pierwsze, dopóki odpowiedź XML pozostaje niezmieniona, programista może organizować swoją aplikację tak, jak chce. Może również modyfikować odpowiedź XML, o ile zachowa elementy <erreur> i <simulation>, których oczekują klienci. W ten sposób może dodawać nowe tagi do tej odpowiedzi. Grafik uwzględni je w swoich arkuszach stylów, a przeglądarki będą mogły otrzymywać nowe wersje odpowiedzi. Natomiast zaprogramowani klienci będą nadal działać zgodnie ze starym modelem, a nowe tagi zostaną po prostu zignorowane. Aby było to możliwe, w analizie odpowiedzi serwera o numerze XML poszukiwane tagi muszą być prawidłowo zidentyfikowane. Tak właśnie postąpiono w naszym kliencie XML aplikacji podatkowej, gdzie w procedurach wyraźnie określono, że przetwarzane są tagi <erreur> i <simulation>. W rezultacie pozostałe tagi są ignorowane.