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:

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:

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

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

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>.
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:
Później zobaczymy, czym jest ten tekst. Na koniec interpreter doda tekst:
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:
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
zostanie przekształcona w wiersz HTML:
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:

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:

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

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:

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

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

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”.
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 |
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. | |
function wyświetlDane($parser, $dane) $parser: parser dokumentu $data: dane tagu | |
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:
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
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
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:
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:
- błędy: tablica błędów
- 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.