0xDEADBEEF

[RSS]
««« »»»

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 ele­mentu, který byl na­par­so­ván jako DOMDocument nebo SimpleXMLElement. Když ho z ně­ja­kých důvodů po­tře­bu­jete včetně po­druž­ností jako uvo­zovky nebo ukon­čo­vací lo­mítka void ele­mentů, máte smůlu.

Exis­tuje ně­ko­lik metod, jak se­ri­a­li­zo­vat DOM uzel. Každý se chová trochu jinak, ale žádný ne­vrá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, pro­vádí XML ka­no­ni­zaci a té za oběť padnou ne­pá­rové tagy. <br> skončí jako <br></br>, což je v HTML ne­smysl.

saveXML v DOMu a asXML v Sim­pleXML vy­tvoří va­lidní XML.

saveHTML vy­ne­chává ukon­čo­vací lo­mítka void ele­mentů, ale všechno ostatní nor­ma­li­zuje a ne­snaží se o žádnou mi­ni­fi­kaci.


Když chcete obsah HTML tagu včetně tagu sa­mot­ného (něco na způsob outerHTML z Ja­vaScriptu), 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 po­tře­bu­jete innerHTML, tedy HTML obsah ele­mentu bez ohra­ni­ču­jí­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á po­sledně uve­dený manévr pro­vést v knihovně Matcher. I když pro innerHTML není žádná spe­ci­ální pod­pora, dá se toho snadno do­sáh­nout ře­tě­ze­ním.

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

V tomto pří­padě je první ar­gu­ment multi matcheru XPath cesta, která vy­pro­du­kuje ko­lekci DOMNode ele­mentů. Na každý z nich je po­sléze apli­ko­vána funkce pře­daná jako druhý ar­gu­ment a hotovo.


K tématu:

píše k47 (@kaja47, k47)