Au fil des flows

18nov/120

Parser des fichiers Xml de grande taille avec SAX

Dans certains contextes, vous pourriez à avoir à analyser des fichiers xml très volumineux. Un parser de type DOM est inefficace dans ce cas.

L'empreinte mémoire du fichier chargé peut dans des cas limites dépasser la capacité mémoire du serveur.

Dans ce cas, vous pouvez utiliser un autre type de parser. Ce mode d'analyse est décrit par l'acronyme SAX (Simple API for XML). L'idée derrière est de revenir aux fondamentaux. Il s'agit de voir le fichier xml comme un flux.

Vous envoyez ce flux vers le parser SAX. Il l'analyse et émet des événements quand il repère des motifs connus.

Il existe aussi une dernière forme de parser qui présente de nombreux avantages, les parsers de type PULL. J'en parle dans la conclusion.

Du code xml sous la forme de flux ?

Pour que l'analyse d'un flux soit efficace, vous devez en extraire des trames simples. Chaque trame est vu comme une entité indépendante. C'est le rôle du programmeur de faire en sorte que le programme gère les trames qu'il reçoit et les traite de façon cohérente.

Comment découper ce flux ?

  • Balise ouvrante
  • Balise fermante
  • Contenu

Même si d'autres événements existent, ces 3 là forment le squelette d'un parser SAX.

Implementation en utilisant PHP et le package Xml Parser

Pour tester cette approche, j'ai choisi d'utiliser l'implémentation de PHP, Xml Parser.

Partons de ce fichier xml représentant une liste de comptes en banque :

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <comptes>
        <compte type="CCourant" nom="Compte courant">324.00</compte>
        <compte type="LBleu" nom="Livret Bleu">14540.00</compte>
        <compte type="PEA" nom="Compte PEA">15000.00</compte>
    </comptes>
</root>

Voici la classe que nous utiliserons pour convertir ce fichier xml en une liste d'éléments structurée par leur identation.

<?php

/**
 * Classe parsant un fichier xml et affichant les informations sous la forme
 * d'une hierarchie de texte
 */
class XmlParserExample1 {
    private $path;
    private $result;
    private $depth;
    
    public function __construct($path)
    {
        $this -> path = $path;
        $this -> depth = 0;
    }
    
    public function getResult() {
        return $this->result;
    }
    
    /**
     * Parse le fichier et met le resultat dans Result
     */
    public function parse()
    {
        ob_start();
        $xml_parser = xml_parser_create();
        xml_set_object($xml_parser, $this);
        xml_set_element_handler($xml_parser, "startElement", "endElement");
        xml_set_character_data_handler($xml_parser, 'characterData');
        if (!($fp = fopen($this -> path, "r"))) {
            die("could not open XML input");
        }

        while ($data = fread($fp, 4096)) {
            if (!xml_parse($xml_parser, $data, feof($fp))) {
                die(sprintf("XML error: %s at line %d",
                            xml_error_string(xml_get_error_code($xml_parser)),
                            xml_get_current_line_number($xml_parser)));
            }
        }
        
        $this -> result = ob_get_contents();
        ob_end_clean();
        fclose($fp);
        xml_parser_free($xml_parser);
    }
    
    private function startElement($parser, $name, $attrs) 
    {
        for ($i = 0; $i < $this -> depth; $i++) {
            echo "  ";
        }
        echo "$name\n";
        $this -> depth++;
        foreach($attrs as $attribute => $text)
        {
            $this ->displayAttribute($attribute, $text);
        }
    }
    
    private function displayAttribute($attribute, $text)
    {
        for ($i = 0; $i < $this -> depth; $i++) {
            echo "  ";
        }
        
        echo "A - $attribute = $text\n";
    }

    private function endElement($parser, $name) 
    {
        $this -> depth--;
    }
    
    private function characterData($parser, $data)
    {
        $data = trim($data);
        
        if (strlen($data) > 0)
        {
            for ($i = 0; $i < $this -> depth; $i++) {
                echo "  ";
            }

            echo 'T :'.$data."\n";
        }
    } 
}
?>

Les handlers sont représentés par les 3 méthodes suivantes :

  • startElement
  • endElement
  • characterData

On les enregistre dans le code à partir de la ligne 30 :

xml_set_object($xml_parser, $this);
xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, 'characterData');

Voici le programme principal :

<?php

include ('XmlParserExample1.php');
        
$parser = new XmlParserExample1(dirname(__FILE__).'/example1.xml');
$parser ->parse();
$result = $parser ->getResult();
echo $result;

?>

Il affiche le résultat suivant :

ROOT
  COMPTES
    COMPTE
      A - TYPE = CCourant
      A - NOM = Compte courant
      T :324.00
    COMPTE
      A - TYPE = LBleu
      A - NOM = Livret Bleu
      T :14540.00
    COMPTE
      A - TYPE = PEA
      A - NOM = Compte PEA
      T :15000.00

Pour conclure

Il existe une autre approche pour traiter les fichiers volumineux.

Un parser de type SAX pousse les événements vers le code (type Push). Ils présentent l'inconvénient d'avoir une machine à état assez complexe dans les handlers pour traiter les différents types de balise.

Des parsers comme XmlReader sont plus facile à manipuler. Ce sont des parsers dits de type Pull.

Aller plus loin

Posted by Fabien Arcellier

Remplis sous: Non classé Laisser un commentaire
Commentaires (0) Trackbacks (0)

Aucun commentaire pour l'instant


Leave a comment

Aucun trackbacks pour l'instant