PHP XML Expat
Der PHP Expat XML-Parser: ereignisbasiertes Streaming-Parsen mit Handler-Callbacks, Attributzugriff, Fehlerprüfung und Beispielen.
PHP wird mit einem eingebauten XML-Parser ausgeliefert, der auf der Expat-Bibliothek basiert. Im Gegensatz zu baumbasierten Parsern wie SimpleXML oder dem DOM ist Expat ein ereignisbasierter (oder „SAX-artiger") Parser: Anstatt das gesamte Dokument als Baum in den Speicher zu laden, streamt er den XML-Inhalt und ruft Ihre Callback-Funktionen auf, wenn er einen Start-Tag, End-Tag oder ein Textstück findet.
Diese Seite erklärt, wie Expat funktioniert, wann Sie es einsetzen sollten, und führt durch ein vollständiges, ausführbares Beispiel mit Attributverarbeitung und Fehlerprüfung.
Was ist der Expat-Parser?
Expat ist ein schneller, schlanker, nicht validierender XML-Parser, der in C geschrieben ist. Die PHP-Erweiterung xml kapselt ihn über die Funktionsfamilie xml_parser_*. Seine charakteristischen Eigenschaften:
- Ereignisbasiert — Sie registrieren Handler-Callbacks; der Parser ruft diese beim Lesen auf.
- Streaming — XML kann stückweise übergeben werden (
xml_parse()kann mehrfach aufgerufen werden), sodass der Speicherverbrauch auch bei großen Dateien gering bleibt. - Nicht validierend — Es wird geprüft, ob das Dokument wohlgeformt ist, aber es erfolgt keine Validierung gegen ein DTD oder Schema.
Dies ist der entgegengesetzte Kompromiss gegenüber einem Baum-Parser. Ein Baum-Parser ist komfortabel (Sie können das gesamte Dokument mit $xml->note->message navigieren), hält aber alles im Speicher. Expat hält den Speicherverbrauch konstant, verlangt aber von Ihnen, den Zustand beim Durchlaufen der Ereignisse selbst zu verwalten.
Wann sollten Sie Expat verwenden?
- Große Dokumente — mehrere Megabyte große Feeds oder Exporte, bei denen das Aufbauen eines vollständigen DOM-Baums verschwenderisch wäre.
- Streaming-Quellen — Daten, die über einen Socket oder in Stücken eintreffen und auf die Sie nicht warten können, bis das gesamte Dokument vorliegt.
- Extrahieren und verwerfen — Sie benötigen nur wenige Werte und möchten den Overhead eines Baums vermeiden.
Für kleine Dokumente, die Sie einfach lesen möchten, ist SimpleXML mit deutlich weniger Code verbunden. Einen direkten Vergleich finden Sie unter Typen von PHP XML-Parsern.
Einrichten der Erweiterung
Die Erweiterung xml ist mit PHP gebündelt und in den meisten Builds standardmäßig aktiviert. Falls Parser-Funktionen fehlen, aktivieren Sie sie in der php.ini:
extension=xml ; Linux/macOS
extension=php_xml.dll ; WindowsSie können die Verfügbarkeit zur Laufzeit prüfen:
<?php
var_dump(function_exists('xml_parser_create')); // bool(true)Ein vollständiges Expat-Beispiel
Das folgende Beispiel parst einen XML-String und gibt jedes Ereignis aus. Es registriert drei Handler — für Start-Tags, End-Tags und Text — und schließt mit einer ordentlichen Fehlerprüfung ab. Das Lesen aus einem String hält es in sich geschlossen; die dateibasierte Variante folgt danach.
<?php
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<note importance="high">
<to>User</to>
<from>System</from>
<message>Hello from Expat</message>
</note>
XML;
// 1. Create the parser.
$parser = xml_parser_create();
// Keep tag names in their original case (Expat upper-cases them by default).
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
// 2. Register handlers for start/end tags and for character data.
xml_set_element_handler(
$parser,
function ($parser, $name, $attrs) {
echo "Start: $name";
foreach ($attrs as $key => $value) {
echo " [$key=$value]";
}
echo "\n";
},
function ($parser, $name) {
echo "End: $name\n";
}
);
xml_set_character_data_handler($parser, function ($parser, $data) {
$data = trim($data); // text between tags includes whitespace
if ($data !== '') {
echo "Text: $data\n";
}
});
// 3. Feed the document to the parser (true = this is the final chunk).
if (!xml_parse($parser, $xml, true)) {
$code = xml_get_error_code($parser);
die(sprintf(
"XML error: %s at line %d\n",
xml_error_string($code),
xml_get_current_line_number($parser)
));
}
// 4. Release the parser.
xml_parser_free($parser);Ausgabe:
Start: note [importance=high]
Start: to
Text: User
End: to
Start: from
Text: System
End: from
Start: message
Text: Hello from Expat
End: message
End: noteWie es funktioniert. xml_parser_create() erstellt den Parser. xml_set_element_handler() (Referenz) registriert die Start- und End-Tag-Callbacks, und xml_set_character_data_handler() (Referenz) registriert den Text-Callback. xml_parse() treibt dann den gesamten Vorgang an und ruft diese Funktionen in der Dokumentreihenfolge auf. Der Start-Handler erhält ein $attrs-Array, sodass das Lesen eines Attributs wie importance einfach $attrs['importance'] ist.
Zwei Details, über die man leicht stolpert:
- Leerzeichen sind Zeichendaten. Die Zeilenumbrüche und Einrückungen zwischen Tags lösen ebenfalls den Zeichendaten-Handler aus — deshalb ruft das Beispiel
trim()auf und überspringt leere Zeichenketten. - Case Folding. Standardmäßig wandelt Expat Elementnamen in Großbuchstaben um.
XML_OPTION_CASE_FOLDINGauffalsegesetzt behält die ursprüngliche Groß-/Kleinschreibung bei, was fast immer das Gewünschte ist.
Eine Datei in Stücken parsen
Die eigentliche Stärke von Expat ist das Streaming. Lesen Sie die Datei blockweise und übergeben Sie jeden Block an xml_parse(), wobei Sie true nur beim letzten Stück übergeben (erkannt mit feof()):
<?php
$parser = xml_parser_create();
xml_set_element_handler($parser, 'startElement', 'endElement');
xml_set_character_data_handler($parser, 'characterData');
$fp = fopen('example.xml', 'r') or die("Could not open file\n");
while ($data = fread($fp, 4096)) {
if (!xml_parse($parser, $data, feof($fp))) {
$code = xml_get_error_code($parser);
die(sprintf(
"XML error: %s at line %d\n",
xml_error_string($code),
xml_get_current_line_number($parser)
));
}
}
fclose($fp);
xml_parser_free($parser);
function startElement($parser, $name, $attrs) { echo "Start: $name\n"; }
function endElement($parser, $name) { echo "End: $name\n"; }
function characterData($parser, $data) {
$data = trim($data);
if ($data !== '') echo "Text: $data\n";
}Da das Dokument nie vollständig im Speicher gehalten wird, kann dieses Muster Dateien verarbeiten, die weit größer als Ihr Speicherlimit sind. Handler können einfache Funktionsnamen (wie hier), Closures oder [$object, 'method']-Callables über xml_set_object() sein.
Fehlerbehandlung
Prüfen Sie immer den Rückgabewert von xml_parse() — bei einem fehlerhaften Dokument gibt er false zurück. Der Fehlercode von xml_get_error_code() kann mit xml_error_string() in eine lesbare Meldung umgewandelt werden, und Sie können den Fehlerort mit xml_get_current_line_number() und xml_get_current_column_number() genau bestimmen. Diese Prüfung zu überspringen bedeutet, dass ein fehlerhafter Feed lautlos scheitert.
Vorteile von Expat
- Geringer Speicherverbrauch — streamt das Dokument, anstatt einen Baum aufzubauen, sodass der Speicher unabhängig von der Dateigröße konstant bleibt.
- Schnell — der zugrunde liegende C-Parser ist hochoptimiert.
- Plattformübergreifend — mit PHP auf jedem unterstützten Betriebssystem gebündelt.
- Feinkörnige Kontrolle — Sie entscheiden bei jedem Ereignis genau, was zu tun ist, und ignorieren alles, was Sie nicht benötigen.
Der Kompromiss: Da es keinen Baum gibt, müssen Sie den Kontext (in welchem Element Sie sich befinden) selbst verfolgen. Wenn diese Buchhaltung aufwendig wird, ist ein Baum-Parser wie SimpleXML oder DOM die bessere Wahl.
Fazit
Der Expat-basierte XML-Parser gibt PHP eine schnelle, speichereffiziente, ereignisgesteuerte Methode zum Lesen von XML. Registrieren Sie Handler für die Ereignisse, die Sie interessieren, übergeben Sie das Dokument mit xml_parse(), prüfen Sie auf Fehler und geben Sie den Parser nach der Arbeit frei. Greifen Sie darauf bei großen oder Streaming-Dokumenten zurück; für kleine Dokumente ist SimpleXML in der Regel die einfachere Wahl.