Как сохранитьHTML DOMDocument без оболочки HTML?

Я являюсь функцией ниже, я изо всех сил пытаюсь вывести DOMDocument без добавления в оболочку XML, HTML, body и p тегов перед выходом содержимого. Предлагаемое исправление:

$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));

Работает только тогда, когда содержимое не содержит элементов уровня блока. Однако, когда это происходит, как в примере ниже с элементом h1, результирующий вывод saveXML усекается, чтобы...

<p> Если вам нравится </p>

Я был указан на этом посту как возможное обходное решение, но я не могу понять, как его реализовать в этом решении (см. комментарии ниже).

Любые предложения?

function rseo_decorate_keyword($postarray) {
    global $post;
    $keyword = "Jasmine Tea"
    $content = "If you like <h1>jasmine tea</h1> you will really like it with Jasmine Tea flavors. This is the last ocurrence of the phrase jasmine tea within the content. If there are other instances of the keyword jasmine tea within the text what happens to jasmine tea."
    $d = new DOMDocument();
    @$d->loadHTML($content);
    $x = new DOMXpath($d);
    $count = $x->evaluate("count(//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and (ancestor::b or ancestor::strong)])");
    if ($count > 0) return $postarray;
    $nodes = $x->query("//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and not(ancestor::h1) and not(ancestor::h2) and not(ancestor::h3) and not(ancestor::h4) and not(ancestor::h5) and not(ancestor::h6) and not(ancestor::b) and not(ancestor::strong)]");
    if ($nodes && $nodes->length) {
        $node = $nodes->item(0);
        // Split just before the keyword
        $keynode = $node->splitText(strpos($node->textContent, $keyword));
        // Split after the keyword
        $node->nextSibling->splitText(strlen($keyword));
        // Replace keyword with <b>keyword</b>
        $replacement = $d->createElement('strong', $keynode->textContent);
        $keynode->parentNode->replaceChild($replacement, $keynode);
    }
$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->item(1));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->childNodes);
return $postarray;
}

Ответ 1

Все эти ответы теперь неверны, потому что с PHP 5.4 и Libxml 2.6 loadHTML теперь имеет параметр $option, который инструктирует Libxml о том, как он должен анализировать содержимое.

Поэтому, если мы загрузим HTML с этими параметрами

$html->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

при выполнении saveHTML() не будет doctype, no <html> и no <body>.

LIBXML_HTML_NOIMPLIED отключает автоматическое добавление подразумеваемых элементов html/body LIBXML_HTML_NODEFDTD предотвращает добавление типа doctype по умолчанию, когда он не найден.

Полная документация о параметрах Libxml здесь

(Обратите внимание, что loadHTML docs говорят, что Libxml 2.6 необходим, но LIBXML_HTML_NODEFDTD доступен только в Libxml 2.7.8 и LIBXML_HTML_NOIMPLIED доступен в Libxml 2.7.7)

Ответ 2

Просто удалите узлы сразу после загрузки документа с помощью loadHTML():

# remove <!DOCTYPE 
$doc->removeChild($doc->doctype);           

# remove <html><body></body></html> 
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

Ответ 3

Вместо этого используйте saveXML() и передайте documentElement в качестве аргумента.

$innerHTML = '';
foreach ($document->getElementsByTagName('p')->item(0)->childNodes as $child) {
    $innerHTML .= $document->saveXML($child);
}
echo $innerHTML;

http://php.net/domdocument.savexml

Ответ 4

Чистым трюком является использование loadXML, а затем saveHTML. Теги html и body вставляются на этапе load, а не на ступень save.

$dom = new DOMDocument;
$dom->loadXML('<p>My DOMDocument contents are here</p>');
echo $dom->saveHTML();

NB, что это немного взломано, и вы должны использовать Jonah, если вы можете заставить его работать.

Ответ 5

использовать DOMDocumentFragment

$html = 'what you want';
$doc = new DomDocument();
$fragment = $doc->createDocumentFragment();
$fragment->appendXML($html);
$doc->appendChild($fragment);
echo $doc->saveHTML();

Ответ 6

Проблема с верхним ответом заключается в том, что LIBXML_HTML_NOIMPLIED является нестабильным.

