0xDEADBEEF

RSS odkazy english edition
««« »»»

Jak dostat HTML z HTML DOMu v PHP

10. 3. 2020

V PHP není možné získat na bajt přesně původní obsah HTML elementu, který byl naparsován jako DOMDocument nebo SimpleXMLElement. Když ho z nějakých důvodů potřebujete včetně podružností jako uvozovky nebo ukončovací lomítka void elementů, máte smůlu.

Existuje několik metod, jak serializovat DOM uzel. Každý se chová trochu jinak, ale žádný nevrátí původní bajty.

$html = "<img id=x src=x>";

$dom = new DOMDocument;
$dom->loadHTML($html);
$el = $dom->getElementById('x');

echo $el->c14n(), "\n";                        // <img src="x"></img>
echo $el->ownerDocument->saveHTML($el), "\n";  // <img src="x">
echo $el->ownerDocument->saveXML($el), "\n";   // <img src="x"/>
echo simplexml_import_dom($el)->asXML(), "\n"; // <img src="x"/>

Metoda c14n() je úplně k ničemu, provádí XML kanonizaci a té za oběť padnou nepárové tagy. <br> skončí jako <br></br>, což je v HTML nesmysl.

saveXML v DOMu a asXML v SimpleXML vytvoří validní XML.

saveHTML vynechává ukončovací lomítka void elementů, ale všechno ostatní normalizuje a nesnaží se o žádnou minifikaci.


Když chcete obsah HTML tagu včetně tagu samotného (něco na způsob outerHTML z JavaScriptu), je to snadné:

$html = "<span id=x> asd <br> asd </span>";

$dom = new DOMDocument;
$dom->loadHTML($html);
$el = $dom->getElementById('x');

function outerHTML(DOMNode $el): string {
  return $el->ownerDocument->saveHTML($el);
}

echo outerHTML($el), "\n"; // <span id="x"> asd <br> asd </span>

Pokud potřebujete innerHTML, tedy HTML obsah elementu bez ohraničujícího tagu, jde to takhle:

function innerHTML(DOMNode $el): string {
  $res = '';
  foreach ($el->childNodes as $ch) {
    $res .= $ch->ownerDocument->saveHTML($ch);
  }
  return $res;
}

echo innerHTML($el), "\n"; //  asd <br> asd

Tohle všechno píšu proto, že se někdo ptal, jestli se tohle dá posledně uvedený manévr provést v knihovně Matcher. I když pro innerHTML není žádná speciální podpora, dá se toho snadno dosáhnout řetězením.

$m = Matcher::multi(
  '//div[@class="second"]',
  function ($node) { return innerHTML($node); }
)->fromHTML();

V tomto případě je první argument multi matcheru XPath cesta, která vyprodukuje kolekci DOMNode elementů. Na každý z nich je posléze aplikována funkce předaná jako druhý argument a hotovo.


K tématu:

píše k47 (@kaja47, k47)