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) تسمح بوصف التحويلات التي يجب إجراؤها لتحويل ملف 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 في نظام ويندوز:
<?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(){
// raz du formulaire
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//with
}//effacer
function calculer(){
// vérification des paramètres avant de les envoyer au serveur
with(document.frmImpots){
//nbre d'enfants
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//salaire
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//if
// c'est bon - on envoie
submit();
}//with
}//calculer
</script>
</head>
<body background="/poly/impots/7/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/poly/impots/7/xmlsimulations.php" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui">oui
<input type="radio" name="optMarie" value="non" checked>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="3" name="txtEnfants" value=""></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value=""></td>
</tr>
<tr></tr>
<tr>
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
</body>
</html>
تجدر الإشارة إلى أن بيانات النموذج يتم إرسالها إلى URL /poly/impots/7/xmlsimulations.php. رمز التطبيق xmlsimulations.php مشابه جدًّا لرمز التطبيق impots.php. يُرجى من القارئ مراجعة هذا الرمز. ونذكر رمز الإدخال التالي:
<?php
// traite le formulaire des impôts
// bibilothèques
include "ImpotsDSN.php";
// démarrage session
session_start();
// configuration de l'application
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);
// on récupère les paramètres de la session
$session=$_SESSION["session"];
// session valide ?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// on démarre une nouvelle session
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// des erreurs ?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// affichage page d'erreurs
include $erreursImpots;
// fin
$session=array();
terminerSession($session);
}//if
}//if
// on récupère les paramètres de l'échange en cours
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// a-t-on tous les paramètres ?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// affichage formulaire vide
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// fin
terminerSession($session);
}//if
// vérification paramètres
$requête=vérifier($requête);
// des erreurs ?
if(count($requête[erreurs])!=0){
// affichage formulaire
include "$formulaireImpots";
// fin
terminerSession($session);
}//if
// calcul de l'impôt à payer
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// une simulation de plus
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// affichage formulaire
include "$formulaireImpots";
// fin
terminerSession($session);
...
كانت الصفحات HTML تُعرض بواسطة الأسطر include "...". نريد هنا إنشاء XML وليس HTML. يكفي كتابة تطبيقين جديدين هما impots_erreurs.php و impots_simulations.php لإنشاء XML بدلاً من HTML. لا يتغير باقي التطبيق. يصبح الكود عندئذٍ كما يلي:
<?php
// traite le formulaire des impôts
// bibilothèques
include "ImpotsDSN.php";
// démarrage session
session_start();
// configuration de l'application
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);
// on récupère les paramètres de la session
$session=$_SESSION["session"];
// session valide ?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// on démarre une nouvelle session
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// des erreurs ?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// affichage page d'erreurs au format XML
header("Content-type: text/xml");
include $erreursImpots;
// fin
$session=array();
terminerSession($session);
}//if
}//if
// on récupère les paramètres de l'échange en cours
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// a-t-on tous les paramètres ?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// affichage formulaire vide
include $formulaireImpots;
// fin
terminerSession($session);
}//if
// vérification paramètres
$requête=vérifier($requête);
// des erreurs ?
if(count($requête[erreurs])!=0){
// affichage erreurs au format XML
header("Content-type: text/xml");
include "$erreursImpots";
// fin
terminerSession($session);
}//if
// calcul de l'impôt à payer
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// une simulation de plus
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// affichage simulations au format XML
header("Content-type: text/xml");
include "$simulationsImpots";
// fin
terminerSession($session);
لقد قمنا سابقًا بعرض ودراسة نوعي الإجابات XML المطلوب تقديمهما، بالإضافة إلى أوراق الأنماط التي يجب أن تصاحبهما. وفيما يلي كود التطبيق impots_simulations.php:
<?php
// génère le code XML de la page de simulations de l'application impots
// qqs constantes
$xslSimulations="simulations.xsl";
// entêtes XML
echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslSimulations\" ?>\n";
// les simulations
echo "<simulations>\n";
for ($i=0;$i<count($requête[simulations]);$i++){
// simulation $i
echo "<simulation marie=\"".$requête[simulations][$i][0]."\" ".
"enfants=\"".$requête[simulations][$i][1]."\" ".
"salaire=\"".$requête[simulations][$i][2]."\" ".
"impot=\"".$requête[simulations][$i][3]."\" />\n";
}//for
echo "</simulations>\n";
?>
يتيح هذا الرمز، انطلاقًا من القاموس $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
// génère le code XML de la page d'erreurs de l'application impots
// qqs constantes
$xslErreurs="erreurs.xsl";
// entêtes XML
echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
echo "<?xml-stylesheet type=\"text/xsl\" href=\"$xslErreurs\" ?>\n";
// les erreurs
echo "<erreurs>\n";
for ($i=0;$i<count($requête[erreurs]);$i++){
// erreur $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
ستكون النسخة القادمة من تطبيق الضرائب الخاص بنا عميلاً مبرمجاً للتطبيق السابق xmlsimulations. وبالتالي، سيتلقى عميلنا كود XML الذي سيتعين عليه تحليله لاستخراج المعلومات التي تهمه. سنقوم هنا بإلقاء نظرة على الإصدارات المختلفة ونتعلم كيفية تحليل مستند XML إلى PHP. وسنقوم بذلك انطلاقًا من المثال التالي:
dos>e:\php43\php.exe xmlParser.php
syntaxe : xmlParser.php fichierXML
تقبل تطبيق xmlParser.php معلمة واحدة: URI (معرّف الموارد الموحد) للوثيقة 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
// syntaxe $0 fichierXML
// affiche la structure et le contenu du fichier fichierXML
// vérification appel
if(count($argv)!=2){
// msg d'erreur
fwrite(STDERR,"syntaxe : $argv[0] fichierXML\n");
// fin
exit(1);
}//if
// initialisations
$file=$argv[1]; // le fichier xml
$depth=0; // niveau d'indentation=profondeur dans l'arborescence
// le programme
// on crée un objet d'analyse de texte xml
$xml_parser=xml_parser_create();
// on indique quelles fonctions exécuter en début et fin de balise
xml_set_element_handler($xml_parser,"startElement","endElement");
// on indique quelle fonction exécuter lors de la rencontre de données
xml_set_character_data_handler($xml_parser,"afficheData");
// ouverture du fichier xml en lecture
if (! ($fp=@fopen($file,"r"))){
fwrite(STDERR,"impossible d'ouvrir le fichier xml $file\n");
exit(2);
}//if
// exploitation du fichier xml par blocs de 4096 octets
while($data=fread($fp,4096)){
// analyse des données lues
if (! xml_parse($xml_parser,$data,feof($fp))){
// il s'est produit une erreur
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));
// fin
exit(3);
}//if
}//while
// le fichier a été exploré
// on libère les ressources occupées par l'analyseur xml
xml_parser_free($xml_parser);
// fin
exit(0);
// -----------------------------------------------------------
// fonction appelée lors de la rencontre d'une balise de début
function startElement($parser,$name,$attributs){
global $depth;
// une suite d'espaces (indentation)
for($i=0;$i<$depth;$i++){
print " ";
}//for
// attributs
$précisions="";
while(list($attrib,$valeur)=each($attributs)){
$précisions.="($attrib,$valeur) ";
}
// on affiche le nom de la balise et les éventuels attributs
if($précisions)
print "$name,$précisions\n";
else print "$name\n";
// un niveau d'arborescence en plus
$depth++;
}//startElement
// -----------------------------------------------------------
// la fonction appelée lors de la rencontre d'une balise de fin
function endElement($parser,$name){
// fin de balise
// niveau d'indentation
global $depth;
$depth--;
// une suite d'espaces (indentation)
for($i=0;$i<$depth;$i++){
echo " ";
}//for
// le nom de la balise
echo "/$name\n";
}//endElement
// -----------------------------------------------------------
// la fonction d'affichage des données
function afficheData($parser,$data){
// niveau d'indentation
global $depth;
// les données sont affichées
$data=trim($data);
if($data!=""){
// une suite d'espaces (indentation)
for($i=0;$i<$depth;$i++){
echo " ";
}//for
echo "[$data]\n";
}//if
}//afficheData
?>
دعونا ندرس الكود المتعلق بـ XML. لتحليل مستند XML، يحتاج تطبيقنا إلى محلل كود XML يُعرف عادةً باسم "المحلل".
عندما يقوم المحلل بتحليل المستند XML، فإنه سيصدر أحداثًا مثل: «لقد وصلت إلى بداية المستند»، «بداية علامة»، «سمة علامة»، «محتوى علامة»، «نهاية علامة»، «نهاية المستند»، ... ويقوم بنقل هذه الأحداث إلى الطرق التي يجب أن نحددها له:
<?php
...
// on indique quelles fonctions exécuter en début et fin de balise
xml_set_element_handler($xml_parser,"startElement","endElement");
// on indique quelle fonction exécuter lors de la rencontre de données
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: بيانات العلامة | |
دالة endElement($parser,$name) المعلمات هي نفسها المستخدمة في الدالة startElement. |
تسمح الدالة startElement باسترداد سمات العنصر بفضل المعلمة $attributs. وهذه المعلمة عبارة عن قاموس لسمات العلامة. لذا، إذا كانت لدينا العلامة التالية:
فسيكون القاموس $attributs كما يلي: array(marie=>oui,enfants=>2,salaire=>200000,impot=>22504)
بمجرد تعريف المحلل والطرق السابقة، يتم تحليل المستند بواسطة الدالة xml_parse:
<?php
...
// exploitation du fichier xml par blocs de 4096 octets
while($data=fread($fp,4096)){
// analyse des données lues
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 قيمة تُقيَّم بـ «خطأ»:
<?php
...
if (! xml_parse($xml_parser,$data,feof($fp))){
// il s'est produit une erreur
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));
// fin
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 : document HTML
// on cherche soit la liste d'erreurs
// soit le tableau des simulations
// préparation du résultat
$impots[erreurs]=array();
$impots[simulations]=array();
........
return $impots;
}//getInfos
كانت الدالة تستقبل المستند HTML $document، وتحلله، ثم تُرجع قاموسًا $impots يحتوي على سمتين:
- الأخطاء: مصفوفة للأخطاء
- المحاكاة: مصفوفة للمحاكاة، كل محاكاة هي في حد ذاتها مصفوفة مكونة من أربعة عناصر (الزوجة،الأطفال،الراتب،الضريبة).
أصبح التطبيق cltImpots.php الآن cltXmlSimulations.php. يجب تغيير الجزء الذي يعالج المستند المستلم من الخادم فقط لمراعاة حقيقة أنه أصبح الآن مستندًا من نوع XML. وبذلك تصبح الدالة getInfos كما يلي:
<?php
...
// --------------------------------------------------------------
function getInfos($document){
// $document : document XML
// on cherche soit la liste d'erreurs
// soit le tableau des simulations
global $impots,$balises;
// préparation du résultat
$impots[erreurs]=array();
$impots[simulations]=array();
// balises en cours
$balises=array();
// on crée un objet d'analyse de texte xml
$xml_parser=xml_parser_create();
// on indique quelles fonctions exécuter en début et fin de balise
xml_set_element_handler($xml_parser,"startElement","endElement");
// on indique quelle fonction exécuter lors de la rencontre de données
xml_set_character_data_handler($xml_parser,"getData");
// on fait l'analyse de $document
xml_parse($xml_parser,$document);
// on libère les ressources occupées par l'analyseur xml
xml_parser_free($xml_parser);
// fin
return $impots;
}//getInfos
// -----------------------------------------------------------
// fonction appelée lors de la rencontre d'une balise de début
function startElement($parser,$name,$attributs){
global $impots,$balise,$balises,$contenu;
// on note le nom de la balise et son contenu
$balise=strtolower($name);
$contenu="";
// on l'ajoute à la pile des balises
array_push($balises,$balise);
// s'agit-il d'une simulation ?
if($balise=="simulation"){
// on note les attributs de la simulation
$impots[simulations][]=array($attributs[MARIE],$attributs[ENFANTS],$attributs[SALAIRE],$attributs[IMPOT]);
}//if
}//startElement
// -----------------------------------------------------------
// la fonction appelée lors de la rencontre d'une balise de fin
function endElement($parser,$name){
// on récupère la balise courante
global $impots,$balises,$contenu;
$balise=array_pop($balises);
// s'agit-il d'une balise d'erreur ?
if($balise=="erreur"){
// une erreur de plus
$impots[erreurs][]=trim($contenu);
}//if
}//endElement
// -----------------------------------------------------------
// la fonction de traitement du contenu d'une balise
function getData($parser,$data){
// données globales
global $balise,$contenu;
// s'agit-il d'une balise d'erreur ?
if($balise=="erreur"){
// on cumule au contenu de la balise courante
$contenu.=$data;
}//if
}//getData
تعليقات:
- تبدأ الدالة getInfos($document) بإنشاء محلل، ثم تهيئته، وأخيرًا تبدأ في تحليل المستند $document:
<?php
...
// on crée un objet d'analyse de texte xml
$xml_parser=xml_parser_create();
// on indique quelles fonctions exécuter en début et fin de balise
xml_set_element_handler($xml_parser,"startElement","endElement");
// on indique quelle fonction exécuter lors de la rencontre de données
xml_set_character_data_handler($xml_parser,"getData");
// on fait l'analyse de $document
xml_parse($xml_parser,$document);
- عند انتهاء التحليل، يتم تحرير الموارد المخصصة للمحلل وإرجاع القاموس $impots.
<?php
...
// on libère les ressources occupées par l'analyseur xml
xml_parser_free($xml_parser);
// fin
return $impots;
- يتم استدعاء الدالة startElement($parser,$name,$attributs) عند بداية كل علامة. وهي
- يتم تسجيل العلامة $name في جدول العلامات $balises. ويتم إدارة هذا الجدول كمكدس: عند العثور على رمز نهاية العلامة، يتم إزالة آخر علامة مكدسة في $balises من المكدس. يتم أيضًا تسجيل العلامة الحالية في $balise. في القاموس $attributs، توجد سمات العلامة التي تم العثور عليها، وتكون هذه السمات بأحرف كبيرة.
- تسجل السمات في القاموس العام $impots[simulations]، إذا كانت العلامة علامة محاكاة.
- تُستدعى الدالة getData($parser,$data) عندما يتم استرداد محتوى العلامة. هنا، تم اتخاذ احتياط. في بعض برامج معالجة المستندات، لا سيما Java، يُشار إلى أنه يمكن استدعاء هذه الوظيفة بشكل متكرر. وليس بالضرورة أن نحصل على محتوى العلامة $data دفعة واحدة. وهنا، لا تشير الوثائق إلى هذا القيد. كإجراء احترازي، يتم تجميع القيمة التي تم الحصول عليها في متغير عام. ولن نعتبر أننا حصلنا على محتوى العلامة بالكامل إلا عند الوصول إلى رمز نهاية العلامة. العلامة الوحيدة التي تنطبق عليها هذه المعالجة هي العلامة <erreur>.
- يتم استدعاء الدالة endElement($parser,$name) عند نهاية كل علامة. وتُستخدم هنا لتغيير اسم العلامة الحالية عن طريق إزالة العلامة الأخيرة من مكدس العلامات وإضافة محتوى العلامة <erreur> التي تنتهي عند المصفوفة $impots[erreurs].
فيما يلي بعض أمثلة التنفيذ، أولاً مع SGBD الذي لم يتم تشغيله:
dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php oui 2 200000
Jeton de session=[e8c29ea12f79e4771960068d161229fd]
Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)
ثم مع SGBD الذي تم تشغيله:
dos>e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php oui 3 200000
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
dos >e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php oui 2 200000 69a54d79db10b70ed0a2d55d5026ac8b
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
[oui,2,200000,22504]
dos >e:\php43\php.exe cltXmlSimulations.php http://localhost/poly/impots/8/xmlsimulations.php non 2 200000 69a54d79db10b70ed0a2d55d5026ac8b
Jeton de session=[69a54d79db10b70ed0a2d55d5026ac8b]
Simulations :
[oui,3,200000,16400]
[oui,2,200000,22504]
[non,2,200000,33388]
5.5. الخلاصة
بفضل استجابته XML، أصبح تطبيق الضرائب أسهل في الإدارة سواء بالنسبة لمصممه أو لمصممي تطبيقات العملاء.
- يمكن الآن تكليف نوعين من الأشخاص بتصميم تطبيق الخادم: مطور PHP للـservlet ومصمم الجرافيك الذي سيتولى إدارة مظهر استجابة المتصفح في المتصفحات. ويكفي أن يكون هذا الأخير على دراية ببنية استجابة الخادم XML لإنشاء أوراق الأنماط التي ستصاحبها. تجدر الإشارة إلى أن أوراق الأنماط هذه تُخزَّن في ملفات منفصلة ومستقلة عن التطبيق. وبالتالي، يمكن لمصمم الجرافيك العمل بشكل مستقل عن المطور.
- كما أن مصممي تطبيقات العملاء يحتاجون ببساطة إلى معرفة بنية استجابة الخادم XML. ولا تؤثر التعديلات التي قد يجريها مصمم الجرافيك على أوراق الأنماط بأي شكل من الأشكال على هذه الاستجابة XML التي تظل كما هي دائمًا. وهذه ميزة هائلة.
- كيف يمكن للمطور تطوير تطبيقه PHP دون إحداث خلل في النظام؟ أولاً، طالما أن استجابته XML لم تتغير، فيمكنه تنظيم تطبيقه كما يشاء. كما يمكنه تطوير الاستجابة XML طالما احتفظ بالعناصر <erreur> و<simulation> التي يتوقعها عملاؤه. وبذلك يمكنه إضافة علامات جديدة إلى هذه الاستجابة. وسيأخذ مصمم الجرافيك هذه العناصر في الاعتبار في أوراق الأنماط الخاصة به، وستتمكن المتصفحات من الحصول على الإصدارات الجديدة من الرد. أما العملاء المبرمجون فسيستمرون في العمل مع النموذج القديم، حيث سيتم تجاهل العلامات الجديدة ببساطة. ولتحقيق ذلك، يجب أن يتم تحديد العلامات المطلوبة بشكل صحيح في تحليل XML لرد الخادم. وهذا ما تم تنفيذه في عميلنا XML الخاص بتطبيق الضرائب، حيث تم النص في الإجراءات بشكل محدد على معالجة العلامات <erreur> و<simulation>. وبالتالي، يتم تجاهل العلامات الأخرى.