Он может переупорядочивать элементы (в частности, перемещать закрывающий тег верхнего элемента в конец документа), добавлять случайные теги p и, возможно, множество других проблем [1]. Он может удалить теги html и body для вас, но за счет нестабильного поведения. В производстве, что красный флаг. Короче:

Не используйте LIBXML_HTML_NOIMPLIED. Вместо этого используйте substr.


Думаю об этом. Длина <html><body> и </body></html> фиксирована и на обоих концах документа - их размеры никогда не меняются, и их позиции не меняются. Это позволяет нам использовать substr чтобы вырезать их:

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

echo substr($dom->saveHTML(), 12, -15); // the star of this operation

(ЭТО НЕ ЗАКЛЮЧИТЕЛЬНОЕ РЕШЕНИЕ ОДНАКО! Полный ответ см. Ниже, продолжайте читать для контекста)

Мы отсекаем 12 от начала документа, потому что <html><body>= 12 символов (<<>>+html+body= 4 + 4 + 4), и мы идем назад и обрезаем 15 с конца, потому что \n</body></html>= 15 символов (\n+//+<<>>+body+html= 1 + 2 + 4 + 4 + 4)

Обратите внимание, что я все еще использую LIBXML_HTML_NODEFDTD пропуская !DOCTYPE от включения. Во-первых, это упрощает удаление substr тегов HTML/BODY. Во-вторых, мы не удаляем тип документа с помощью substr потому что мы не знаем, будет ли default doctype всегда иметь фиксированную длину. Но, самое главное, LIBXML_HTML_NODEFDTD останавливает анализатор DOM от применения к документу не-HTML5-документа, что по крайней мере не дает парсеру обрабатывать элементы, которые он не распознает как свободный текст.

Мы точно знаем, что теги HTML/BODY имеют фиксированную длину и позиции, и мы знаем, что такие константы, как LIBXML_HTML_NODEFDTD, никогда не удаляются без какого-либо уведомления об устаревании, поэтому вышеописанный метод должен хорошо LIBXML_HTML_NODEFDTD в будущее, НО...


... единственное предостережение в том, что реализация DOM может изменить способ размещения тегов HTML/BODY в документе - например, удаление новой строки в конце документа, добавление пробелов между тегами или добавление новых строк.

Это может быть исправлено путем поиска позиций открывающих и закрывающих тегов для body и использования этих смещений, чтобы урезать нашу длину. Мы используем strpos и strrpos для поиска смещений спереди и сзади соответственно:

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

$trim_off_front = strpos($dom->saveHTML(),'<body>') + 6;
// PositionOf<body> + 6 = Cutoff offset after '<body>'
// 6 = Length of '<body>'

$trim_off_end = (strrpos($dom->saveHTML(),'</body>')) - strlen($dom->saveHTML());
// ^ PositionOf</body> - LengthOfDocument = Relative-negative cutoff offset before '</body>'

echo substr($dom->saveHTML(), $trim_off_front, $trim_off_end);

В заключение повторим окончательный ответ на будущее:

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

$trim_off_front = strpos($dom->saveHTML(),'<body>') + 6;
$trim_off_end = (strrpos($dom->saveHTML(),'</body>')) - strlen($dom->saveHTML());

echo substr($dom->saveHTML(), $trim_off_front, $trim_off_end);

Нет типа документа, нет тега HTML, нет тега тела. Мы можем только надеяться, что парсер DOM скоро получит новый слой краски, и мы сможем более непосредственно устранить эти нежелательные теги.

Ответ 7

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

Решение, которое я создал на самом деле, довольно просто.

Загружаемый HTML помещается в элемент <div>, поэтому он содержит контейнер, содержащий все загружаемые узлы.

Затем этот контейнерный элемент удаляется из документа (но DOMElement он все еще существует).

Затем все прямые дети из документа удаляются. Это включает в себя любые добавленные теги <html>, <head> и <body> (эффективно LIBXML_HTML_NOIMPLIED), а также объявление <!DOCTYPE html ... loose.dtd"> (эффективно LIBXML_HTML_NODEFDTD).

Затем все прямые дочерние элементы контейнера снова добавляются в документ и могут выводиться.

$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);
}

$htmlFragment = $doc->saveHTML();

XPath работает как обычно, просто позаботьтесь о том, что теперь есть несколько элементов документа, поэтому не один корень node:

$xpath = new DOMXPath($doc);
foreach ($xpath->query('/p') as $element)
{   #                   ^- note the single slash "/"
    # ... each of the two <p> element

  • PHP 5.4.36-1 + deb.sury.org ~ exact + 2 (cli) (построено: 21 декабря 2014 20:28:53)

Ответ 8

Это 2017 год, и за этот вопрос 2011 года мне не нравится ни один из ответов. Множество регулярных выражений, больших классов, loadXML и т.д.

Простое решение, которое решает известные проблемы:

$dom = new DOMDocument();
$dom->loadHTML( '<html><body>'.mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8').'</body></html>' , LIBXML_HTML_NODEFDTD);
$html = substr(trim($dom->saveHTML()),12,-14);

Легкий, простой, надежный, быстрый. Этот код будет работать с тегами HTML и кодировкой, например:

$html = '<p>äöü</p><p>ß</p>';

Если кто-нибудь обнаружит ошибку, скажите, я буду использовать это сам.

Изменить. Другие допустимые параметры, которые работают без ошибок (очень похожие на уже заданные):

@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$saved_dom = trim($dom->saveHTML());
$start_dom = stripos($saved_dom,'<body>')+6;
$html = substr($saved_dom,$start_dom,strripos($saved_dom,'</body>') - $start_dom );

Вы могли бы добавить тело самостоятельно, чтобы предотвратить какую-то странную вещь на фурме.

Вариант Thirt:

 $mock = new DOMDocument;
 $body = $dom->getElementsByTagName('body')->item(0);
  foreach ($body->childNodes as $child){
     $mock->appendChild($mock->importNode($child, true));
  }
$html = trim($mock->saveHTML());

Ответ 9

Хорошо, я нашел более элегантное решение, но это просто утомительно:

$d = new DOMDocument();
@$d->loadHTML($yourcontent);
...
// do your manipulation, processing, etc of it blah blah blah
...
// then to save, do this
$x = new DOMXPath($d);
$everything = $x->query("body/*"); // retrieves all elements inside body tag
if ($everything->length > 0) { // check if it retrieved anything in there
      $output = '';
      foreach ($everything as $thing) {
           $output .= $d->saveXML($thing);
      }
      echo $output; // voila, no more annoying html wrappers or body tag
}

Хорошо, надеюсь, это ничего не опускает и помогает кому-то?

Ответ 10

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

  • Принимает текстовое содержимое без тегов, а также содержимое HTML.
  • Не добавляет теги (включая теги <doctype>, <xml>, <html>, <body> и <p>)
  • Оставляет только что-либо, заключенное в <p>.
  • Остается пустой текст.

Итак, вот решение, которое устраняет эти проблемы:

class DOMDocumentWorkaround
{
    /**
     * Convert a string which may have HTML components into a DOMDocument instance.
     *
     * @param string $html - The HTML text to turn into a string.
     * @return \DOMDocument - A DOMDocument created from the given html.
     */
    public static function getDomDocumentFromHtml($html)
    {
        $domDocument = new DOMDocument();

        // Wrap the HTML in <div> tags because loadXML expects everything to be within some kind of tag.
        // LIBXML_NOERROR and LIBXML_NOWARNING mean this will fail silently and return an empty DOMDocument if it fails.
        $domDocument->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING);

        return $domDocument;
    }

    /**
     * Convert a DOMDocument back into an HTML string, which is reasonably close to what we started with.
     *
     * @param \DOMDocument $domDocument
     * @return string - The resulting HTML string
     */
    public static function getHtmlFromDomDocument($domDocument)
    {
        // Convert the DOMDocument back to a string.
        $xml = $domDocument->saveXML();

        // Strip out the XML declaration, if one exists
        $xmlDeclaration = "<?xml version=\"1.0\"?>\n";
        if (substr($xml, 0, strlen($xmlDeclaration)) == $xmlDeclaration) {
            $xml = substr($xml, strlen($xmlDeclaration));
        }

        // If the original HTML was empty, loadXML collapses our <div></div> into <div/>. Remove it.
        if ($xml == "<div/>\n") {
            $xml = '';
        }
        else {
            // Remove the opening <div> tag we previously added, if it exists.
            $openDivTag = "<div>";
            if (substr($xml, 0, strlen($openDivTag)) == $openDivTag) {
                $xml = substr($xml, strlen($openDivTag));
            }

            // Remove the closing </div> tag we previously added, if it exists.
            $closeDivTag = "</div>\n";
            $closeChunk = substr($xml, -strlen($closeDivTag));
            if ($closeChunk == $closeDivTag) {
                $xml = substr($xml, 0, -strlen($closeDivTag));
            }
        }

        return $xml;
    }
}

Я также написал несколько тестов, которые будут жить в том же классе:

public static function testHtmlToDomConversions($content)
{
    // test that converting the $content to a DOMDocument and back does not change the HTML
    if ($content !== self::getHtmlFromDomDocument(self::getDomDocumentFromHtml($content))) {
        echo "Failed\n";
    }
    else {
        echo "Succeeded\n";
    }
}

public static function testAll()
{
    self::testHtmlToDomConversions('<p>Here is some sample text</p>');
    self::testHtmlToDomConversions('<div>Lots of <div>nested <div>divs</div></div></div>');
    self::testHtmlToDomConversions('Normal Text');
    self::testHtmlToDomConversions(''); //empty
}

Вы можете проверить, что он работает для вас. DomDocumentWorkaround::testAll() возвращает это:

    Succeeded
    Succeeded
    Succeeded
    Succeeded

Ответ 11

Используйте эту функцию

$layout = preg_replace('~<(?:!DOCTYPE|/?(?:html|head|body))[^>]*>\s*~i', '', $layout);

Ответ 12

Если решение флагов, на которое отвечает Alessandro Vendruscolo, не работает, вы можете попробовать следующее:

$dom = new DOMDocument();
$dom->loadHTML($content);

//do your stuff..

$finalHtml = '';
$bodyTag = $dom->documentElement->getElementsByTagName('body')->item(0);
foreach ($bodyTag->childNodes as $rootLevelTag) {
    $finalHtml .= $dom->saveHTML($rootLevelTag);
}
echo $finalHtml;

$bodyTag будет содержать полный обработанный HTML-код без всех этих оберток HTML, за исключением тега <body>, который является корнем вашего содержимого. Затем вы можете использовать функцию регулярного выражения или триммера, чтобы удалить ее из последней строки (после saveHTML) или, как и в случае выше, перебрать все ее дочерние элементы, сохраняя их содержимое во временную переменную $finalHtml и возвращая это (что я считаю безопаснее).

Ответ 13

Добавление тега <meta> приведет к поведению DOMDocument. Хорошая часть заключается в том, что вам не нужно добавлять этот тег вообще. Если вы не хотите использовать кодировку по своему выбору, просто передайте ее как аргумент конструктора.

http://php.net/manual/en/domdocument.construct.php

$doc = new DOMDocument('1.0', 'UTF-8');
$node = $doc->createElement('div', 'Hello World');
$doc->appendChild($node);
echo $doc->saveHTML();

Выход

<div>Hello World</div>

Благодаря @Bart

Ответ 14

У меня тоже было это требование, и мне понравилось решение, вышедшее Alex. Однако есть пара проблем - если элемент <body> содержит более одного дочернего элемента, результирующий документ будет содержать только первый дочерний элемент <body>, но не все из них. Кроме того, мне нужно было зачистки для обработки вещей условно - только когда у вас был документ с заголовками HTML. Поэтому я уточнил это следующим образом. Вместо удаления <body> я преобразовал его в <div> и удалил объявление XML и <html>.

function strip_html_headings($html_doc)
{
    if (is_null($html_doc))
    {
        // might be better to issue an exception, but we silently return
        return;
    }

    // remove <!DOCTYPE 
    if (!is_null($html_doc->firstChild) &&
        $html_doc->firstChild->nodeType == XML_DOCUMENT_TYPE_NODE)
    {
        $html_doc->removeChild($html_doc->firstChild);     
    }

    if (!is_null($html_doc->firstChild) &&
        strtolower($html_doc->firstChild->tagName) == 'html' &&
        !is_null($html_doc->firstChild->firstChild) &&
        strtolower($html_doc->firstChild->firstChild->tagName) == 'body')
    {
        // we have 'html/body' - replace both nodes with a single "div"        
        $div_node = $html_doc->createElement('div');

        // copy all the child nodes of 'body' to 'div'
        foreach ($html_doc->firstChild->firstChild->childNodes as $child)
        {
            // deep copies each child node, with attributes
            $child = $html_doc->importNode($child, true);
            // adds node to 'div''
            $div_node->appendChild($child);
        }

        // replace 'html/body' with 'div'
        $html_doc->removeChild($html_doc->firstChild);
        $html_doc->appendChild($div_node);
    }
}

Ответ 15

Как и другие участники, я впервые наслаждался простотой и потрясающей силой ответа @Alessandro Vendruscolo. Возможность просто передать некоторые помеченные константы конструктору казалась слишком хорошей, чтобы быть правдой. Для меня это было. У меня есть правильные версии как LibXML, так и PHP, но как бы он не добавил тег HTML к структуре node объекта Document.

Мое решение работало лучше, чем использование...

$html->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

Флаги или....

# remove <!DOCTYPE 
$doc->removeChild($doc->firstChild);            

# remove <html><body></body></html>
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

Node Удаление, которое становится беспорядочным без структурированного порядка в DOM. Снова фрагменты кода не имеют возможности предопределять структуру DOM.

Я начал эту поездку, желая простого способа сделать DOM-обход, как это делает JQuery или, по крайней мере, в некотором роде, у которого был структурированный набор данных, либо связанный, дважды связанный, либо древовидный node обход. Мне было все равно, насколько я могу разобрать строку так, как это делает HTML, а также обладает потрясающей способностью свойств класса сущностей node.

До сих пор объект DOMDocument оставил меня в покое... Как и многие другие программисты, кажется... Я знаю, что я видел много разочарования в этом вопросе, так как я НАКОНЕЦ... (примерно через 30 часов попробуйте и не проверите тест типа) Я нашел способ получить все это. Надеюсь, это поможет кому-то...

Во-первых, я цинично ВСЕ... lol...

Я бы потратил всю жизнь, прежде чем согласиться с кем-либо, что сторонний класс в любом случае необходим в этом случае использования. Я очень был и не являюсь поклонником использования какой-либо структуры сторонних классов, однако я наткнулся на отличный парсер. (около 30 раз в Google, прежде чем я сдался, так что не чувствуйте себя в одиночестве, если вы избегаете этого, потому что он выглядел хромым неофициальным в любом случае...)

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

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

Вы можете найти его файлы для загрузки здесь, его команды запуска здесь и его API здесь. Я настоятельно рекомендую использовать этот класс с помощью простых методов, которые могут сделать .find(".className") так же, как метод поиска JQuery будет использоваться или даже знакомые методы, такие как getElementByTagName() или getElementById()...

Когда вы сохраняете дерево node в этом классе, оно ничего не добавляет. Вы можете просто сказать $doc->save();, и он выводит все дерево на строку без каких-либо проблем.

Теперь я буду использовать этот парсер для всех, без ограничений пропускной способности, проектов в будущем.

Ответ 16

У меня PHP 5.3, и ответы здесь не помогли мне.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild); заменил весь документ только первым ребенком, у меня было много абзацев, и только первый был сохранен, но решение дало мне хорошую отправную точку, чтобы написать что-то без regex. Я оставил некоторые комментарии, и я я уверен, что это можно улучшить, но если у кого-то есть такая же проблема, как у меня, это может быть хорошей отправной точкой.

function extractDOMContent($doc){
    # remove <!DOCTYPE
    $doc->removeChild($doc->doctype);

    // lets get all children inside the body tag
    foreach ($doc->firstChild->firstChild->childNodes as $k => $v) {
        if($k !== 0){ // don't store the first element since that one will be used to replace the html tag
            $doc->appendChild( clone($v) ); // appending element to the root so we can remove the first element and still have all the others
        }
    }
    // replace the body tag with the first children
    $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
    return $doc;
}

Тогда мы могли бы использовать его следующим образом:

$doc = new DOMDocument();
$doc->encoding = 'UTF-8';
$doc->loadHTML('<p>Some html here</p><p>And more html</p><p>and some html</p>');
$doc = extractDOMContent($doc);

Обратите внимание, что appendChild принимает DOMNode поэтому нам не нужно создавать новые элементы, мы можем просто повторно использовать существующие, которые реализуют DOMNode, такие как DOMElement, это может быть важно сохранить код "нормальным" при манипулировании несколькими документами HTML/XML

Ответ 17

Я столкнулся с этой темой, чтобы найти способ удалить оболочку HTML. Использование LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD отлично работает, но у меня проблема с utf-8. После долгих усилий я нашел решение. Я публикую его ниже, у кого есть такая же проблема.

Проблема, вызванная <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Проблема:

$dom = new DOMDocument();
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$dom->saveHTML();

Решение 1:

$dom->loadHTML(mb_convert_encoding($document, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    $dom->saveHTML($dom->documentElement));

Решение 2:

$dom->loadHTML($document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
utf8_decode($dom->saveHTML($dom->documentElement));

Ответ 18

Я борюсь с этим на RHEL7 под управлением PHP 5.6.25 и LibXML 2.9. (Старые вещи в 2018 году, я знаю, но это Red Hat для вас.)

Я обнаружил, что предложенное Алессандро Вендрусколо решение с большим количеством голосов нарушает HTML, переставляя теги. То есть:

<p>First.</p><p>Second.</p>'

будет выглядеть так:

<p>First.<p>Second.</p></p>'

Это касается обоих вариантов, которые он предлагает вам использовать: LIBXML_HTML_NOIMPLIED и LIBXML_HTML_NODEFDTD.

Решение, предложенное Алексом, идет наполовину, но оно не работает, если <body> имеет более одного дочернего узла.

Решение, которое работает для меня, заключается в следующем:

Во-первых, чтобы загрузить DOMDocument, я использую:

$doc = new DOMDocument()
$doc->loadHTML($content);

Чтобы сохранить документ после массирования DOMDocument, я использую:

// remove <!DOCTYPE 
$doc->removeChild($doc->doctype);  
$content = $doc->saveHTML();
// remove <html><body></body></html> 
$content = str_replace('<html><body>', '', $content);
$content = str_replace('</body></html>', '', $content);

Я первый согласен с тем, что это не очень элегантное решение, но оно работает.

Ответ 19

Я тоже столкнулся с этой проблемой.

К сожалению, я не чувствовал себя комфортно с помощью каких-либо решений, предоставляемых в этой теме, поэтому я пошел проверить тот, который удовлетворит меня.

Вот что я составил, и он работает без проблем:

$domxpath = new \DOMXPath($domDocument);

/** @var \DOMNodeList $subset */
$subset = $domxpath->query('descendant-or-self::body/*');

$html = '';
foreach ($subset as $domElement) {
    /** @var $domElement \DOMElement */
    $html .= $domDocument->saveHTML($domElement);
}

В сущности он работает аналогично большинству решений, представленных здесь, но вместо ручного труда он использует селектор xpath для выбора всех элементов в теле и конкатенации их html-кода.

Ответ 20

мой сервер получил php 5.3 и не может обновиться, поэтому эти параметры

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

не для меня.

Чтобы решить эту проблему, я передаю функции SaveXML для печати элемента Body, а затем просто замените "body" на "div"

вот мой код, надеюсь, что он поможет кому-то:

<? 
$html = "your html here";
$tabContentDomDoc = new DOMDocument();
$tabContentDomDoc->loadHTML('<?xml encoding="UTF-8">'.$html);
$tabContentDomDoc->encoding = 'UTF-8';
$tabContentDomDocBody = $tabContentDomDoc->getElementsByTagName('body')->item(0);
if(is_object($tabContentDomDocBody)){
    echo (str_replace("body","div",$tabContentDomDoc->saveXML($tabContentDomDocBody)));
}
?>

utf-8 для поддержки иврита.

Ответ 21

Ответ Alex правильный, но может привести к следующей ошибке на пустых узлах:

Аргумент 1 передан в DOMNode:: removeChild() должен быть экземпляром DOMNode

Вот мой маленький мод:

    $output = '';
    $doc = new DOMDocument();
    $doc->loadHTML($htmlString); //feed with html here

    if (isset($doc->firstChild)) {

        /* remove doctype */

        $doc->removeChild($doc->firstChild);

        /* remove html and body */

        if (isset($doc->firstChild->firstChild->firstChild)) {
            $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
            $output = trim($doc->saveHTML());
        }
    }
    return $output;

Добавление обрезки() также является хорошей идеей для удаления пробелов.

Ответ 22

Возможно, я слишком поздно. Но, возможно, у кого-то (такого как я) все еще есть эта проблема.
Итак, ни одно из вышеперечисленных не работало для меня. Поскольку $dom- > loadHTML также закрывает открытые теги, не только добавляйте теги html и body.
Поэтому добавьте <div> не работает для меня, потому что иногда мне иногда нравится 3-4 незакрытых div в html файле.
Мое решение:

1.) Добавьте маркер для вырезания, затем загрузите html-фрагмент

$html_piece = "[MARK]".$html_piece."[/MARK]";
$dom->loadHTML($html_piece);

2.) сделайте все, что захотите, с документом
3.) сохранить html

