Bon moment de la journée!
La portée du format XML est assez étendue. Avec CSV, JSON et autres, XML est l'un des moyens les plus courants de présenter des données pour l'échange entre différents services, programmes et sites. A titre d'exemple, on peut citer le format CommerceML pour l'échange de marchandises et de commandes entre 1C "Trade Management" et une boutique en ligne.
Par conséquent, presque tous ceux qui créent de temps à autre des services Web doivent faire face à la nécessité d'analyser des documents XML. Dans mon article, je propose l'une des méthodes pour le faire de la manière la plus claire et transparente possible à l'aide de XMLReader.
PHP propose plusieurs façons de travailler avec le format XML. Sans entrer dans les détails, je dirai qu'en principe ils peuvent être divisés en deux groupes:
- Chargement de l'intégralité du document XML en mémoire en tant qu'objet et utilisation de cet objet
- Lecture pas à pas d'une chaîne XML au niveau des balises, des attributs et du contenu textuel
La première façon est plus intuitive, le code semble plus transparent. Cette méthode fonctionne bien pour les petits fichiers.
La deuxième méthode est une approche de niveau inférieur, qui nous donne un certain nombre d'avantages et, en même temps, éclipse quelque peu la vie. Arrêtons-nous dessus plus en détail. Avantages:
- Analyse de la vitesse. Vous pouvez lire plus en détail ici .
- Consommation de moins de RAM. Nous ne stockons pas toutes les données sous la forme d'un objet très cher en mémoire.
Mais: nous sacrifions la lisibilité du code. Si le but de notre analyse est, par exemple, de calculer la somme des valeurs à certains endroits dans XML avec une structure simple, alors il n'y a aucun problème.
Cependant, si la structure du fichier est complexe, même travailler avec des données dépend du chemin d'accès complet à ces données, et le résultat doit inclure de nombreux paramètres, alors nous arrivons à un code plutôt chaotique.
J'ai donc écrit un cours qui m'a par la suite facilité la vie. Son utilisation simplifie l'écriture des règles et améliore considérablement la lisibilité des programmes, leur taille devient beaucoup plus petite et le code est plus beau.
L'idée principale est la suivante: nous allons stocker à la fois le schéma de notre XML et comment travailler avec lui dans un seul tableau qui répète la hiérarchie des seules balises dont nous avons besoin. De plus, pour n'importe laquelle des balises du même tableau, nous serons en mesure d'enregistrer les fonctions dont nous avons besoin, qui sont des gestionnaires pour ouvrir une balise, la fermer, lire des attributs ou lire du texte, ou tous ensemble. Ainsi, nous stockons notre structure XML et nos gestionnaires en un seul endroit. Un simple coup d'œil sur notre structure de traitement sera suffisant pour comprendre ce que nous faisons avec notre fichier XML. Je ferai une réserve que sur des tâches simples (comme dans les exemples ci-dessous), l'avantage en termes de lisibilité est faible, mais il sera évident lorsque vous travaillez avec des fichiers d'une structure relativement complexe - par exemple, un format d'échange avec 1C.
Maintenant, les détails. Voici notre classe:
Version de débogage (avec le paramètre $ debug):
Classe XMLReaderStruct - cliquez pour développerclass XMLReaderStruct extends XMLReader { public function xmlStruct($xml, $structure, $encoding = null, $options = 0, $debug = false) { $this->xml($xml, $encoding, $options); $stack = array(); $node = &$structure; $skipToDepth = false; while ($this->read()) { switch ($this->nodeType) { case self::ELEMENT: if ($skipToDepth === false) {
Version de sortie (sans le paramètre $ debug et les commentaires):
Classe XMLReaderStruct - cliquez pour développer class XMLReaderStruct extends XMLReader { public function xmlStruct($xml, $structure, $encoding = null, $options = 0) { $this->xml($xml, $encoding, $options); $stack = array(); $node = &$structure; $skipToDepth = false; while ($this->read()) { switch ($this->nodeType) { case self::ELEMENT: if ($skipToDepth === false) { if (isset($node[$this->name])) { $stack[$this->depth] = &$node; $node = &$node[$this->name]; if (isset($node["__open"]) && (false === $node["__open"]())) return false; if (isset($node["__attrs"])) { $attrs = array(); if ($this->hasAttributes) while ($this->moveToNextAttribute()) $attrs[$this->name] = $this->value; if (false === $node["__attrs"]($attrs)) return false; } if ($this->isEmptyElement) { if (isset($node["__close"]) && (false === $node["__close"]())) return false; $node = &$stack[$this->depth]; } } else { $skipToDepth = $this->depth; } } break; case self::TEXT: if ($skipToDepth === false) { if (isset($node["__text"]) && (false === $node["__text"]($this->value))) return false; } break; case self::END_ELEMENT: if ($skipToDepth === false) { if (isset($node["__close"]) && (false === $node["__close"]())) return false; $node = &$stack[$this->depth]; } elseif ($this->depth === $skipToDepth) { $skipToDepth = false; } break; } } return true; } }
Comme vous pouvez le voir, notre classe étend les capacités de la classe XMLReader standard, à laquelle nous avons ajouté une méthode:
xmlStruct($xml, $structure, $encoding = null, $options = 0, $debug = false)
Paramètres:
- $ xml, $ encoding, $ options : comme dans XMLReader :: xml ()
- $ structure : un tableau associatif qui décrit complètement comment nous devons travailler avec notre fichier. Il est entendu que son apparence est connue à l'avance, et nous savons exactement quelles balises et ce que nous devons faire.
- $ debug : (uniquement pour la version Debug) s'il faut afficher les informations de débogage (par défaut - off).
L'argument
$ structure .
Il s'agit d'un tableau associatif, dont la structure répète la hiérarchie des balises dans le fichier XML, plus, si nécessaire, chacun des éléments de structure peut avoir des fonctions de gestionnaire (définies comme des champs avec la clé correspondante):
- "__open" - fonction lors de l'ouverture d'une balise - fonction ()
- "__attrs" - fonction de traitement des attributs de balise (le cas échéant) - fonction ($ assocArray)
- "__text" - fonction en présence de la valeur de texte de la balise - fonction ($ text)
- "__close" - fonction lors de la fermeture d'une balise - fonction ()
Si l'un des gestionnaires renvoie false, l'analyse sera abandonnée et la fonction xmlStruct () renverra false. Les exemples suivants montrent comment construire l'argument $ structure:
Exemple 1 montrant l'ordre dans lequel les gestionnaires sont appelésSupposons qu'il existe un fichier XML:
<?xml version="1.0" encoding="UTF-8"?> <root> <a attr_1="123" attr_2="456">Abc</a> <b> <x>This is node x inside b</x> </b> <c></c> <d> <x>This is node x inside d</x> </d> <e></e> </root>
$structure = array( 'root' => array( 'a' => array( "__attrs" => function($array) { echo "ATTR ARRAY IS ",json_encode($array),"\r\n"; }, "__text" => function($text) use (&$a) { echo "TEXT a {$text}\r\n"; } ), 'b' => array( "__open" => function() { echo "OPEN b\r\n"; }, "__close" => function() { echo "CLOSE b\r\n"; }, 'x' => array( "__open" => function() { echo "OPEN x\r\n"; }, "__text" => function($text) { echo "TEXT x {$text}\r\n"; }, "__close" => function() { echo "CLOSE x\r\n"; } ) ) ) ); $xmlReaderStruct->xmlStruct($xml, $structure);
Les gestionnaires seront appelés (par ordre chronologique):
attributs racine-> a
racine du champ de texte-> a
racine d'ouverture-> b
racine d'ouverture-> b-> x
racine du texte-> b-> x
racine de fermeture-> b-> x
racine de fermeture-> b
Les champs restants ne seront pas traités (y compris root-> d-> x seront ignorés, car il est en dehors de la structure)
Exemple 2 illustrant une tâche pratique simpleSupposons qu'il existe un fichier XML:
<?xml version="1.0" encoding="UTF-8"?> <shop> <record> <id>0</id> <type>product</type> <name>Some product name. ID:0</name> <qty>0</qty> <price>0</price> </record> <record> <id>1</id> <type>service</type> <name>Some product name. ID:1</name> <qty>1</qty> <price>15</price> </record> <record> <id>2</id> <type>product</type> <name>Some product name. ID:2</name> <qty>2</qty> <price>30</price> </record> <record> <id>3</id> <type>service</type> <name>Some product name. ID:3</name> <qty>3</qty> <price>45</price> </record> <record> <id>4</id> <type>product</type> <name>Some product name. ID:4</name> <qty>4</qty> <price>60</price> </record> <record> <id>5</id> <type>service</type> <name>Some product name. ID:5</name> <qty>5</qty> <price>75</price> </record> </shop>
Il s'agit d'un chèque de banque contenant des biens et services.
Chaque enregistrement de contrôle contient l'identifiant de l'enregistrement, le type (produit «produit» ou service «service»), le nom, la quantité et le prix.
Tâche: calculer le montant du chèque, mais séparément pour les biens et services.
include_once "xmlreaderstruct.class.php"; $x = new XMLReaderStruct(); $productsSum = 0; $servicesSum = 0; $structure = array( 'shop' => array( 'record' => array( 'type' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['isService'] = $text === 'service'; } ), 'qty' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['qty'] = (int)$text; } ), 'price' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['price'] = (int)$text; } ), '__open' => function() use (&$currentRecord) { $currentRecord = array(); }, '__close' => function() use (&$currentRecord, &$productsSum, &$servicesSum) { $money = $currentRecord['qty'] * $currentRecord['price']; if ($currentRecord['isService']) $servicesSum += $money; else $productsSum += $money; } ) ) ); $x->xmlStruct(file_get_contents('example.xml'), $structure); echo 'Overal products price: ', $productsSum, ', Overal services price: ', $servicesSum;