LoadHTML LIBXML_HTML_NOIMPLIED на фрагменте html генерирует неправильные теги

Использование флага LIBXML_HTML_NOIMPLIED с фрагментом html генерирует неправильные теги:

$str = '<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>';
$doc = new DOMDocument();
$doc->loadHTML($str, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
echo $doc->saveHTML();

Выходы:

<p>Lorem ipsum dolor sit amet.<p>Nunc vel vehicula ante.</p></p>

Я нашел хаки, чтобы обойти это с помощью регулярных выражений, но это побеждает цель использования DOM. Я тестировал это с несколькими версиями libxml и php, последними с libxml 2.9.2, php 5.6.7 (Debian Jessy). Любые предложения оценили.

Ответ 1

Перестановка выполняется с помощью параметра LIBXML_HTML_NOIMPLIED, который вы используете. Похоже, он недостаточно стабилен для вашего дела.

Также вы можете не использовать его по причинам, связанным с портабельностью, например, у меня есть один PHP 5.4.36 с Libxml 2.7.8 под рукой, который не поддерживает LIBXML_HTML_NOIMPLIED (Libxml >= 2.7.7), но позже LIBXML_HTML_NODEFDTD (Libxml >= 2.7.8).

Я знаю этот способ борьбы с этим. Когда вы загружаете фрагмент, вы завертываете его в элемент <div>:

$doc->loadHTML("<div>$str</div>");

Это помогает вести DOMDocument в нужной структуре.

Вы можете извлечь этот контейнер из самого документа:

$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);

И затем удалите всех детей из документа:

while ($doc->firstChild) {
    $doc->removeChild($doc->firstChild);
}

Теперь документ полностью пуст, и теперь вы можете снова добавить детей. К счастью, элемент контейнера <div>, который мы удалили ранее, мы можем добавить из него:

while ($container->firstChild ) {
    $doc->appendChild($container->firstChild);
}

Затем фрагмент можно получить с помощью известного метода saveHTML:

echo $doc->saveHTML();

Что дает в вашем сценарии:

<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>

Эта методология немного отличается от существующего материала здесь, на сайте (см. ссылки ниже), поэтому пример сразу:

$str = '<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>';

$doc = new DOMDocument();
$doc->loadHTML("<div>$str</div>");

$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);
while ($doc->firstChild) {
    $doc->removeChild($doc->firstChild);
}

while ($container->firstChild ) {
    $doc->appendChild($container->firstChild);
}

echo $doc->saveHTML();

Я также рекомендую ссылочный вопрос о том, как сохранитьHTML DOMDocument без оболочки HTML? для дальнейшего чтения, так же как и о inner-html

Ссылки

Ответ 2

Параметр LIBXML_HTML_NOIMPLIED не является ошибкой, он просто плохо документирован. Чтобы устранить проблему, оберните свою строку ввода <html>…</html>, обработайте свой HTML-код и отделите его от вывода. LibXML требует root node и обрабатывает первый элемент, который он находит в качестве корня node, удаляя закрывающий тег (неправильно расположенный), который он находит на полпути, а затем выводит закрывающий тег первого найденного им элемента в конце документа. Это логично, когда вы видите это с точки зрения (Lib) XML.