338 Chapitre 8. XML function donneesCaracteres ($parser , $chaine) { global $tab_elements , $element_courant ; if ( trim ($chaine) != "") $tab_elements [ $element_courant ] .= $chaine ; } On reçoit en argument la chaîne de caractères constituant le contenu du nœud de texte. De tels nœuds sont très souvent constitués uniquement d’espaces ou de retours à la ligne, quand il servent uniquement à la mise en forme du document. On élimine ici ces espaces avec la fonction PHP trim() et on vérifie que la chaîne obtenue n’est pas vide. On sait alors qu’on a affaire au contenu d’un élément. La fonction ne permet pas de savoir de quel élément il s’agit (rappelons que les éléments et le texte constituent des nœuds distincts dans l’arborescence d’un document XML, et sont donc traités séparément par le parseur). La seule solution est de s’appuyer sur une variable globale, $element_courant qui stocke le nom du dernier élément rencontré (voir la fonction debutElement()). On utilise ce nom pour mémoriser le contenu dans le tableau $tab_elements, lui aussi déclaré comme une variable globale. Ce style de programmation, assez laborieux, est imposé par l’absence d’informa- tion sur le contexte quand une fonction est déclenchée. On ne sait pas à quelle profondeur on est dans l’arbre, quels sont les éléments rencontrés auparavant ou ceux qui qui vont être rencontrés après, etc. Cette limitation est le prix à payer pour l’efficacité du modèle d’analyse : le parseur se contente de parcourir le document une seule fois, détectant le marquage au fur et à mesure et déclenchant les fonctions appropriées. Le code ci-dessous montre comment faire appel au module, en l’appliquant au document Gladiator.xml (voir page 318). Exemple 8.13 exemples/ExSAX.php : Exemple d’application du parseur <?php // Application des fonctions SAX require ("SAX.php"); Header ("Content-type: text/plain"); // Analyse du document $film = ParseFilm ("Gladiator.xml"); // Affichage des donn´ees extraites while ( list ($nom, $val) = each ($film)) echo "Nom : $nom Valeur : $val\n"; ?> 8.3 Import de données XML dans MySQL 339 On obtient alors le résultat suivant (noter que les noms d’éléments sont mis en majuscules par le parseur, option par défaut qui peut être modifiée) : Exemple 8.14 ResultatSAX.txt : Résultat de l’application du parseur balise ouvrante de FILM balise ouvrante de TITRE balise fermante de TITRE balise ouvrante de ANNEE balise fermante de ANNEE balise ouvrante de CODEPAYS balise fermante de CODEPAYS balise ouvrante de GENRE balise fermante de GENRE balise ouvrante de REALISATEUR balise fermante de REALISATEUR balise fermante de FILM Nom : TITRE Valeur : Gladiator Nom : ANNEE Valeur : 2000 Nom : CODEPAYS Valeur : USA Nom : GENRE Valeur : Drame L’approche très simple employée ici se généralise difficilement à des documents XML plus complexes comprenant des imbrications d’éléments, comme par exemple un film avec son réalisateur et la liste de ses acteurs. Le fait de devoir mémoriser dans des variables globales le positionnement du parseur dans le document rend rapidement la programmation assez laborieuse. Heureusement, une fonctionnalité très intéressante du parseur XML/PHP permet une intégration forte avec la programmation orientée-objet. Nous montrons dans ce qui suit comment utiliser cette fonctionnalité. C’est également l’occasion de revenir sur les notions de spécialisation et d’héritage de la programmation objet, présentées dans le chapitre 3. 8.3.3 Une classe de traitement de documents XML La fonction xml_set_object() permet d’associer un parseur XML et un objet, avec deux effets liés : • les « déclencheurs » seront pris parmi les méthodes de l’objet (ou, plus préci- sément, de la classe dont l’objet est une instance) ; • ces déclencheurs ont accès aux variables d’état de l’objet, ce qui évite d’avoir recours aux variables globales. On peut donc développer des classes, dédiées chacune à un type de document particulier (par exemple nos films), avec un style de programmation beaucoup plus élégant que celui employé précédemment. Chaque classe fournit, sous forme de méthodes, des fonctionnalités de création du parseur, de gestion des erreurs, de défi- nition des déclencheurs, de lancement de l’analyse, etc. Afin d’essayer d’être –encore 340 Chapitre 8. XML une fois– le plus général possible, nous allons distinguer dans ces fonctionnalités celles, génériques, qui sont identiques pour tous les types de document de celles qui sont spécifiques à un type de document donné. Il serait dommage de dupliquer les premières autant de fois qu’il y a de types de documents, avec les inconvénients évidents en termes de maintenance et de modification qui en découlent. Il est donc souhaitable de recourir à la spécialisation objet qui permet de « factoriser » les fonctions génériques et de les rendre disponibles pour chaque classe traitant d’un type de document particulier. Le principe consiste à créer une (super-)classe qui regroupe les fonctions géné- riques au traitement d’un document par SAX, et à créer, par spécialisation, une sous-classe de cette super-classe qui peut réutiliser ses fonctionnalités (héritage), les redéfinir (surcharge), et lui en ajouter d’autres (extension). Voici le code de la super-classe. Nous en décrivons plus bas les principales méthodes. Exemple 8.15 webscope/lib/SAX.php : Une classe générique de traitement d’un document XML <?php /∗∗ ∗ Classe générique d’appel aux fonctions SAX d’ analyse de ∗ documents XML. ∗ Cette classe doit être sous−typée pour chaque type de ∗ document ∗ Au niveau de la super− classe on se contente d’instancier le ∗ parseur , ∗ de gérer les messages d ’erreur , et d’ afficher au fur et à ∗ mesure ∗ le contenu du document analysé . ∗ / class SAX { private $parseur ; protected $donnees; // Données caractères rencontrées en cours d’analyse protected $erreur_rencontree , $message; // Indicateur et message d ’erreur // Constructeur de la classe function __construct ($codage) { // On instancie le parseur $this−>parseur = xml_parser_create($codage) ; // On indique que les déclencheurs sont des méthodes de la // classe xml_set_object ($this−>parseur , &$this ); // Tous les noms d’éléments et d’attributs seront traduits en // majuscules 8.3 Import de données XML dans MySQL 341 xml_parser_set_option($this−>p a r s e u r , XML_OPTION_CASE_FOLDING , true); xml_set_element_handler ($this−>parseur , "debutElement" , " finElement"); xml_set_character_data_handler ($this−>parseur , " donneesCaracteres"); $this−>erreur_rencontree = 0; } // Méthode générique de traitement des débuts d ’ éléments protected function debutElement ($parseur , $nom, $attrs) { // On recherche si une méthode nommée "debut {NomElement}" existe if ( method_exists ($this , "debut$nom" )) { call_user_func ( array( $this , "debut$nom") , $parseur , $nom , $attrs); } else if ( get_class ($this) == "sax") { echo "Début d ’ élément : < ; " . $nom . "> ;\n" ; } // Effacement des données caractères $this−>donnees = " " ; } // Méthode générique de traitement des fins d ’ élément protected function finElement ($parseur , $nom) { // On recherche si une méthode nommée " fin {NomElement}" // existe if ( method_exists ($this , "fin$nom" )) { call_user_func ( array($this , "fin$nom") , $parseur , $nom) ; } else if ( get_class ($this) == "sax") { echo "Fin d ’élément : < ;/ " . $nom . "> ;\n" ; } } // Pour les données , on stocke simplement au fur et à mesure // dans la propriété $this −>donnees protected function donneesCaracteres ($parseur , $chaine) { // On ne prend pas les chaînes vides ou ne contenant que des // blancs if (!empty($chaine)) $this−>donnees .= $chaine ; } 342 Chapitre 8. XML // Méthode pour analyser le document public function parse( $fichier ) { // Ouverture du fichier if (!($f=fopen($fichier , "r"))) { trigger_error ("Impossible d’ ouvrir le fichier $fichier\n" , E_USER_ERROR) ; return ; } // Analyse du contenu du document while ($data = fread($f , 4096)) { if (!xml_parse ($this−>parseur , $data , feof($f))) { $this−>erreur ("Erreur rencontrée ligne " . xml_error_string( xml_get_error_code($this−>parseur)) . "%de $fichier : " . xml_get_current_line_number($this−>parseur)); return ; } } fclose ($f); } // Destructeur de la classe function __destruct () { xml_parser_free($this−>parseur); } // Fonction retournant le message d’erreur public function messageErreur ($message) { return $this−>message ; } // Fonction indiquant si une erreur est survenue public function erreurRencontree () { return $this−>erreur_rencontree ; } // Fonction traitant une erreur function erreur ($message) { trigger_error ($message , E_USER_ERROR) ; } } ?> La classe comprend trois méthodes, __construct() (le constructeur), parse() et __destruct() (le destructeur), qui correspondent respectivement aux trois phases de l’analyse d’un document : création du parseur, ouverture du fichier et . précédemment. Chaque classe fournit, sous forme de méthodes, des fonctionnalités de création du parseur, de gestion des erreurs, de défi- nition des déclencheurs, de lancement de l’analyse, etc inconvénients évidents en termes de maintenance et de modification qui en découlent. Il est donc souhaitable de recourir à la spécialisation objet qui permet de « factoriser » les fonctions génériques et de. d’associer un parseur XML et un objet, avec deux effets liés : • les « déclencheurs » seront pris parmi les méthodes de l’objet (ou, plus préci- sément, de la classe dont l’objet est une instance) ; • ces