Skip to content

21. Traitement de documents XML

Les services web que nous avons écrits jusqu'ici échangent des données JSON. Le format XML (eXtensible Markup Language) a longtemps été le format d'échange le plus répandu, et on le rencontre encore souvent : services web SOAP, flux RSS, fichiers de configuration, documents bureautiques (un fichier [.odt] ou [.docx] est une archive de fichiers XML). PHP offre deux API pour l'exploiter :

  • [SimpleXML] : une API très simple, où les éléments du document deviennent des propriétés d'objets ;
  • [DOM] (Document Object Model) : l'API standard du W3C, plus complète, qui permet aussi de modifier un document. PHP 8.4 en a introduit une nouvelle version, dans l'espace de noms [Dom], conforme au standard actuel du web .

Les scripts de ce chapitre sont dans le dossier [scripts-console/xml] :

1
2
3
4
scripts-console/xml
├── data.xml
├── dom-01.php
└── simplexml-01.php

21.1. Le document étudié

Nous considérons le fichier XML [data.xml] suivant :

<?xml version="1.0" encoding="UTF-8"?>

<tribu>
    <enseignant>
        <personne sexe="M">
            <nom>dupont</nom>
            <prenom>jean</prenom>
            <age>28</age>
            ceci est un commentaire
        </personne>
        <section>27</section>
    </enseignant>
    <etudiant>
        <personne sexe="F">
            <nom>martin</nom>
            <prenom>charline</prenom>
            <age>22</age>
        </personne>
        <formation>dess IAIE</formation>
    </etudiant>
</tribu>

Un document XML est un arbre d'éléments :

  • ligne 1 : le prologue, qui donne la version de XML et le codage des caractères du document ;
  • lignes 3-21 : l'élément racine [tribu]. Un document XML a une racine et une seule ;
  • lignes 4-12 : l'élément [enseignant], fils de [tribu]. Il a deux fils, [personne] et [section] ;
  • ligne 5 : l'élément [personne] a un attribut [sexe] de valeur [M] ;
  • ligne 9 : un élément peut contenir à la fois des éléments et du texte ;
  • lignes 13-20 : l'élément [etudiant], de même structure que [enseignant] .

21.2. Exploitation avec SimpleXML

Nous analysons ce document avec le script [simplexml-01.php] suivant :

<?php

declare(strict_types=1);

// le fichier XML à exploiter
const FILE_NAME = "data.xml";

// chargement du document XML
$xml = simplexml_load_file(FILE_NAME);
if ($xml === false) {
    print "Le fichier [" . FILE_NAME . "] n'a pu être exploité\n";
    exit(1);
}
// affichage du document
print_r($xml);
// l'attribut [sexe] de la personne enseignante
print_r($xml->enseignant->personne['sexe']);
// valeurs d'éléments et d'attributs, converties en chaînes
$nom = (string) $xml->enseignant->personne->nom;
print "nom=$nom\n";
$sexe = (string) $xml->enseignant->personne['sexe'];
print "sexe=$sexe\n";
$formation = (string) $xml->etudiant->formation;
print "formation=$formation\n";
// existence d'un élément
print "isset(nom)=" . var_export(isset($xml->enseignant->personne->nom), true) . "\n";
print "isset(xx)=" . var_export(isset($xml->enseignant->personne->xx), true) . "\n";
// une conversion en entier
$age = (int) $xml->etudiant->personne->age;
print "âge de l'étudiante=$age\n";
// parcours de tous les éléments <personne> du document avec XPath
foreach ($xml->xpath('//personne') as $personne) {
    print "personne : {$personne->prenom} {$personne->nom} ({$personne['sexe']})\n";
}

Commentaires

  • ligne 6 : le nom du fichier XML est une constante. Le script est exécuté dans son dossier, celui de [data.xml] ;
  • ligne 9 : la fonction [simplexml_load_file] charge le document et rend un objet de type [SimpleXMLElement] qui représente l'élément racine [tribu]. En cas d'erreur (fichier absent, XML mal formé), elle rend [false], ce que nous testons ligne 10 ;
  • ligne 15 : affichage du document. [print_r] montre comment [SimpleXML] voit l'arbre XML ;
  • ligne 17 : les éléments fils sont atteints comme des propriétés ([$xml->enseignant->personne]), les attributs comme des clés de tableau ([['sexe']]). On notera que la racine [tribu] n'apparaît pas dans le code : [$xml] est déjà cet élément ;
  • ligne 19 : [$xml->enseignant->personne->nom] n'est pas une chaîne mais un objet [SimpleXMLElement]. La conversion [(string)] donne son texte. Avec [declare(strict_types=1)], il vaut mieux faire cette conversion explicitement avant de passer la valeur à une fonction qui attend une chaîne ;
  • ligne 21 : la valeur de l'attribut [sexe] ;
  • lignes 26-27 : [isset] permet de savoir si un élément existe. Le script PHP 7 affichait le résultat booléen avec [print], ce qui donne [1] pour [true] et une chaîne vide pour [false]. Nous utilisons [var_export], qui affiche [true] ou [false] ;
  • ligne 29 : la conversion [(int)] donne la valeur entière du texte de l'élément ;
  • ligne 32 : la méthode [xpath] évalue une expression XPath. L'expression [//personne] désigne tous les éléments [personne] du document, à quelque niveau qu'ils soient. On obtient un tableau de [SimpleXMLElement] .

Résultats console

