Als Webentwickler kommt man nicht drum herum, hier und da mal etwas HTML verändern zu müssen. Blöd ist es, wenn man nicht direkt Templates bearbeiten kann, dann wird es etwas komplizierter. Und was fällt dem geneigten Entwickler in einem solchen Fall als erstes ein? Richtig, str_replace
! (Das heißt natürlich nur, falls man es mit PHP zu tun hat. Alle anderen hier bitte mental etwas anderes einsetzen.) str_replace
bekommt drei Parameter eingespeist und gibt das Ergebnis zurück, also eine einfache, doofe Funktion. Das gefällt.
Reguläre Ausdrücke (RegEx)
Den Fortgeschrittenen genügt das natürlich meistens nicht, da müssen schon komplexere Tools her. Möchte man z. B. die URL eines Links verändern, weiß man nicht unbedingt vorher, was da drinsteht. Wer es sich zutraut, greift in einem solchen Fall zu regulären Ausdrücken, kurz RegEx.
Doch halt! ☝️
Reguläre Ausdrücke auf HTML loszulassen ist in 99 % aller Fälle eine blöde Idee, denn die sind für Sprachen mit regulärer Grammatik gedacht. HTML ist allerdings eine irreguläre Sprache (ein Erklärungsversuch von Vlad Gudim). Oder anders gesagt:
Every time you attempt to parse HTML with regular expressions, the unholy child weeps the blood of virgins, and Russian hackers pwn your webapp. Parsing HTML with regex summons tainted souls into the realm of the living. HTML and regex go together like love, marriage, and ritual infanticide.
Das Zitat stammt von Marc Gravell und dürfte wohl zu den Top-Kommentaren auf Stack Overflow gehören.
Die Grenzen von RegEx
Wenn wir uns noch mal das Beispiel mit dem Link oben anschauen, wird es vielleicht deutlicher:
Man würde vermuten, dass ein Link so aussieht:
<a href="https://greatestview.de">Toller Blog</a>
Ein Link kann aber auch so aussehen:
<a href='https://greatestview.de'>Toller Blog</a>
Man darf also nicht davon ausgehen, dass gewisse Anführungszeichen benutzt werden, beides ist valides HTML. Aber das hier auch:
<a href=https://greatestview.de>Toller Blog</a>
Anführungszeichen sind im HTML optional. Alleine das in einem RegEx abzubilden macht schon wahnsinnig. Aber HTML kann noch viel mehr:
<a title="Guckst du lieber auf <a href=http://google.com>Google!</a>" href="https://greatestview.de">Toller Blog</a>
Würde man hier RegEx verwenden, gibt man vermutlich nach ein paar Stunden auf oder nimmt in Kauf, dass der RegEx in manchen Fällen die Seite zerschießt.
HTML-DOM-Parser für PHP
Die oft passendere Herangehensweise ist es, einen DOM-Parser zu verwenden. Damit man schnell in das Thema hineinfindet, haben sich ein paar Leute die Website htmlparsing.com ausgedacht. Die Site ist zwar schon mehrere Jahre alt, aber auf der PHP-Unterseite gibt es zum Einstieg einen schönen Vergleich zweier Parser, nämlich des PHP-DOM-Moduls und des PHP Simple HTML DOM Parsers.
Ich hatte bisher schon in dem einen oder anderen Projekt den PHP Simple HTML DOM Parser verwendet und war immer zufrieden damit. Im Vergleich zu regulären Ausdrücken kommt man bei komplexeren Aufgaben sehr viel schneller ans Ziel. Allerdings handelt es sich um ein Third-Party-Modul, man muss also eine zusätzliche Library einbinden. Das fand ich eher unschön. Außerdem ist die Bibliothek inzwischen drei Jahre alt. Also habe ich mir mal die oben genannte, native PHP-Lösung angeschaut. Man sollte ja schließlich immer mal wieder über den Tellerrand blicken.
Das Fazit?
Schlimm. Das PHP-DOM-Modul bringt einige Nachteile mit sich:
- Das Encoding ist standardmäßig auf
ISO-8859-1
eingestellt. Alleine das reicht eigentlich schon als Grund aus, das Tool zu meiden, denn es sagt so viel über dessen Beschaffenheit aus. Möchte man das Encoding ändern, muss man eine XML-Zeile mit Encoding-Attribut ergänzen… Sehr umständlich. - Auch wenn man nur ein Element braucht, muss man ein komplettes Dokument erstellen, inklusive
html
undbody
. - Die Konvertierung in einen String ist sehr umständlich. Und wenn man es schließlich geschafft hat, bekommt man auch hier das komplettes Dokument zurück und muss
html
undbody
wieder händisch rauswerfen. - Manche kennen vielleicht noch die Möglichkeit von jQuery, neue HTML-Elemente zu erzeugen, indem man sie einfach als String übergibt:
Das geht hier nur sehr umständlich, indem man neue Instanzen des$('<div>Ich werde ein neues HTML-Element</div>')
DOMDocument
s erzeugt. Ich kann mir nicht vorstellen, dass das sonderlich performant ist. - Wenn man Elemente nach einem etwas komplexeren Muster suchen möchte, muss man XPath verwenden. Nix gegen XPath, aber noch eine weitere Syntax möchte ich mir ungern merken, wenn es nur darum geht, einen Link zu manipulieren. Der PHP Simple HTML DOM Parser z. B. benutzt hier eine CSS-ähnliche Syntax.
Das ganze hat mich in meinem Anwendungsfall mehrere Stunden gekostet und war nicht von Erfolg gekrönt. Zum Spaß habe ich dann noch mal den PHP Simple HTML DOM Parser eingebunden und das damit probiert: In wenigen Minuten war das Problem gelöst. 😳
Also, falls ihr mal auch vor der Herausforderung steht, HTML parsen zu müssen, und ein String-Replace nicht ausreicht: Probierts doch mal mit dem PHP Simple HTML DOM Parser. Oder vielleicht habt ihr ja noch bessere Tipps?
Kleiner Disclaimer der Ausgewogenheit halber: In einer Stack-Overflow-Übersicht kommt der PHP Simple HTML DOM Parser nicht so gut weg. Das juckt mich aber erst mal nicht, für heute habe ich genug Parser ausprobiert. 🤯
Vielen Dank fürs optische Parsen des Artikels!
Zu Regexen gibt es natürlich noch den Witz, dass wer ein Problem mit HTML hat und denkt, es mit Regexen wegzubekommen, zwei Probleme hat. Besonders schlimm daran ist ja auch, dass man als normaler Webentwickler all die seltenen Randfälle vergisst, die SGML bietet, non-closing-Tags und so.
Xpath hingegen ist eigentlich total zauberhaft ebenso wie alle anderen XML-Werkzeuge (XPointer, Xlink). Das Problem an den ganzen HTML als XML ist latürnich, dass der Versuch HTML als XML neuzuerfinden gescheitert ist.
Was mir in letzter Zeit immer häufiger aufgefallen ist: jQuery macht einen verdammt guten Job, was Dom-Opterationen angeht. Ich hab noch einen Artikel mit dem provokanten Titel “jQuery ist das neue XSLT” in den Drafts liegen, hihi. Klar, das geht für Fälle, wo Du was im Backend machen musst nicht. Aber vermutlich bin ich nicht der Erste, der die Idee hat und vermutlich gibt es dafür eine nette Lösung.
Ich bin gespannt. :) Das Megafeature von jQuery war damals wie ich fand ich das Chaining und die Nutzung der CSS-Selektor-Schreibweise. Ein bissl Chaining kann der PHP Simple HTML DOM Parser (was ein Name…) auch, sieht jQuery schon recht ähnlich: