5. XML та PHP
У цьому розділі ми ознайомимося з використанням документів XML (мова розмітки eXtensible) разом із PHP. Ми зробимо це в контексті програми для розрахунку податків, розглянутої в попередньому розділі.
5.1. Файли XML та таблиці стилів XSL
Розглянемо наступний файл XML, який може представляти результат моделювання:
<?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>
Якщо переглянути це за допомогою IE 6, отримаємо такий результат:

IE6 розпізнає, що має справу з файлом XML (завдяки розширенню .xml у файлі), і оформлює його на власний розсуд. У Netscape відображається порожня сторінка. Однак якщо переглянути вихідний код (View/Source), то можна побачити саме вихідний файл XML:

Чому Netscape нічого не відображає? Тому що йому потрібен стильовий шаблон, який би вказав, як перетворити файл XML на файл HTML, який він зможе потім відобразити. Виявляється, що файл IE 6 має стильовий шаблон за замовчуванням, тоді як файл XML його не містить, що й було в даному випадку.
Існує мова під назвою XSL (eXtensible StyleSheet Language), яка дозволяє описувати перетворення, необхідні для перетворення файлу XML у будь-який текстовий файл. XSL підтримує використання численних інструкцій і дуже нагадує мови програмування. Ми не будемо тут детально його описувати, оскільки на це знадобилося б кілька десятків сторінок. Ми просто наведемо два приклади таблиць стилів XSL. Перша з них перетворює файл XML simulations.xml у код HTML. Ми змінюємо останній так, щоб він вказував на таблицю стилів, яку браузери зможуть використовувати для перетворення його на документ HTML, який вони зможуть відобразити:
<?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>
Команда XML
позначає файл simulations.xsl як таблицю стилів (xml-stylesheet) типу text/xsl c.a.d — текстовий файл, що містить код XSL. Цей стильовий аркуш буде використовуватися браузерами для перетворення тексту XML у документ HTML. Ось результат, отриманий у Netscape 7 під час завантаження файлу XML simulations.xml:

Коли ми переглядаємо вихідний код документа (View/Source), ми бачимо початковий документ XML, а не документ HTML, що відображається:

Netscape використав таблицю стилів simulations.xsl, щоб перетворити наведений вище документ XML на документ HTML, придатний для відображення. Тепер настав час розглянути вміст цієї таблиці стилів:
<?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>
- Таблиця стилів XSL є файлом XML і, отже, підпорядковується його правилам. Вона, серед іншого, має бути «правильно сформованою», тобто кожен відкритий тег має бути закритий.
- Файл починається з двох команд XML, які можна залишити в будь-якому стильовому аркуші XSL у Windows:
<?xml version="1.0" encoding="windows-1252"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Атрибут encoding="windows-1252" дозволяє використовувати символи з діакритичними знаками у таблиці стилів.
- Тег <xsl:output method="html" indent="yes"/> вказує інтерпретатору XSL, що потрібно створити «відступлений» HTML.
- Тег <xsl:template match="елемент"> слугує для визначення елемента документа XML, до якого будуть застосовуватися інструкції, що містяться між тегами <xsl:template ...> та </xsl:template>.
У наведеному вище прикладі елемент «/» позначає корінь документа. Це означає, що як тільки буде виявлено початок документа XML, команди XSL, розташовані між цими двома тегами, будуть виконані.
- Усе, що не є тегом XSL, потрапляє у вихідний потік без змін. Теги XSL виконуються. Деякі з них генерують результат у вихідному потоці. Розглянемо наступний приклад:
<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>
Нагадаємо, що аналізований документ XML має такий вигляд:
<?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>
Від самого початку аналізованого документа XML (match="/") інтерпретатор XSL видасть на вихід текст
<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>
Слід зауважити, що в початковому тексті було <hr/>, а не <hr>. У початковому тексті не можна було написати <hr>, оскільки, хоча це і є дійсним тегом HTML, це є недійсним тегом XML. Однак тут ми маємо справу з текстом XML, який має бути «правильно сформованим», c.a.d, тобто кожен тег має бути закритий. Тому ми пишемо <hr/>, і оскільки ми написали <xsl:output text="html ...">, інтерпретатор перетворить текст <hr/> на <hr>. Після цього тексту йтиме текст, згенерований командою XSL:
Пізніше ми дізнаємося, що це за текст. Нарешті інтерпретатор додасть текст:
Команда <xsl:apply-templates select="/simulations/simulation"/> вимагає виконання «шаблону» (template) елемента /simulations/simulation. Вона виконуватиметься щоразу, коли інтерпретатор XSL зустріне в проаналізованому тексті XML тег <simulation>..</simulations> або <simulation/> всередині тегу <simulations>..</simulations>. При виявленні такого тегу інтерпретатор виконає інструкції наступної моделі:
<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>
Розглянемо такі рядки XML:
Рядок <simulation ..> відповідає шаблону інструкції XSL <xsl:apply-templates select="/simulations/simulation>". Тому інтерпретатор XSL спробує застосувати до нього інструкції, що відповідають цьому шаблону. Він знайде шаблон <xsl:template match="simulation"> і виконає його. Нагадаємо, що те, що не є командою XSL, інтерпретатор XSL бере без змін, а команди XSL замінюються результатом їх виконання. Таким чином, інструкція XSL <xsl:value-of select="@champ"/> замінюється значенням атрибута «champ» аналізованого вузла (у даному випадку вузла <simulation>). Аналіз попереднього рядка XML дасть такий результат:
XSL | вихідні дані |
<tr><td> | <tr><td> |
<xsl:value-of select="@marie"/> | так |
</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="@impot"/> | 22504 |
</td></tr> | </td></tr> |
Загалом, рядок XML
буде перетворено на рядок HTML:
Усі ці пояснення є дещо спрощеними, але тепер читачеві має стати зрозуміло, що такий текст 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>
разом із наступним стильовим шаблоном 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>
генерує такий текст: 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>
Наступний файл XML simulations.xml
<?xml version="1.0" encoding="windows-1252"?>
<?xml-stylesheet type="text/xsl" href="simulations.xsl"?>
<simulations>
<simulation marie="oui" enfants="2" salaire="200000" impot="22504"/>
<simulation marie="non" enfants="2" salaire="200000" impot="33388"/>
</simulations>
при відкритті в сучасному браузері (у даному випадку Netscape 7) відображається наступним чином:

5.2. Додаток «Податки»: версія 5
5.2.1. Файли XML та таблиці стилів XSL додатка «Податки»
Повернемося до нашої вихідної точки — веб-додатку для оподаткування — і нагадаємо, що ми хочемо змінити його так, щоб відповідь, що надсилається клієнтам, мала формат XML, а не HTML. Ця відповідь у форматі HTML супроводжуватиметься таблицею стилів XSL, щоб браузери могли її відобразити. У попередньому абзаці ми представили:
- файл simulations.xml, який є прототипом відповіді XML із симуляціями розрахунків податків
- файл simulations.xsl, який стане таблицею стилів XSL, що супроводжуватиме цю відповідь XML
Також слід передбачити випадок відповіді з помилками. Прототипом відповіді XML у цьому випадку буде наступний файл 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>
Таблиця стилів erreurs.xsl, що дозволяє відображати цей документ XML у браузері, матиме такий вигляд:
<?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>
Цей стильовий аркуш вводить ще не зустрічану команду XSL: <xsl:value-of select="."/>. Ця команда видає значення аналізованого вузла, в даному випадку вузла <erreur>texte</erreur>. Значенням цього вузла є текст, що міститься між двома тегами — відкриваючим і закриваючим, у даному випадку texte.
Код erreurs.xml перетворюється за допомогою таблиці стилів erreurs.xsl у такий документ 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>
Файл erreurs.xml разом із таблицею стилів відображається у браузері наступним чином:

5.2.2. Додаток xmlsimulations
Ми створюємо файл xmlsimulations.html, який розміщуємо в каталозі додатка impots. Сторінка, що відображається, виглядає так:

Цей документ HTML є статичним. Його код такий:
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// очищення форми
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//з
}//видалити
function calculer(){
// перевірка параметрів перед відправкою на сервер
with(document.frmImpots){
//кількість дітей
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// шаблон не перевірено
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//якщо
//зарплата
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// модель не перевірена
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//якщо
// все гаразд — надсилаємо
submit();
}//з
}//обчислити
</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>
Слід зазначити, що дані з форми надсилаються на URL /poly/impots/7/xmlsimulations.php. Код програми xmlsimulations.php дуже схожий на код програми impots.php. Читачеві пропонується ознайомитися з ним. Нагадаємо код введення:
<?php
// обробити податкову декларацію
// бібліотеки
include "ImpotsDSN.php";
// запуск сеансу
session_start();
// налаштування програми
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);
// отримання параметрів сесії
$session=$_SESSION["session"];
// сесія дійсна?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// запуск нової сесії
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// Чи є помилки?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// відображення сторінки помилок
include $erreursImpots;
// кінець
$session=array();
terminerSession($session);
}//if
}//if
// отримуємо параметри поточного обміну
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// чи є всі параметри?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// відображення порожньої форми
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// кінець
terminerSession($session);
}//if
// перевірка параметрів
$requête=vérifier($requête);
// Чи є помилки?
if(count($requête[erreurs])!=0){
// відображення форми
include "$formulaireImpots";
// кінець
terminerSession($session);
}//якщо
// розрахунок податку до сплати
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// ще одне моделювання
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// відображення форми
include "$formulaireImpots";
// кінець
terminerSession($session);
...
Сторінки HTML відображалися за допомогою рядків include «...». Тут потрібно згенерувати XML, а не HTML. Достатньо написати два нових додатки impots_erreurs.php та impots_simulations.php, щоб вони генерували XML замість HTML. Решта програми залишається без змін. Тоді код виглядатиме так:
<?php
// обробка податкової форми
// бібліотеки
include "ImpotsDSN.php";
// запуск сеансу
session_start();
// налаштування програми
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);
// отримання параметрів сесії
$session=$_SESSION["session"];
// сесія дійсна?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// запуск нової сесії
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// Чи є помилки?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// відображення сторінки помилок у форматі XML
header("Content-type: text/xml");
include $erreursImpots;
// кінець
$session=array();
terminerSession($session);
}//if
}//if
// отримуємо параметри поточного обміну
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// чи є всі параметри?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// відображення порожньої форми
include $formulaireImpots;
// кінець
terminerSession($session);
}//if
// перевірка параметрів
$requête=vérifier($requête);
// Чи є помилки?
if(count($requête[erreurs])!=0){
// відображення помилок у форматі XML
header("Content-type: text/xml");
include "$erreursImpots";
// кінець
terminerSession($session);
}//if
// розрахунок податку до сплати
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// ще одне моделювання
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// відображення моделювань у форматі XML
header("Content-type: text/xml");
include "$simulationsImpots";
// кінець
terminerSession($session);
Раніше ми представили та розглянули два типи відповідей XML, які слід надавати, а також стильові таблиці, що мають їх супроводжувати. Код додатка impots_simulations.php є таким:
<?php
// генерує код XML сторінки моделювань додатка «Податки»
// деякі константи
$xslSimulations="simulations.xsl";
// заголовки XML
echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslSimulations\" ?>\n";
// моделювання
echo "<simulations>\n";
for ($i=0;$i<count($requête[simulations]);$i++){
// моделювання $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";
?>
Цей код дозволяє на основі словника $requête згенерувати код XML, аналогічний такому:
<?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>
Таблиця стилів simulations.xsl перетворить цей код XML на код HTML.
Код додатка impots_erreurs.php виглядає так:
<?php
// генерує код XML сторінки помилок додатка impots
// деякі константи
$xslErreurs="erreurs.xsl";
// заголовки XML
echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslErreurs\" ?>\n";
// помилки
echo "<erreurs>\n";
for ($i=0;$i<count($requête[erreurs]);$i++){
// помилка $i
echo "<erreur>".$requête[erreurs][$i]."</erreur>";
}//for
echo "</erreurs>\n";
?>
Цей код дозволяє на основі словника $requête згенерувати код XML, аналогічний такому:
<?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>
Таблиця стилів erreurs.xsl перетворить цей код XML на код HTML.
Розглянемо перший приклад:

SGBD MySQL не запускається. У результаті отримуємо таку відповідь:

Якщо подивитися на вихідний код, отриманий браузером, то бачимо таке:
<?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>
Тепер запускаємо SGBD MySQL і виконуємо кілька послідовних симуляцій. Отримуємо таку відповідь:

Код, отриманий браузером, такий:
<?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>
Слід зазначити, що наша нова програма простіша в обслуговуванні, ніж попередня. Частина роботи була перенесена до таблиць стилів XSL. Перевага такого нового розподілу завдань полягає в тому, що після затвердження формату відповідей XML розробка таблиць стилів відбувається незалежно від розробки програми.
5.3. Аналіз документа XML у форматі PHP
Наступна версія нашого додатка impots буде клієнтом, запрограмованим на основі попереднього додатка xmlsimulations. Отже, наш клієнт отримуватиме код XML, який він повинен буде проаналізувати, щоб витягти з нього потрібну інформацію. Тут ми зробимо невелику паузу в огляді різних версій і дізнаємося, як можна проаналізувати документ XML у форматі PHP. Ми зробимо це на прикладі:
dos>e:\php43\php.exe xmlParser.php
syntaxe : xmlParser.php fichierXML
Додаток xmlParser.php підтримує один параметр: URI (Uniform Resource Identifier) документа XML, який потрібно проаналізувати. У нашому прикладі цей URI буде просто іменем файлу XML, розміщеного в каталозі програми xmlParser.php. Розглянемо два приклади виконання. У першому прикладі аналізований файл XML — це наступний файл 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>
Аналіз дає такі результати:
dos>e:\php43\php.exe xmlParser.php erreurs.xml
ERREURS
ERREUR
[erreur 1]
/ERREUR
ERREUR
[erreur 2]
/ERREUR
/ERREURS
Ми ще не описували, що саме робить програма xmlParser.php, але тут видно, що вона відображає структуру проаналізованого документа XML. У другому прикладі аналізується такий файл: 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>
Аналіз дає такі результати:
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
Програма xmlParser.php містить усе, що нам потрібно в нашій програмі для розрахунку податків, оскільки вона здатна виявляти як помилки, так і симуляції, які може надсилати веб-сервер. Розглянемо її код:
<?php
// синтаксис $0 fichierXML
// відображає структуру та вміст файлу fichierXML
// перевірка виклику
if(count($argv)!=2){
// повідомлення про помилку
fwrite(STDERR,"syntaxe : $argv[0] fichierXML\n");
// кінець
exit(1);
}//if
// ініціалізації
$file=$argv[1]; // XML-файл
$depth=0; // рівень втягнення = глибина в дереві
// програма
// створюється об’єкт для аналізу XML-тексту
$xml_parser=xml_parser_create();
// вказуємо, які функції виконувати на початку та в кінці тегу
xml_set_element_handler($xml_parser,"startElement","endElement");
// вказуємо, яку функцію виконувати при виявленні даних
xml_set_character_data_handler($xml_parser,"afficheData");
// відкриття XML-файлу для читання
if (! ($fp=@fopen($file,"r"))){
fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
exit(2);
}//if
// обробка XML-файлу блоками по 4096 байт
while($data=fread($fp,4096)){
// аналіз прочитаних даних
if (! xml_parse($xml_parser,$data,feof($fp))){
// сталася помилка
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));
// кінець
exit(3);
}//if
}//while
// файл проаналізовано
// звільняються ресурси, зайняті XML-аналізатором
xml_parser_free($xml_parser);
// кінець
exit(0);
// -----------------------------------------------------------
// функція, що викликається при виявленні тегу початку
function startElement($parser,$name,$attributs){
global $depth;
// послідовність пробілів (відступ)
for($i=0;$i<$depth;$i++){
print " ";
}//for
// атрибути
$précisions="";
while(list($attrib,$valeur)=each($attributs)){
$précisions.="($attrib,$valeur) ";
}
// виводиться назва тегу та можливі атрибути
if($précisions)
print "$name,$précisions\n";
else print "$name\n";
// додатковий рівень дерева
$depth++;
}//startElement
// -----------------------------------------------------------
// функція, що викликається при виявленні тегу завершення
function endElement($parser,$name){
// тег закінчення
// рівень відступу
global $depth;
$depth--;
// послідовність пробілів (відступ)
for($i=0;$i<$depth;$i++){
echo " ";
}//for
// ім'я тегу
echo "/$name\n";
}//endElement
// -----------------------------------------------------------
// функція відображення даних
function afficheData($parser,$data){
// рівень відступу
global $depth;
// дані відображаються
$data=trim($data);
if($data!=""){
// послідовність пробілів (відступ)
for($i=0;$i<$depth;$i++){
echo " ";
}//for
echo "[$data]\n";
}//if
}//afficheData
?>
Розглянемо код, що стосується XML. Для аналізу документа XML нашому додатку потрібен аналізатор коду XML, який зазвичай називають «парсером».
Коли парсер аналізує документ XML, він генерує такі події: «знайдено початок документа», «початок тегу», «атрибут тегу», «вміст тегу», «кінець тегу», «кінець документа» тощо. Він передає ці події методам, які ми повинні йому вказати:
<?php
...
// вказуємо, які функції виконувати на початку та в кінці тегу
xml_set_element_handler($xml_parser,"startElement","endElement");
// вказуємо, яку функцію виконувати при виявленні даних
xml_set_character_data_handler($xml_parser,"afficheData");
подія, згенерована аналізатор | методом обробки |
function startElement($parser, $name, $attributs) $parser: парсер документа $name: ім'я проаналізованого елемента. Якщо зустрічається елемент <simulations>, то буде name="simulations". $attributs: список атрибутів тегу у вигляді (ATTRIBUT,значення), де ATTRIBUT — це назва тегу великими літерами. | |
function afficheData($parser, $data) $parser: парсер документа $data: дані тегу | |
function endElement($parser, $name) параметри відповідають параметрам методу startElement. |
Функція startElement дозволяє отримати атрибути елемента за допомогою параметра $attributs. Цей параметр є словником атрибутів тегу. Отже, якщо ми маємо такий тег:
то словник $attributs матиме такий вигляд: array(marie=>oui,enfants=>2,salaire=>200000,impot=>22504)
Після визначення парсера та наведених вище методів аналіз документа здійснюється за допомогою функції xml_parse:
<?php
...
// обробка XML-файлу блоками по 4096 байт
while($data=fread($fp,4096)){
// аналіз прочитаних даних
if (! xml_parse($xml_parser,$data,feof($fp))){
...
}//if
}//while
function xml_parse($parser, $doc, $fin) Парсер $parser аналізує документ $doc. $doc може бути частиною більшого документа. Параметр $fin вказує, чи є це останньою частиною (true), чи ні (false). Під час аналізу документа $doc функції, визначені xml_set_element_handler, викликаються на початку та в кінці кожного тегу. Чи викликається функція, визначена xml_set_character_data_handler, щоразу, коли отримується вміст тегу? |
Під час аналізу документа XML можуть виникати помилки, зокрема якщо документ XML має «неправильну структуру», наприклад, у разі пропуску закриваючих тегів. У цьому випадку функція xml_parse повертає значення, що дорівнює «false»:
<?php
...
if (! xml_parse($xml_parser,$data,feof($fp))){
// сталася помилка
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));
// кінець
exit(3);
}//if
function xml_get_error_code($parser) повертає код останньої помилки, що сталася -- function xml_error_string($code) повертає повідомлення про помилку, пов'язане з кодом, переданим у параметрі -- function xml_get_current_line($parser) повертає номер рядка документа XML, який зараз аналізується парсером |
Після завершення аналізу документа звільняються ресурси, виділені для парсера:
function xml_free($parser) |
Після цього пояснення попередня програма разом із прикладами виконання стає зрозумілою сама по собі.
5.4. Програма для роботи з податковою службою: версія 6
Тепер ми маємо всі елементи для написання клієнтської програми для нашої податкової служби, яка видає XML. Ми використовуємо версію 4 нашого додатка для створення клієнта, а версію 5 залишаємо для сервера. У цьому клієнт-серверному додатку:
- сервіс моделювання розрахунку податків забезпечується додатком xmlsimulations.php. Відповідь сервера, отже, має формат XML, як ми бачили у версії 5.
- клієнт більше не є браузером, а самостійним PHP-клієнтом. Його консольний інтерфейс відповідає версії 4.
Читачеві пропонується ще раз ознайомитися з кодом додатка cltImpots.php, який був запрограмованим клієнтом у версії 4. Він отримував від сервера документ $document. На той час це був документ HTML. Тепер це документ XML. Документи HTML та $document аналізувалися за допомогою такої функції:
<?php
...
function getInfos($document){
// $document : документ HTML
// шукається або список помилок
// або таблицю моделювань
// підготовка результату
$impots[erreurs]=array();
$impots[simulations]=array();
........
return $impots;
}//getInfos
Функція отримувала документ HTML $document, аналізувала його та повертала словник $impots з двома атрибутами:
- помилки: масив помилок
- симуляції: масив симуляцій, кожна з яких є масивом із чотирма елементами (дружина, діти, зарплата, податок).
Програма cltImpots.php тепер стає cltXmlSimulations.php. Змінитися має лише та частина, яка обробляє документ, отриманий від сервера, щоб врахувати той факт, що тепер це документ XML. Функція getInfos тепер виглядає так:
<?php
...
// --------------------------------------------------------------
function getInfos($document){
// $document: документ XML
// потрібно знайти або список помилок
// або таблицю моделювань
global $impots,$balises;
// підготовка результату
$impots[erreurs]=array();
$impots[simulations]=array();
// тегування триває
$balises=array();
// створюється об’єкт аналізу XML-тексту
$xml_parser=xml_parser_create();
// вказуємо, які функції виконувати на початку та в кінці тегу
xml_set_element_handler($xml_parser,"startElement","endElement");
// вказуємо, яку функцію виконувати при виявленні даних
xml_set_character_data_handler($xml_parser,"getData");
// проводиться аналіз $document
xml_parse($xml_parser,$document);
// звільняються ресурси, зайняті XML-аналізатором
xml_parser_free($xml_parser);
// кінець
return $impots;
}//getInfos
// -----------------------------------------------------------
// функція, що викликається при виявленні тегу початку
function startElement($parser,$name,$attributs){
global $impots,$balise,$balises,$contenu;
// записуємо ім'я тегу та його вміст
$balise=strtolower($name);
$contenu="";
// додається до стека тегів
array_push($balises,$balise);
// чи це симуляція?
if($balise=="simulation"){
// фіксуються атрибути симуляції
$impots[simulations][]=array($attributs[MARIE],$attributs[ENFANTS],$attributs[SALAIRE],$attributs[IMPOT]);
}//if
}//startElement
// -----------------------------------------------------------
// функція, що викликається при виявленні тегу кінця
function endElement($parser,$name){
// отримуємо поточний тег
global $impots,$balises,$contenu;
$balise=array_pop($balises);
// чи це тег помилки?
if($balise=="erreur"){
// ще одна помилка
$impots[erreurs][]=trim($contenu);
}//if
}//endElement
// -----------------------------------------------------------
// функція обробки вмісту тегу
function getData($parser,$data){
// глобальні дані
global $balise,$contenu;
// чи це тег помилки?
if($balise=="erreur"){
// додаємо до вмісту поточного тегу
$contenu.=$data;
}//if
}//getData
Коментарі:
- функція getInfos($document) спочатку створює парсер, потім налаштовує його і, нарешті, запускає аналіз документа $document:
<?php
...
// створюється об’єкт аналізу XML-тексту
$xml_parser=xml_parser_create();
// вказуємо, які функції виконувати на початку та в кінці тегу
xml_set_element_handler($xml_parser,"startElement","endElement");
// вказуємо, яку функцію виконувати при виявленні даних
xml_set_character_data_handler($xml_parser,"getData");
// проводиться аналіз $document
xml_parse($xml_parser,$document);
- після завершення аналізу звільняються ресурси, виділені для парсера, і повертається словник $impots.
<?php
...
// звільняються ресурси, зайняті XML-аналізатором
xml_parser_free($xml_parser);
// кінець
return $impots;
- функція startElement($parser,$name,$attributs) викликається на початку кожного тегу. Вона
- помічає тег $name у масиві тегів $balises. Цей масив обробляється як стек: при виявленні символу кінця тегу останній тег, що знаходиться у стеку $balises, буде вилучений зі стека. Поточний тег також записується в $balise. У словнику $attributs містяться атрибути виявленого тегу, причому ці атрибути записані великими літерами.
- записує атрибути у глобальний словник $impots[simulations], якщо це тег симуляції.
- функція getData($parser,$data), коли вміст $data тегу було отримано. Тут було вжито запобіжного заходу. У деяких API, зокрема в Java, для обробки документів XML зазначено, що цю функцію можна викликати повторно, c.a.d. але не завжди вміст тегу $data отримується за один раз. У даному випадку документація PHP не вказує на це обмеження. З міркувань обережності отримане значення $data накопичується у глобальній змінній $contenu. Лише після виявлення символу кінця тегу можна вважати, що отримано весь вміст тегу. Єдиним тегом, на який поширюється ця обробка, є тег <erreur>.
- Функція endElement($parser, $name) викликається після кожного закриття тегу. Вона використовується тут для зміни імені поточного тегу шляхом видалення останнього тегу зі стека тегів та для додавання вмісту тегу <erreur>, який закінчується масивом $impots[erreurs].
Ось кілька прикладів виконання, спочатку з SGBD, який не було запущено:
dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php так 2 200000
Jeton de session=[e8c29ea12f79e4771960068d161229fd]
Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)
Потім з запущеним SGBD:
dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php так 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 так 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 ні 2 200000 69a54d79db10b70ed0a2d55d5026ac8b
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
[oui,2,200000,22504]
[non,2,200000,33388]
5.5. Висновок
Завдяки відповіді XML управління додатком «impos» стало простішим як для його розробника, так і для розробників клієнтських додатків.
- Розробку серверного додатка тепер можна доручити двом категоріям фахівців: розробнику сервлету PHP та графічному дизайнеру, який відповідатиме за зовнішній вигляд відповіді у браузерах. Останньому достатньо знати структуру відповіді XML сервера, щоб створити стильові таблиці, які її супроводжуватимуть. Нагадаємо, що ці таблиці оформлення зберігаються в окремих файлах XSL, незалежних від самого додатка PHP. Отже, графічний дизайнер може працювати незалежно від розробника.
- Розробникам клієнтських додатків також достатньо просто знати структуру відповіді XML від сервера. Зміни, які може внести графічний дизайнер у таблиці стилів, жодним чином не впливають на цю відповідь XML, яка завжди залишається незмінною. Це величезна перевага.
- Як розробник може вдосконалювати свій додаток PHP, не порушуючи його цілісності? По-перше, доки відповідь XML не змінюється, він може організовувати свій додаток як завгодно. Він також може вдосконалювати відповідь XML, доки зберігає елементи <erreur> та <simulation>, яких очікують його клієнти. Таким чином, він може додавати до цієї відповіді нові теги. Веб-дизайнер врахує їх у своїх таблицях стилів, і браузери зможуть отримувати нові версії відповіді. А запрограмовані клієнти продовжуватимуть працювати зі старою моделлю, а нові теги просто ігноруватимуться. Щоб це було можливо, у аналізі XML відповіді сервера шукані теги мають бути чітко ідентифіковані. Саме це було зроблено в нашому клієнті XML для податкового додатка, де в процедурах конкретно зазначалося, що обробляються теги <erreur> та <simulation>. Таким чином, інші теги ігноруються.