Gute Tageszeit!
Der Umfang des XML-Formats ist ziemlich umfangreich. Neben CSV, JSON und anderen ist XML eine der häufigsten Methoden, um Daten für den Austausch zwischen verschiedenen Diensten, Programmen und Sites darzustellen. Als Beispiel können wir das CommerceML-Format für den Austausch von Waren und Bestellungen zwischen 1C "Trade Management" und einem Online-Shop anführen.
Daher muss sich fast jeder, der von Zeit zu Zeit Webdienste erstellt, mit der Notwendigkeit befassen, XML-Dokumente zu analysieren. In meinem Beitrag schlage ich eine der Methoden vor, wie dies mit XMLReader so klar und transparent wie möglich gemacht werden kann.
PHP bietet verschiedene Möglichkeiten, um mit dem XML-Format zu arbeiten. Ohne auf Details einzugehen, möchte ich sagen, dass sie im Prinzip in zwei Gruppen unterteilt werden können:
- Laden des gesamten XML-Dokuments als Objekt in den Speicher und Arbeiten mit diesem Objekt
- Schrittweises Lesen einer XML-Zeichenfolge auf der Ebene von Tags, Attributen und Textinhalten
Der erste Weg ist intuitiver, der Code sieht transparenter aus. Diese Methode eignet sich gut für kleine Dateien.
Die zweite Methode ist ein Ansatz auf niedrigerer Ebene, der uns eine Reihe von Vorteilen bietet und gleichzeitig das Leben etwas überschattet. Lassen Sie uns näher darauf eingehen. Vorteile:
- Analysegeschwindigkeit. Sie können hier ausführlicher lesen.
- Verbrauch von weniger RAM. Wir speichern nicht alle Daten in Form eines Objekts, das im Speicher sehr teuer ist.
Aber: Wir opfern die Lesbarkeit des Codes. Wenn das Ziel unserer Analyse beispielsweise darin besteht, die Summe der Werte an bestimmten Stellen in XML mit einer einfachen Struktur zu berechnen, gibt es keine Probleme.
Wenn die Dateistruktur jedoch komplex ist, hängt selbst das Arbeiten mit Daten vom vollständigen Pfad zu diesen Daten ab, und das Ergebnis sollte viele Parameter enthalten. Dann kommen wir hier zu einem ziemlich chaotischen Code.
Also schrieb ich eine Klasse, die mir später das Leben leichter machte. Seine Verwendung vereinfacht das Schreiben von Regeln und verbessert die Lesbarkeit von Programmen erheblich, ihre Größe wird um ein Vielfaches kleiner und der Code ist schöner.
Die Hauptidee ist folgende: Wir werden sowohl das Schema unseres XML als auch die Arbeitsweise in einem einzigen Array speichern, das die Hierarchie nur der benötigten Tags wiederholt. Außerdem können wir für jedes der Tags im selben Array die Funktionen registrieren, die wir benötigen, um ein Tag zu öffnen, zu schließen, Attribute zu lesen oder Text zu lesen oder alles zusammen. Daher speichern wir unsere XML-Struktur und Handler an einem Ort. Ein einziger Blick auf unsere Verarbeitungsstruktur reicht aus, um zu verstehen, was wir mit unserer XML-Datei machen. Ich werde reservieren, dass bei einfachen Aufgaben (wie in den folgenden Beispielen) der Vorteil der Lesbarkeit gering ist, aber es wird offensichtlich, wenn Sie mit Dateien mit einer relativ komplexen Struktur arbeiten - zum Beispiel einem Austauschformat mit 1C.
Nun die Einzelheiten. Hier ist unsere Klasse:
Debug-Version (mit $ debug-Parameter):
XMLReaderStruct-Klasse - Zum Vergrößern anklickenclass 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) {
Release-Version (ohne den $ debug-Parameter und Kommentare):
XMLReaderStruct-Klasse - Zum Vergrößern anklicken 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; } }
Wie Sie sehen können, erweitert unsere Klasse die Funktionen der Standard-XMLReader-Klasse, zu der wir eine Methode hinzugefügt haben:
xmlStruct($xml, $structure, $encoding = null, $options = 0, $debug = false)
Parameter:
- $ xml, $ encoding, $ options : wie in XMLReader :: xml ()
- $ struct : Ein assoziatives Array, das vollständig beschreibt, wie wir mit unserer Datei arbeiten sollen. Es versteht sich, dass sein Aussehen im Voraus bekannt ist und wir genau wissen, welche Tags und was wir tun sollen.
- $ debug : (nur für die Debug-Version) Gibt an, ob Debugging-Informationen ausgegeben werden sollen (standardmäßig deaktiviert).
Das Argument
$ structure .
Dies ist ein assoziatives Array, dessen Struktur die Hierarchie der Tags der XML-Datei wiederholt. Falls erforderlich, kann jedes der Strukturelemente Handlerfunktionen haben (definiert als Felder mit dem entsprechenden Schlüssel):
- "__open" - Funktion beim Öffnen eines Tags - function ()
- "__attrs" - Funktion zum Verarbeiten von Tag-Attributen (falls vorhanden) - Funktion ($ assocArray)
- "__text" - Funktion bei Vorhandensein des Textwerts der Tag - Funktion ($ text)
- "__close" - Funktion beim Schließen einer Tag - Funktion ()
Wenn einer der Handler false zurückgibt, wird die Analyse abgebrochen und die Funktion xmlStruct () gibt false zurück. Die folgenden Beispiele zeigen, wie das Argument $ structure erstellt wird:
Beispiel 1 zeigt die Reihenfolge, in der Handler aufgerufen werdenAngenommen, es gibt eine XML-Datei:
<?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);
Handler werden aufgerufen (in chronologischer Reihenfolge):
Wurzelattribute-> a
Textfeld root-> a
Wurzel öffnen-> b
Öffnungswurzel-> b-> x
Text root-> b-> x
Wurzel schließen-> b-> x
Wurzel schließen-> b
Die verbleibenden Felder werden nicht verarbeitet (einschließlich root-> d-> x wird ignoriert, da sie sich außerhalb der Struktur befinden).
Beispiel 2 zeigt eine einfache praktische AufgabeAngenommen, es gibt eine XML-Datei:
<?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>
Dies ist ein Bankscheck mit Waren und Dienstleistungen.
Jeder Prüfdatensatz enthält die Datensatzkennung, den Typ (Produkt „Produkt“ oder Dienstleistung „Dienstleistung“), den Namen, die Menge und den Preis.
Aufgabe: Berechnen Sie den Scheckbetrag, jedoch getrennt für Waren und Dienstleistungen.
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;