$new_html_piece = $dom->saveHTML();

4.), прежде чем вы вернете его, удалите <p> /p > из маркера, странно он появляется только на [MARK], но не на [/MARK]...!?

$new_html_piece = preg_replace( "/<p[^>]*?>(\[MARK\]|\s)*?<\/p>/", "[MARK]" , $new_html_piece );

5.) удалить все до и после маркера

$pattern_contents = '{\[MARK\](.*?)\[\/MARK\]}is';
if (preg_match($pattern_contents, $new_html_piece, $matches)) {
    $new_html_piece = $matches[1];
}

6.) вернуть его

return $new_html_piece;

Было бы намного проще, если бы LIBXML_HTML_NOIMPLIED работал у меня. Это может быть, но это не так. PHP 5.4.17, libxml Версия 2.7.8.
Я нахожу действительно странным, я использую парсер HTML DOM, а затем, чтобы исправить эту "вещь", я должен использовать регулярное выражение... Весь смысл заключался в том, чтобы не использовать регулярное выражение;)

Ответ 23

Для всех, кто использует Drupal, для этого используется встроенная функция:

https://api.drupal.org/api/drupal/modules!filter!filter.module/function/filter_dom_serialize/7.x

Код для справки:

function filter_dom_serialize($dom_document) {
  $body_node = $dom_document->getElementsByTagName('body')->item(0);
  $body_content = '';

  if ($body_node !== NULL) {
    foreach ($body_node->getElementsByTagName('script') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node);
    }

    foreach ($body_node->getElementsByTagName('style') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node, '/*', '*/');
    }

    foreach ($body_node->childNodes as $child_node) {
      $body_content .= $dom_document->saveXML($child_node);
    }
    return preg_replace('|<([^> ]*)/>|i', '<$1 />', $body_content);
  }
  else {
    return $body_content;
  }
}

Ответ 24

#remove doctype tag
$doc->removeChild($doc->doctype); 

#remove html & body tags
$html = $doc->getElementsByTagName('html')[0];
$body = $html->getElementsByTagName('body')[0];
foreach($body->childNodes as $child) {
    $doc->appendChild($child);
}
$doc->removeChild($html);

Ответ 25

Эта библиотека позволяет легко проходить/изменять DOM, а также заботится об удалении упаковщиков doctype/html для вас:

https://github.com/sunra/php-simple-html-dom-parser

Ответ 26

После сотен тестов и поисков я нашел хорошую практику.

        $dom = new DOMDocument();
        $dom->loadHTML( $links, LIBXML_HTML_NODEFDTD );
        $as = $dom->getElementsByTagName( 'a' );
        //do something ...
        foreach ( $as as $a ) {
            $a->setAttribute( 'class', 'list-group-item list-group-item-action' );
        }
        //its the output without any doctype html or body tags
        return str_replace( [ '<html>', '</html>', '<body>', '</body>' ], '', $dom->saveHTML() );