> php simplexml-01.php
SimpleXMLElement Object
(
    [enseignant] => SimpleXMLElement Object
        (
            [personne] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [sexe] => M
                        )

                    [nom] => dupont
                    [prenom] => jean
                    [age] => 28
                )

            [section] => 27
        )

    [etudiant] => SimpleXMLElement Object
        (
            [personne] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [sexe] => F
                        )

                    [nom] => martin
                    [prenom] => charline
                    [age] => 22
                )

            [formation] => dess IAIE
        )

)
SimpleXMLElement Object
(
    [0] => M
)
nom=dupont
sexe=M
formation=dess IAIE
isset(nom)=true
isset(xx)=false
âge de l'étudiante=22
personne : jean dupont (M)
personne : charline martin (F)
  • lignes 2-38 : le document XML sous la forme d'un objet de type [SimpleXMLElement]. On remarque que le texte [ceci est un commentaire] n'apparaît pas : [print_r] ne montre que les éléments et les attributs ;
  • lignes 39-42 : un attribut est lui aussi un objet [SimpleXMLElement] ;
  • lignes 43-48 : les valeurs lues dans le document ;
  • lignes 49-50 : le parcours XPath des deux personnes .

21.3. Exploitation avec la nouvelle API DOM de PHP 8.4

Depuis PHP 8.4, l'espace de noms [Dom] propose une nouvelle API DOM : la classe [Dom\XMLDocument] pour les documents XML et [Dom\HTMLDocument] pour les documents HTML5. Elle remplace avantageusement l'ancienne classe [DOMDocument] (toujours disponible) : elle est conforme au standard DOM actuel et offre les méthodes [querySelector] et [querySelectorAll] que connaissent les développeurs JavaScript. Elles permettent de chercher des éléments avec des sélecteurs CSS.

Le script [dom-01.php] exploite le même document :

<?php

declare(strict_types=1);

// le même document exploité avec la nouvelle API DOM de PHP 8.4

use Dom\XMLDocument;

// le fichier XML à exploiter
const FILE_NAME = "data.xml";

try {
    // chargement du document XML
    $document = XMLDocument::createFromFile(FILE_NAME);
} catch (Throwable $ex) {
    print "Le fichier [" . FILE_NAME . "] n'a pu être exploité : {$ex->getMessage()}\n";
    exit(1);
}
// l'élément racine
print "racine=" . $document->documentElement->tagName . "\n";
// la 1re personne enseignante, trouvée avec un sélecteur CSS
$personne = $document->querySelector('enseignant > personne');
print "nom=" . $personne->querySelector('nom')->textContent . "\n";
print "sexe=" . $personne->getAttribute('sexe') . "\n";
print "formation=" . $document->querySelector('etudiant > formation')->textContent . "\n";
// un élément absent : querySelector rend null
print "xx existe=" . var_export($personne->querySelector('xx') !== null, true) . "\n";
// toutes les personnes du document
foreach ($document->querySelectorAll('personne') as $personne) {
    // l'opérateur nullsafe ?-> protège d'un élément absent
    $prénom = $personne->querySelector('prenom')?->textContent ?? '?';
    $nom = $personne->querySelector('nom')?->textContent ?? '?';
    print "personne : $prénom $nom ({$personne->getAttribute('sexe')})\n";
}
// modification du document : on change le contenu de l'élément <section> de l'enseignant
$section = $document->querySelector('enseignant > section');
$section->textContent = "26";
// le document modifié, sous forme de texte XML
print $document->saveXml($document->querySelector('enseignant')) . "\n";

Commentaires

  • ligne 7 : la classe [XMLDocument] de l'espace de noms [Dom] ;
  • ligne 14 : la méthode statique [createFromFile] charge le document. Contrairement à [simplexml_load_file], elle lance une exception en cas d'erreur ;
  • ligne 20 : la propriété [documentElement] est l'élément racine ; [tagName] est son nom ;
  • ligne 22 : le sélecteur CSS [enseignant > personne] désigne un élément [personne] fils direct d'un élément [enseignant]. [querySelector] rend le premier élément qui convient, ou [null] s'il n'y en a pas ;
  • ligne 23 : la propriété [textContent] est le texte contenu dans l'élément ;
  • ligne 24 : la valeur d'un attribut ;
  • ligne 27 : un élément absent donne [null] ;
  • ligne 29 : tous les éléments [personne] du document ;
  • ligne 31 : si l'élément [prenom] manquait, [querySelector('prenom')] rendrait [null]. L'opérateur nullsafe [?->] (PHP 8.0) rend alors [null] au lieu de provoquer une erreur, et l'opérateur [??] remplace ce [null] par ['?'] ;
  • ligne 37 : l'API DOM permet aussi de modifier le document : ici, on change le texte de l'élément [section] ;
  • ligne 39 : [saveXml] rend le texte XML du document, ou d'un de ses éléments s'il est passé en paramètre .

Résultats console

> php dom-01.php
racine=tribu
nom=dupont
sexe=M
formation=dess IAIE
xx existe=false
personne : jean dupont (M)
personne : charline martin (F)
<enseignant>
        <personne sexe="M">
            <nom>dupont</nom>
            <prenom>jean</prenom>
            <age>28</age>
            ceci est un commentaire
        </personne>
        <section>26</section>
    </enseignant>
  • ligne 16 : la modification faite par le script .

Ces deux exemples ne montrent pas toutes les possibilités de [SimpleXML] et de [DOM], mais ils nous suffisent pour écrire une nouvelle version de l'exercice d'application. La documentation de PHP les décrit en détail : SimpleXML et DOM.