Archiv für den Monat: Juli 2014

HTML-Tags inkl. Inhalt mit DOMDocument aus String entfernen (PHP)

Häufig muss man aus einem Stück HTML bestimmte andere HTML-Tags entfernen. In einem aktuellen Fall nutze ich im Backend eines CMS einen WYSIWYG-Editor, der für Abbildungen <figure><img …><figcaption>Bildunterschrift</figcaption></figure> einsetzt. Auf einer Übersichtsseite benötige ich den von diesem Editor generierten Text allerdings ohne Abbildungen.

Ich halte es für einen Fehler, in diesem Fall Regular Expressions (REGEXPs) zu verwenden. Ja, sie sind schnell, aber damit sie auch verschachtelte HTML-Tags greifen muss man unglaublich komplizierten Code einsetzen, der recht schnell zur Wartungshölle wird.

Stattdessen finde ich für die Manipulation von HTML DOMDocument sinnvoll. Es gibt allerdings einige Fallstricke:

  1. Das von DOMDocument eingesetzte libxml kann mit HTML5 nicht richtig umgehen und spuckt Warnungen aus
  2. UTF8-Zeichen werden zerstört, scheint ein Fehler von DOMDocument/PHP/libxml zu sein, müsste ich mal näher recherchieren
  3. PHP ergänzt automatisch eine DOCTYPE-Deklaration, sowie <html> und <body>-Tags, die man nicht benötigt, wenn man nur mit einem HTML-Fragment arbeitet

Die gute Nachricht: All diese Probleme lassen sich lösen! 🙂

So habe ich es gemacht:

/**
 * Entfernt die HTML-Node des HTML-Tags $tag aus dem HTML-Fragment $text
 * 
 * @param string $text
 * @param string $tag
 * @return string
 */
function removeTag($text, $tag = 'figure') {
   // Wichtig, denn bei leerem Input wirft loadHTML eine Fehlermeldung
   if(empty($text)) return $text;

   $dom = new DOMDocument;

   // Olle libxml-Warnungen unterdrücken
   libxml_use_internal_errors(true);
        
   /* UTF-8-Problematik lösen - wenn das aus irgendwelchen Gründen
      NICHT klappen sollte, könnte auch ein voranstellen von
      <!--?xml version="1.0" encoding="UTF-8"?--> vor $text helfen - habe ich
      allerdings nie ausprobiert */
   $dom->loadHTML(mb_convert_encoding($text, 'HTML-ENTITIES', 'UTF-8'));
        
   /* loadHTML fügt eine DOCTYPE-Deklaration, sowie und ein, die DOCTYPE-Deklaration kann sehr leicht entfernt werden, denn sie ist immer das erste Child */
   $dom->removeChild($dom->firstChild);

   /* Ich baue zuerst einen Array mit allen zu entfernenden Elementen
      Sie direkt im foreach zu entfernen scheiterte, ich nehme an, es
      hat irgendwas mit internen Indizes zu tun */
   $domElemsToRemove = array();
   $figureElements = $dom->getElementsByTagName($tag);
   foreach($figureElements as $figureElement) {
      $domElemsToRemove[] = $figureElement;
   }
   
   /* Hier werden die Elemente entfernt. Nicht das parentNode vergessen,
      ist mir zuerst passiert, kann ja nicht klappen ... */
   foreach($domElemsToRemove as $domElem) {
      $domElem->parentNode->removeChild($domElem);
   }
        
   $text = $dom->saveHTML();
                
   libxml_clear_errors();

   /* $text ist nach wie vor von... umschlossen
    * Hierfür gibt es vielfältige Lösungen, darunter str_replace, REGEXPs,
    * strip_tags(), oder replaceChild() - ich selbst brauchte 
    * GAR kein HTML-Tags in meiner Ausgabe und konnte daher strip_tags 
    * verwenden */
   return trim(strip_tags($text));
}