Potřeboval jsem ověřit, zda se od sebe dva HTML dokumenty liší pouze mezerami. To není tak jednoduché, protože texty mohou být od sebe oddělené inline značkami. Pokud jsou mezery naopak před nebo za blokovými značkami, tak je musíme ignorovat. A konečně uvnitř některých značek (např. <pre>
) je potřeba mezery zachovat. Tohle chování se dá změnit i pomocí CSS, to jsem ale naštěstí nepotřeboval.
<?php class HtmlSpaces { const INLINE_TAGS = 'b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|img|q|span|sub|sup|button|input|label|select|textarea'; const IGNORED_TAGS = 'script|style|textarea|pre'; static private $inText; static private $addSpace; /** Odstranění nadbytečných mezer z HTML dokumentu * @param string * @return string normalizovaný kód * @copyright Jakub Vrána, https://php.vrana.cz/ */ static function normalize($html) { $dom = new DOMDocument; $dom->loadHTML($html); self::$inText = false; self::stripSpaces($dom); return $dom->saveHTML(); } static private function stripSpaces(DOMNode $node) { $isBlock = false; if ($node instanceof DOMElement) { if (!preg_match("~^(" . self::INLINE_TAGS . ")$~i", $node->tagName)) { $isBlock = true; self::$inText = false; } if (preg_match('~^(' . self::IGNORED_TAGS . ')$~i', $node->tagName)) { return; } } elseif ($node instanceof DOMText) { $data = trim(preg_replace('~\s+~', " ", $node->data)); if ($data != '') { if (self::$inText) { if (self::$addSpace) { self::$addSpace->data .= "\n"; } elseif (preg_match('~^\s~', $node->data)) { $data = "\n$data"; } } self::$inText = true; self::$addSpace = null; } if (self::$inText && !self::$addSpace && preg_match('~\s$~', $node->data)) { self::$addSpace = $node; } $node->data = $data; } if ($node->childNodes) { foreach ($node->childNodes as $node) { self::stripSpaces($node); } } if ($isBlock) { self::$inText = false; } } } ?>
Kód postupně prochází všechny uzly. U značek rozlišuje, zda se jedná o blokový nebo inline element, některé značky přeskakuje úplně. U textových uzlů odstraňuje nadbytečné mezery a pamatuje si, jestli už našel nějaký neprázdný text. Pokud ano, tak si zapamatuje, kam má doplnit případnou mezeru, pokud by následoval další text uvnitř stejné blokové značky.
Důležité je upozornit na to, že kód kromě odstranění mezer dokument také normalizuje – přidá uvozovky kolem hodnot atributů (pokud někde chybí), doplní uzavírací značky a podobně. O to se stará použitá knihovna DOM. Mně to nevadí, protože potřebuji jen porovnat dva dokumenty, ale někdy to může být spíše na škodu.
Diskuse je zrušena z důvodu spamu.