Home > PHP > Parser des XML volumineux avec XMLReader

Parser des XML volumineux avec XMLReader

Avec des classes comme DomDocument ou SimpleXML, le parsing de fichiers XML s’est considérablement simplifié en PHP 5. Mais quand on doit parser des fichiers très volumineux (comme j’ai eu à le faire récemment pour le catalogue produits de mon comparateur d’électroménager), on se retrouve très rapidement avec des scripts très gourmands en mémoire. En effet, ces classes lisent et analysent la totalité du fichier XML avant de pouvoir agir dessus. Ce n’est pas le cas de XMLReader, qui elle, fonctionne en mode flux: le fichier est alors lu au fur et à mesure des besoins. Il en résulte des scripts plus rapides et très peu consommateurs de RAM.

Utiliser XMLReader et SimpleXML de concert

Voici comment on procède:

  1. $xml = new XMLReader();
  2. $xml->open(‘gros_fichier.xml’);
  3. while($xml->read()){
  4.     if ($xml->nodeType == XMLREADER::ELEMENT && $xml->localName == "product") {
  5.         $product = $xml->expand();
  6.         $product = new SimpleXMLElement(‘<product>’.$xml->readInnerXML().‘</product>’);
  7.         $prod[‘name’] = utf8_decode($product->name);
  8.     }
  9. }

Ligne 1, on instancie classiquement un objet XMLReader. Le constructeur ne prend pas de paramètres. Ligne 2, on assigne le fichier que l’on souhaite parser.
C’est ensuite que les joyeusetés commencent : $xml->read() permet de passer au node suivant. Tant que XMLReader trouvera des nodes, on continuera à lire le fichier séquentiellement. L’intérieur de la boucle contiendra le traitement que je souhaite effectuer sur chaque node. Ici, si le nodeType est “ELEMENT” si le localName (le libellé du tag) est “product”, nous sommes en présence de l’un des produits que je souhaite traiter.
Dans ce cas, $xml->expand() permet de copier le node courant afin de le manipuler plus aisément. L’astuce ligne 6 (j’imagine bien qu’il doit y avoir un moyen de faire ça beaucoup plus proprement) consiste à recréer le node à partir de son localName et de la chaîne de caractères au format XML qu’il contient, récupérée grâce à $xml->readInnerXML(). Etant habitué à l’utilisation courante de SimpleXML, et pour des raisons de confort personnel, je passe la chaîne de caractères obtenue en paramètre au constructeur de SimpleXMLElement afin de récupérer un élement XML classique, que j’utilise ensuite simplement en accédant aux données qu’il contient.

Vérifier le bon déroulement des opérations

  1. $i++;
  2. if (($i % 500) == 0) {
  3.         echo "$i – Ok\r\n";
  4.         flush();
  5. }

Pour les imports de gros fichiers (1.2 giga-octets dans mon cas), mieux vaut savoir exactement ce qu’il se passe. J’ai donc déclaré avant mon while une variable $i=0, que j’incrémente à la fin de ma boucle, après avoir traité un produit. Ligne 2, je vérifie que $i (le nombre de produits traités) est un multiple de 500. Si oui, j’affiche le nombre courant et le flush() permet d’envoyer immédiatement le contenu du buffer à la sortie du script. Ici, le but était de faire tourner le script en ligne de commandes (CLI), mais cela aurait aussi bien marché avec un navigateur. (Il aurait fallu remplacer les \r\n par un <br /> classique.

Si cet article vous a intéressé, j’apprécierais un peu de promotion : ;)


Ce post vous a été utile ? Re-Twittez le ! ReTwittez ce post

PHP , , , , ,

  1. | #1

    C’est vrai que XMLREADER est une bonne alternative, je l’utilise également dans mes interfaces, on peu même le redéfinir à sa sauce sans probléme.
    Il permet de créer de vrai fichier sitemap, rss et j’en passe par contre je n’ai pas tenter de créer des ACL avec.

  2. | #2

    Merci pour cet excellent code, j’avais atteint les limites de simplexml.

  3. | #3

    Merci beaucoup pour cette superbe classe pour parser les XML… J’ai mis longtemps avant de comprendre que les class classiques comme simplexml surchargeaient beaucoup le serveur, à un point qu’il pouvais le planter !!! :)

    Je vais essayer tout ca :) Merci encore !

  4. | #4

    Es ce qu’il n’existe pas une fonction avec xmlreader pour récupéré le contenu d’une famille de noeuds ?

  1. No trackbacks yet.