DOMDocument und UTF-8

Vor ziemlich genau sechs Jahren empfahl Toscho die Verwendung von UTF-8 als einheitliche Zeichenkodierung. Seither hielt ich mich daran und hatte nie mehr Probleme mit falsch interpretierten Zeichen wie ü wo eigentlich ein ü stehen sollte. Inzwischen denkt man über Zeichencodierung kaum noch nach und müsste das auch nicht, wäre da nicht PHPs DOMDocument. Das nutze ich z.B. um alltägliches HTML (das heißt: invalides HTML) zu parsen.

Eine einfache Beispielaufgabe: Ersetze den Beschreibungstext, der dem Span-Element vorangeht mit einem gegebenen Wert. Die Klasse image-src kann hier als Identifier verwendet werden.

<img class="size-medium wp-attachment-123" src="img/irgendwas.jpg" alt="Übermäig" />
Übermäßig! <span class="image-src">Hans</span>

Okay, keine allzu schwere Aufgabe. Reduziert auf das Wesentliche schaut das in etwa so aus:

$html     =  '...'; // see above
$new_desc = 'Überwältigend!';
$document = new DOMDocument;
$document->loadHTML( $html );
$xpath    = new DOMXpath( $document );

$image_source = $xpath
	->query( "//span[ contains( @class, 'image-src' ) ]" )
	->item( 0 );
$description = $xpath
	->query( "//preceding-sibling::text()", $image_source )
	->item( 0 );
$description->nodeValue = $new_desc;
$body = $document
	->getElementsByTagName( 'body' )
	->item( 0 );
$html = $document->saveHTML( $body );

Die erste Xpath-Query sucht nach dem Span-Element mit der entsprechenden Klasse. Ausgehend von diesem Element sucht die zweite Query nach dem vorangehenden Text-Knoten. Dessen Wert wird dann einfach ersetzt. Kein Problem also. Eigentlich…

Schaut man sich das Resultat als Plaintext an, fällt sofort etwas auf:

Als ISO-8859 interpretiertes UTF-8.

Als ISO-8859 interpretiertes UTF-8.

DOMDocument::loadHTML() geht standardmäßig nicht von UTF-8 sondern von ISO-8895-1 aus. Das große Ü (U+00DC) wird in UTF-8 mit zwei Bytes codiert: 0xC3 0x9C. Als ISO-8859 interpretiert werden daraus zwei Zeichen: Ã und ein Platzhalter für eine offensichtlich nicht definierte Position in der Zeichentabelle (siehe Bild).

Das Problem lässt sich ganz einfach lösen, indem man dem Parser sagt, dass es sich um UTF-8 handelt und zwar in dem man ein Meta-Element vor das eigentliche HTML stellt:

<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<img class="size-medium wp-attachment-123" src="img/irgendwas.jpg" alt="Übermäig" />
Übermäßig! <span class="image-src">Hans</span>

Das ganze funktionierte auch, würde man stattdessen eine XML-Deklaration <?xml version="1.0"?> voranstellen. XML setzt als Standard UTF-8 voraus. Aber man muss ja aus falschem HTML nicht noch falscheres XML machen.

Hier wäre die Geschichte eigentlich zu ende, aber solche Fehler wären nur halb so unterhaltsam, würden sie sich nicht gut verstecken. Einsatzzweck war in diesem Fall ein Shortcode-Interpreter, der im Backend von WordPress die Bildbeschreibungen ad-hoc im Editor »globalisieren« sollte. (Die Bildbeschreibung für ein und das selbe Bild soll sich in allen Beiträgen gleichen, egal wo man die Beschreibung ändert.) Beim Laden des Posts wird der caption-Shortcode interpretiert und die Bildbeschreibung im Text durch die aktuelle Beschreibung vom Attachment ersetzt.

Da ich die kaputten Umlaute nur auf das Alt-Attribut auswirkten, fiel es im visuellen Modus des Editors nicht weiter auf. Nun werden bei jedem Speichern aus einem Umlaut zwei Zeichen, die WordPress als zwei eigenständige Zeichen in UTF-8 speichert, worauf beim nächsten Aufruf 4 Zeichen werden usw. Exponentielles Wachstum halt. Wer sich jemals Gedanken über Grenzen der MySQL-Datenbank bezüglich des Post-Contents gemacht hat, dem kann ich versichern: jeder Browser wird seine Hufe hoch gerissen haben lange bevor das Datenbankfeld sein Limit erreicht.

Die alles entscheidende Frage am Ende ist die, ob die Zeit die es gekostet hätte Unittests zu entwickeln, die Zeit für die Fehlersuche und Reperatur überschritten hätte. Ich meine: nein.

PS: Die body Tags in dem gezeigten Beispiel lassen sich übrigens recht einfach entfernen, in dem man sich nur das inner-HTML ausgeben lässt.

Kommentare

Es wurden noch keine Kommentarte zu diesem Artikel geschrieben.

Fragen, Ideen oder Kritik? – Hier ist Platz dafür!

Dein Kommentar

Um ein Kommentar abzugeben, reicht der Text im Kommentarfeld. Die Angabe eines Namens wäre nett, ist aber nicht erforderlich.

Du darfst folgenden HTML-Code verwenden, musst aber nicht:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>