XML en L2, université d'Angers
gilles.hunault@univ-angers.fr
T.P. numéro 5 : Programmation
Table des matières cliquable
1. Programmation en PHP (1) : lecture
2. Programmation en PHP (2) : génération
3. Programmation en PHP (3) : recherche
4. Programmation en PHP (4) : transformation
Il est possible d'afficher toutes les solutions via ?solutions=1 et de les masquer via ?solutions=0 .
Attention : tout code PHP qui doit produire du XHTML doit être écrit via du PHP conceptuel, donc sans marqueur < ou >.
On pourra consulter le code-source php de cette page à titre d'exemple.
Un rappel sur les fonctions et méthodes PHP pour traiter du XML se trouve à la page xmlphp.
1. Programmation en PHP (1) : lecture
Comment fait-on pour lire des fichiers en XML ?
Essayer par exemple d'afficher, en ligne de commandes, pour le fichier films2.xml, tous les titres de films avec leur numéro, comme ci-dessous :
Voici tous les titres de films 1. Vertigo 2. Alien 3. Titanic 4. Sacrifice 5. Volte/Face 6. Sleepy Hollow 7. American Beauty 8. Impitoyable 9. Gladiator 10. Blade Runner 11. Piège de cristal 12. 58 minutes pour vivre 13. Van Gogh 14. Seven 15. L'armée des douze singes 16. Le nom de la rose 17. Pulp fiction 18. Mary à tout prix 19. Terminator 20. Les dents de la mer 21. Le silence des agneaux 22. Le prince d'Egypte 23. Godzilla 24. Matrix 25. Mission: Impossible 26. Kagemusha 27. Les pleins pouvoirs 28. Le gendarme et les extra-terrestres 29. Les frères pétards 30. Le monde perdu 31. Rain Man 32. Top Gun 33. Les bronzés font du ski 34. MICROCOSMOS 35. Psychose 36. Le retour du Jedi 37. Les oiseaux 38. Reservoir dogs 39. Eyes Wide Shut 40. Shining 41. Pas de printemps pour Marnie 42. Fenêtre sur cour 43. La mort aux trousses 44. Jeanne d'Arc 45. Le cinquième élément 46. Léon 47. Nikita 48. Le grand bleuOn pourra utiliser SimpleXML.
Solution : masquer la solution
Comme le montre la page de manuel de PHP consacrée à XML, on dispose de nombreuses fonctions et de plusieurs modules pour traiter du XML en PHP. Avec SimpleXML la lecture d'un fichier XML se fait via la fonction simplexml_load_file() et produit un objet de classe XML. Ensuite la méthode xpath() renvoie un tableau d'éléments pour lequel l'itération via foreach est possible. Le script demandé se réduit donc, hors commandes d'affichages avec echo, à une lecture et à une itération.
Fichier titresL2.php
<?php echo "Voici tous les titres de films \n" ; $nomFicXml = "films2.xml" ; $films = simplexml_load_file($nomFicXml) ; /* attention, ce n'est pas exactement comme $films = new SimpleXMLElement(file_get_contents($nomFicXml)) ; */ $nbFilms = 0 ; foreach ($films->xpath('//TITRE') as $titre) { $nbFilms++ ; echo sprintf("%3d",$nbFilms).". " ; echo $titre, PHP_EOL; } // fin de pour chaque ?>
2. Programmation en PHP (2) : génération
Essayez d'écrire le programme PHP correspondant à l'action du formulaire genere_f.php de façon à ce que le navigateur ouvre le fichier XML résultat dès qu'il est généré. Ici, on utilisera DOM.
Au passage, quelle serait la «meilleure» structuration du fichier résultat ?
Solution : masquer la solution
Il est certainement "rassurant" de commencer par vérifier qu'on est bien capable d'appeler le programme défini par l'attribut action de l'élément <form ...>. Pour cela, on peut se contenter du code suivant :
<?php echo "<h1>Vous avez bien appuyé sur le bouton \"générer\" dans le formulaire</h1>" ; ?>Vous pouvez vérifier ici ce qu'il affiche.
Pour que le script PHP soit correct, il faut utiliser en début de script le bon header(). Le début du code doit donc ressembler à :
<?php header('Content-type: text/xml') ; $domTree = new DOMDocument('1.0', 'UTF-8'); $rootElt = $domTree->createElement("proteins"); $tmp = $domTree->appendChild($rootElt); echo $domTree->saveXML(); ?>Vous pouvez voir là le rendu associé à ce programme PHP et constater que le navigateur affiche bien le contenu en mode XML.
Le formulaire utilise la méthode GET donc la gestion des paramètres peut se faire simplement avec des tests if (isset($_GET[....
Si on utilise DOM, les fonctions et méthodes ont des noms déja connus via l'API de JavaScript. Ainsi, pour créer un élément on utilise la méthode createElement(), pour ajouter un fils on utilise la méthode appendChild(), pour créer ou définir un attribut, on utilise la méthode setAttribute(). La création d'un objet XML se fait via DOMDocument() et l'affichage par la méthode saveXML().
Voici donc le code PHP complet nommé genereXml.php qui produit le fichier XML en fonction du choix dans le formulaire
Fichier genereXml.php
<?php # (gH) -_- genereXml.php ; TimeStamp (unix) : 29 Septembre 2012 vers 18:04 error_reporting(E_ALL | E_NOTICE | E_STRICT) ; header('Content-type: text/xml') ; $pId = array() ; $pClass = array() ; $pLength = array() ; $pSeq = array() ; include("genereXml-inc.php") ; # contient les données pour les séquences $nbProt = $pn ; # issu de genereXml-inc.php $domtree = new DOMDocument('1.0', 'UTF-8'); $rootElt = $domtree->createElement("proteins"); $tmp = $domtree->appendChild($rootElt); for ($idp=1;$idp<=$nbProt;$idp++) { $prot = $domtree->createElement("protein"); # nouvel élément <protein> if (isset($_GET["i"])) { # identifiant if ($_GET["i"]=="e") { $pi = $domtree->createElement("identifiant",$pId[$idp]) ; # nouvel élément <identifiant> $tmp = $prot->appendChild($pi); } else { $prot->setAttribute("identifiant",$pId[$idp]) ; # nouvel attribut identifiant } # fin si sur élément ou attribut } # fin si sur i if (isset($_GET["c"])) { # classe if ($_GET["c"]=="e") { $pc = $domtree->createElement("class",$pClass[$idp]) ; $tmp = $prot->appendChild($pc); } else { $prot->setAttribute("class",$pClass[$idp]) ; } # fin si sur élément ou attribut } # fin si sur c if (isset($_GET["l"])) { # longueur if ($_GET["l"]=="e") { $pl = $domtree->createElement("longueur",$pLength[$idp]) ; $tmp = $prot->appendChild($pl); } else { $prot->setAttribute("longueur",$pLength[$idp]) ; } # fin si sur élément ou attribut } # fin si sur c if (isset($_GET["s"])) { # sequence if ($_GET["s"]=="e") { # une variante, sans doute moins lisible $tmp = $prot->appendChild( $domtree->createElement("sequence",$pSeq[$idp]) ) ; } else { $prot->setAttribute("sequence",$pSeq[$idp]) ; } # fin si sur élément ou attribut } # fin si sur s # ajout de la nouvelle protéines $tmp = $rootElt->appendChild($prot); } ; # fin pour idp echo $domtree->saveXML(); ?>Le fichier inclus cité, soit genereXml-inc.php, contient juste du code pour le contenu des protéines :
<?php # # (gH) -_- genereXml-inc2.php ; TimeStamp (unix) : 27 Novembre 2017 vers 14:25 $pn = 0 ; # protéine 1 $pn++ ; $pId[$pn] = "A2ZDX4" ; $pClass[$pn] = 1 ; $pSeq[$pn] = "MEYQGQHGGHASSRADEHGNPAVTTGNAPTGMGAGHIQEP" ; $pSeq[$pn] .= "AREDKKTDGVLRRSGSSSSSSSSEDDGMGGRRKKGIKEKI" ; $pSeq[$pn] .= "KEKLPGGNKGNNQQQQQEHTTTTTGGAYGPQGHDTKIATG" ; $pSeq[$pn] .= "AHGGTAATTADAGGEKKGIVDKIKEKLPGQH" ; $pLength[$pn] = strlen($pSeq[$pn]) ; # protéine 2 $pn++ ; $pId[$pn] = "A2ZDX6" ; $pClass[$pn] = 1 ; $pSeq[$pn] = "MENYQGQHGYGADRVDVYGNPVAGQYGGGATAP" ; $pLength[$pn] = strlen($pSeq[$pn]) ; # protéine 3 $pn++ ; $pId[$pn] = "AAA33480" ; $pClass[$pn] = 2 ; $pSeq[$pn] = "MEDERNTQQHQGGEQAQDQENEVKDRGLLDSLLGRNKH" ; $pLength[$pn] = strlen($pSeq[$pn]) ; # protéine 4 $pn++ ; $pId[$pn] = "AAB18201" ; $pClass[$pn] = 2 ; $pSeq[$pn] = "MEDERSTQSYQGGEAAEQVEVTDRG" ; $pLength[$pn] = strlen($pSeq[$pn]) ; # protéine 5 $pn++ ; $pId[$pn] = "AAB18202" ; $pClass[$pn] = 2 ; $pSeq[$pn] = "MEDERSTQSYQGGEAAEQVEVTDR" ; $pLength[$pn] = strlen($pSeq[$pn]) ; ?>La "meilleure" structuration est celle qui met les identifiants et les séquences en éléments, avec les classes et les longueurs en attributs, car ces deux dernières informations peuvent sans doute être considérées comme moins importantes. Un lien possible avec ces choix, qui utilise la solution programmée précédente est ici.
Une des difficultés pour programmer du XML en PHP qu'il n'est pas possible d'utiliser des "echo ..." pour afficher ce qui se passe. On passe donc par des commentaires au sens de XML comme ici. Le détail du code source associé est là.
3. Programmation en PHP (3) : recherche
On voudrait compter le nombre de protéines du fichier leadb880.xml et vérifier que chaque protéine a bien un attribut length. Ecrire un script PHP qui effectue ce traitement puis qui trouve la plus petite longueur de protéine et la plus grande. On pourra se contenter d'un script en ligne de commandes.
Solution : masquer la solution
Ici, aucune bibliothèque ni module n'est imposé. Dans la mesure où SimpleXML est simple (!) et suffit ici, on peut se contenter du code suivant :
Fichier protminmax.php
<?php echo "Longueurs de protéines. \n" ; $nomFicXml = "leadb880.xml" ; $prots = simplexml_load_file($nomFicXml) ; $minLng = pow(10,6) ; # ceci est du php 7 : $minLng = 10**6 ; $maxLng = 0 ; foreach ($prots->xpath('//@length') as $lng) { $lng = intval($lng) ; if ($lng<$minLng) { $minLng = $lng ; } ; if ($lng>$maxLng) { $maxLng = $lng ; } ; } // fin de pour chaque echo " longueur minimale $minLng aa, longueur maximale $maxLng aa.\n\n" ; ?>Voici ce qu'affiche ce script :
$gh> php protminmax.php Longueurs de protéines. longueur minimale 66 aa, longueur maximale 843 aa.On notera les initialisations de longueur minimale et maximale assez "laxistes" et on remarquera l'utilisation de la fonction intval() pour forcer la longueur à être un entier, XML ne connaissant que des chaines de caractères pour les valeurs des attributs.
Les initialisations des longueurs minimales et maximales du programme précédent sont pas très rigoureuses. Il vaut mieux utiliser la première longueur vue comme minimum et maximum. Voici donc un code "plus propre"&,nsp;:
<?php # # (gH) -_- protminmaxv2.php ; TimeStamp (unix) : 19 Février 2018 vers 11:20 $nomFicXml = "leadb880.xml" ; $prots = simplexml_load_file($nomFicXml) ; $lngs = $prots->xpath('//@length') ; echo "Longueurs des ".count($lngs)." protéines. \n" ; $nbProt = 0 ; foreach ($lngs as $lng) { $nbProt++ ; $lng = intval($lng) ; if ($nbProt==1) { $minLng = $lng ; $maxLng = $lng ; } else { if ($lng<$minLng) { $minLng = $lng ; } ; if ($lng>$maxLng) { $maxLng = $lng ; } ; } ; # fin si } // fin de pour chaque echo " longueur minimale $minLng aa, longueur maximale $maxLng aa.\n\n" ; ?>
4. Programmation en PHP (4) : transformation
Ecrire une transformation XSL qui compte les films, les artistes et les metteurs en scène, comme ci-dessous. On la testera en ligne de commandes.
$gh> xmlstarlet tr nbfa1.xsl films2.xml Il y a 48 films dans films2.xml et 117 artistes. 48 références de metteurs en scènes sont utilisées pour 32 metteurs en scène distincts.Ecrire ensuite une page Web en PHP valide au sens de XHTML 1.0 Strict qui effectue cette transformation sur le serveur. On affichera le résultat dans un élément de type <pre> comme ici.
Comment faut-il faire si on veut afficher avec de la couleur et dans des paragraphes comme dans la page nbfa2 ?
Solution : masquer la solution
La solution en ligne de commandes avait déjà été vue la semaine dernière, dans le T.P. précédent. Nous la reproduisons ici, avec un encodage en ISO8859-15 explicite car c'est celui de la page Web qui exécute la transformation XSL.
<?xml version="1.0" encoding="ISO-8859-1" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text" encoding="ISO8859-15" /> <!-- fichier nbfa1.xsl, s'applique à films2.xml --> <xsl:template match="/"> <xsl:text>Il y a </xsl:text> <xsl:value-of select="count(//FILM)" /> <xsl:text> films dans films2.xml</xsl:text> <xsl:text> et </xsl:text> <xsl:value-of select="count(//ARTISTE)" /> <xsl:text> artistes. </xsl:text> <xsl:value-of select="count(//MES)" /> <xsl:text> références de metteurs en scènes sont utilisées pour </xsl:text> <!-- ceci est du XSL 2 : <xsl:value-of select="count(distinct-values(//MES))" /> --> <xsl:value-of select="count(//ARTISTE[./@id=//MES/@idref])" /> <xsl:text> metteurs en scène distincts. </xsl:text> </xsl:template> <!-- fin de document --> </xsl:stylesheet>Si xmlstarlet est accessible depuis PHP sur le serveur, on peut se contenter du code suivant pour répondre à la question :
<?php # # (gH) -_- nbfa1.php ; TimeStamp (unix) : 27 Novembre 2017 vers 15:55 error_reporting(E_ALL | E_NOTICE | E_STRICT ) ; include("std.php") ; debutPage("Nombre de films et d'acteurs","strict") ; debutSection() ; h1("Nombre de films, d'acteurs et de metteurs en scène") ; pre() ; system("xmlstarlet tr nbfa1.xsl films2.xml") ; finpre() ; finSection() ; finPage() ; ?>De même si on veut du XHTML dans la sortie de la transformation, il suffit de modifier légèrement la feuille de style, d'enlever les <xsl:text> et d'ajouter des <span class=...> :
<?xml version="1.0" encoding="ISO-8859-1" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" encoding="ISO8859-15" /> <!-- fichier nbfa2.xsl, s'applique à films2.xml --> <xsl:template match="/"> <p> Il y a <span class="grouge"> <xsl:value-of select="count(//FILM)" /> </span> films dans films2.xml et <span class="gvert"> <xsl:value-of select="count(//ARTISTE)" /> </span> artistes. </p> <p> <span class="grouge"> <xsl:value-of select="count(//MES)" /> </span> références de metteurs en scènes sont utilisées pour <span class="gbleuf"> <xsl:value-of select="count(//ARTISTE[./@id=//MES/@idref])" /> </span> metteurs en scène distincts. </p> </xsl:template> <!-- fin de document --> </xsl:stylesheet>Le résultat est visible ici.
Si par contre xmlstarlet n'est pas accessible, on peut effectuer la transformation dans PHP via XSLTProcessor, importStyleSheet() et transformToXML comme suit
<?php # # (gH) -_- nbfa3.php ; TimeStamp (unix) : 27 Novembre 2017 vers 16:22 error_reporting(E_ALL | E_NOTICE | E_STRICT ) ; include("std.php") ; debutPage("Nombre de films et d'acteurs","strict") ; debutSection() ; h1("Nombre de films, d'acteurs et de metteurs en scène") ; $nomXml = "films2.xml" ; $nomXsl = "nbfa2.xsl" ; $films = simplexml_load_file($nomXml) ; $transf = simplexml_load_file($nomXsl) ; $xsl = new XSLTProcessor() ; $xsl->importStyleSheet($transf) ; echo $xsl->transformToXML($films) ; finSection() ; finPage() ; ?>
5. Programmation en JavaScript (1) : lecture
Comment fait-on pour lire du XML en JavaScript ? Reproduire la sortie de l'exercice 1 via du Javascript externalisé et non intrusif. On pourra utiliser la page nbelt.php comme point de départ, l'archive des fichiers à utiliser est ici.
<?php # # (gH) -_- nbelt.php ; TimeStamp (unix) : 27 Novembre 2017 vers 16:31 error_reporting(E_ALL | E_NOTICE | E_STRICT ) ; include("std.php") ; debutPage("Nombre d'éléments XML","strict") ; debutSection() ; h1("Comptage d'éléments XML") ; finSection() ; finPage() ; ?>Solution : masquer la solution
Commençons par ajouter du JavaScript à la page. Pour cela, on déclare le fichier nbelt.js comme paramètre numéro quatre dans l'appel de la fonction debutPage(), ce qui permet d'inclure le code JavaScript dès la partie <head> du document. Voici donc la nouvelle page Web :
Fichier nbeltjs.php
<?php # # (gH) -_- nbelt.php ; TimeStamp (unix) : 27 Novembre 2017 vers 16:31 error_reporting(E_ALL | E_NOTICE | E_STRICT ) ; include("std.php") ; debutPage("Nombre d'éléments XML","strict","","nbelt.js") ; debutSection() ; h1("Comptage d'éléments XML") ; pre("cadre","nb") ; finpre() ; finSection() ; finPage() ; ?>Pour que le JavaScript soit non intrusif, on ne doit pas définir une action à exécuter dès le chargement de la page (évènement load). Par contre on doit définir un gestionnaire d'évènements et ajouter ce qu'on veut faire à la liste des évènements prévus au chargement. Si on convient que l'on veut exécuter une fonction nommée films alors le début du fichier JavaScript doit ressembler à :
// # (gH) -_- nbelt.js ; TimeStamp (unix) : 27 Novembre 2017 vers 16:38 // ############# lecture de films2.xml et affichage des titres de films function films() { [..] ici notre code de traitement XML } // fin de fonction films function addEvent(obj, evType, fn) { if (obj.addEventListener){ obj.addEventListener(evType, fn, false); return true; } else if (obj.attachEvent){ var r = obj.attachEvent("on"+evType, fn); return r; } else { return false; } ; // fin de si } // fin de fonction addEvent addEvent(window, 'load', films);Il y a deux actions à réaliser dans notre fonction films : charger le fichier puis évaluer du code XPATH pour trouver les titres de films. La première action est très classique si on connait AJAX car on passe par XMLHttpRequest. Pour la seconde, il faut utiliser la méthode evaluate pour traiter les expressions XPATH. Voici le code complet du fichier JavaScript nommé nbeltjs.
// # (gH) -_- nbelt.js ; TimeStamp (unix) : 27 Novembre 2017 vers 16:38 // ############# lecture de films2.xml et affichage des titres de films function films() { var preNb = window.document.getElementById("nb") ; var xhr ; try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e2) { try { xhr = new XMLHttpRequest(); } catch (e3) { xhr = false; } } } // fin du catch xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { preNb.innerHTML += "Voici tous les titres de films :\n\n" ; films = xhr.responseXML ; var nbFilms = 0 var iterator = films.evaluate('//TITRE', films, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null ); var thisNode = iterator.iterateNext(); while (thisNode) { nbFilms++ ; preNb.innerHTML += " " if (nbFilms<10) { preNb.innerHTML += " " } preNb.innerHTML += nbFilms + ". " + thisNode.textContent + "\n" ; thisNode = iterator.iterateNext(); } ; // fin de tant que } // fin de si } ; // fin de onreadystatechange var url = "films2.xml" xhr.open("GET",url,true) xhr.send(null) } // fin de fonction films function addEvent(obj, evType, fn) { if (obj.addEventListener){ obj.addEventListener(evType, fn, false); return true; } else if (obj.attachEvent){ var r = obj.attachEvent("on"+evType, fn); return r; } else { return false; } ; // fin de si } // fin de fonction addEvent addEvent(window, 'load', films);On peut vérifier ici ce que cela donne.
Remarque :
Si on utilise JQUERY on peut se passer de XPATH. A titre d'exemple, on pourra consulter la page nbeltjquery.php dont le code est ci-dessous :
<?php # # (gH) -_- nbeltjquery.php ; TimeStamp (unix) : 27 Novembre 2017 vers 17:55 error_reporting(E_ALL | E_NOTICE | E_STRICT ) ; include("std.php") ; debutPage("Nombre d'éléments XML","strict","","https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") ; debutSection() ; h1("Comptage d'éléments XML avec JQUERY") ; pre("cadre","nb") ; finpre() ; jsf("nbeltjq.js") ; finSection() ; finPage() ; ?>Le code complet du fichier JavaScript associé, qui utilise JQUERY, nommé nbeltjq.js, formatté selon nos règles, est ci-dessous :
$(document).ready( function() { $.ajax( { type: "GET" , url: "films2.xml" , dataType: "xml" , success: function(xml) { var nbFilms = 0 $('#nb')[0].innerHTML = "Voici tous les titres de films :\n\n" ; $(xml).find('TITRE').each( function() { nbFilms++ strNbFilms = nbFilms if (nbFilms<10) { strNbFilms = " " + nbFilms } $('#nb')[0].innerHTML += " " + strNbFilms + ". " + $(this).text() + "\n" } // fin de fonction ) // fin de each } // fin de success } // fin de structure ) // fin de ajax } // fin de fonction ) // fin de ready
6. Programmation en JavaScript (2) : génération
Essayer de produire, via du JavaScript externalisé, le contenu XML suivant
<trajets> <trajet id="tr02436"> <train>TGV </train> <depart>Angers</depart> <arrive>Paris Montparnasse</arrive> </trajet> <trajet id="tr5897"> <train>Micheline </train> <depart ts="2016-09-08 13:00">Troyes</depart> <arrive ts="2016-09-08 17:08">Dijon</arrive> </trajet> </trajets>Solution : masquer la solution
Vous n'êtes pas autorisé pour l'instant à lire la solution. Le fichier solution sera indiqué pendant le T.P.
Retour à la page principale de (gH)