PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


XML Parsen mit DOMDocument: störende Whitespace-Knoten

with 7 comments

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 😉

Written by Michael Kliewe

August 10th, 2012 at 10:29 am

Posted in PHP

Tagged with , , , ,

7 Responses to 'XML Parsen mit DOMDocument: störende Whitespace-Knoten'

Subscribe to comments with RSS or TrackBack to 'XML Parsen mit DOMDocument: störende Whitespace-Knoten'.

  1. 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

  2. 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

  3. @KingCrunch XPath geht natürlich auch, aber da tritt das Problem ja nicht auf (und es ist 2 Zeilen länger) 😉

    $xPath = new DOMXPath($doc);
    $childNodes = $xPath->query('/nodes/*');
    foreach ($childNodes as $childNode) {
        var_dump($childNode->nodeName.':'.$childNode->nodeValue);
    }

    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

  4. 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

  5. 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

  6. @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

  7. 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

Leave a Reply

You can add images to your comment by clicking here.