XML Parsen mit DOMDocument: störende Whitespace-Knoten
Gestern nach Jahren mal wieder drüber gestolpert, deshalb jetzt hier schriftlich festgehalten:
Bei Nutzung von DOMDocument und der loadXML() Funktion werden standardmäßig erstmal Umbrüche und Whitespace (Leerzeichen, Tabs), die nur zu Formatierungszwecken genutzt werden, auch als Textelemente angesehen. Folgendes Beispielsnippet:
<?php $xml = <<<XML <nodes> <node>text</node> <field>hello</field> </nodes> XML; $doc = new DOMDocument(); $doc->loadXML($xml); foreach ($doc->childNodes->item(0)->childNodes as $childNode) { var_dump($childNode->nodeName.':'.$childNode->nodeValue); }
Eigentlich möchte ich nur die beiden inneren Knoten haben. Aber statt 2 Kindknoten erhalte ich 5, die wie folgt aussehen:
string(11) "#text: " string(9) "node:text" string(11) "#text: " string(11) "field:hello" string(7) "#text: "
Da sind also komische #text-Elemente, die Leerzeichen und einen Umbruch enthalten. Wenn mich die nun nicht interessieren und ich nur die „wirklichen“ XML-Kindknoten haben möchte, muss ich einfach nur eine Zeile hinzufügen:
$doc = new DOMDocument(); $doc->preserveWhiteSpace = false; $doc->loadXML($xml);
Und schon habe ich das erwartete Ergebnis:
string(9) "node:text" string(11) "field:hello"
Es kann so einfach sein, wenn man weiß wonach man suchen muss. Aber nach wie vor mag ich XML nicht 😉
Mag jetzt kleinlich wirken, aber wäre XPath nicht einfacher?
/nodes/*
:X
(Code-Einrückung wäre übrigens cool :D)
KingCrunch
10 Aug 12 at 10:33
Man sollte preserveWhitespace eigentlich immer auf false setzen, es sei denn man braucht sie doch mal. Ansonsten hat man bei der Verarbeitung größerer formatierter XMLs nur unnötig viele Knoten im Speicher.
Wie man darauf zugreift ist eigentlich jedem selbst überlassen bzw. ‚kommt drauf an‘. Ich denke Michael ging es hier nur darum ein kurzes Beispiel zu zeigen.
Man könnte ja auch mit
foreach ($doc->documentElement->childNodes as $childNode) …
zugreifen 😉
Quetschi
10 Aug 12 at 10:46
@KingCrunch XPath geht natürlich auch, aber da tritt das Problem ja nicht auf (und es ist 2 Zeilen länger) 😉
Was meinst du mit „Code-Einrückung wäre übrigens cool“, wo habe ich nicht richtig eingerückt?
@Quetschi Da hast du natürlich völlig recht mit dem documentElement, ist deutlich schöner als mein komisches Konstrukt da oben.
Michael Kliewe
10 Aug 12 at 12:10
Hab grad nochmal ein paar kurze Tests laufen lassen ohne Anspruch auf empirisch belastbare Ergebnisse:
Ein gewöhnliches Html-Dokument (ca. 35Kb) mit lesbar formatierten Quelltext (nur Einrückungen, keine überflüssigen Leerzeilen) wird 100 mal in ein DOM geladen.
Mit preserveWhitespace ‚true‘ dauern die 100 Durchläufe im Schnitt 0.20sec – mit preserveWhitespace ‚false‘ werden die 100 Durchläufe im Schnitt in 0.16sec erledigt. Das Dokument mit preserveWhitespace ‚true‘ zu parsen kostet also bei mir im Schnitt ca. 25% Aufschlag.
Der Verweis auf XPath ist natürlich gegeben, wenn sich ein bestimmter Knoten mit DOM-Mitteln nicht direkt lokalisieren lässt.
Einfachere Aufgaben wie z.B. alle Knoten mit einem bestimmten Namen zu ermitteln sollten allerdings mit z.B. getElementsByTagName() erledigt werden. Die DOM-Methode findet z.B. alle ‚p‘-Elemente im Test-Dokument ca. 35x schneller als eine XPath-Query.
Je nach Dokument-Struktur werden hier die Unterschiede ggf. geringer oder höher ausfallen – vielleicht teste ich es nochmal etwas ausführlicher wenn ich Zeit habe.
Quetschi
13 Aug 12 at 13:39
Ich meinte Code-Eintrückung in den Kommentaren (bzw wo ich das bei dir sehe: Wie mach ich echte Code-Blocks? :X)
#topic: Jaja, Lesbarkeit und so, aber im diesem Fall hätte ich auf die eine temporäre Varable schon verzichtet
$xPath = new DOMXPath($doc);
foreach ($xPath->query(‚/nodes/*‘) as $childNode) {
var_dump($childNode->nodeName.‘:‘.$childNode->nodeValue);
}
Damit wäre es nur eine Zeile länger :p. Vor Allem finde ich aber, dass die Intention wesentlich stärker zum Asudruck kommt, denn schließlich steht dort wörtlich, was du haben willst. Beim DOMDocument-Ansatz ist mE hierfür zu viel Abstraktion und Hintergrundrauschen.
@Quetschi: Mikrooptimierungen :p
KingCrunch
18 Aug 12 at 11:01
@KingCrunch
Bei einem größerem XML oder einer großen Anzahl zu parsenden XML kann aus Mikro ganz schnell Makro werden.
Quetschi
19 Aug 12 at 15:55
Da hier in den Kommentaren öfter mal die Rede von größeren XML Dokumenten war: Meine Erfahrung mit größeren XML Dokumenten (10MB aufwärts) ist, dass DomDocument zum Parsen derart großer Dokumente nicht geeignet ist.
DomDocument liest immer das komplette Dokument in den Speicher. Wenn man natürlich einen entsprechend groß konfigurierten Speicher zur Verfügung hat, ist dies nicht problematisch. Performanter und Resourcen-schonender ist es derart große Dokumente mit XMLReader zu streamen und nicht komplett einzulesen. Die Speicherauslastung bleibt hier eher minimal und der Zugriff auf DOM Funktionen ist trotzdem gewährt.
Marcel
31 Aug 12 at 10:44