如何在没有HTML包装器的情况下保存DOMDocument的HTML?

时间:2011-02-02 21:15:16

标签: php serialization domdocument

我是下面的函数,我在努力输出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;
}

28 个答案:

答案 0 :(得分:65)

使用loadHTML()加载文档后直接删除节点:

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

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

答案 1 :(得分:18)

改为使用saveXML(),并将documentElement作为参数传递给它。

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

http://php.net/domdocument.savexml

答案 2 :(得分:13)

一个巧妙的诀窍是使用loadXML然后使用saveHTMLhtmlbody代码会在load阶段而不是save阶段插入。

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

请注意,这有点笨拙,你应该使用Jonah的答案,如果你能让它发挥作用。

答案 3 :(得分:13)

使用DOMDocumentFragment

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

答案 4 :(得分:12)

最佳答案的问题是 LIBXML_HTML_NOIMPLIED不稳定

它可以重新排序元素(特别是将顶部元素的结束标记移动到文档的底部),添加随机p标记,以及可能的各种其他问题[1]。它可能会为您删除htmlbody标记,但会以不稳定的行为为代价。在制作中,那是一面红旗。简而言之:

请勿使用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删除doctype,因为我们不知道“default doctype”是否总是具有固定长度。但是,最重要的是,LIBXML_HTML_NODEFDTD阻止DOM解析器将非HTML5文档类型应用于文档 - 这至少会阻止解析器处理它不能识别为松散文本的元素。

我们知道HTML / BODY标签具有固定长度和位置的事实,并且我们知道在没有某种类型的弃用通知的情况下永远不会删除像LIBXML_HTML_NODEFDTD这样的常量,因此上述方法应该很好地滚动到未来, ......

...唯一需要注意的是DOM实现可以更改HTML / BODY标记放置在文档中的方式 - 例如,删除文档末尾的换行符,添加标签之间的空格,或添加换行符。

这可以通过搜索body的开始和结束标记的位置来解决,并使用这些偏移来修剪我们的长度。我们分别使用strposstrrpos来查找正面和背面的偏移量:

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

没有doctype,没有html标记,没有body标记。我们只能希望DOM解析器很快就能获得一层新的涂料,我们可以更直接地消除这些不需要的标签。

答案 5 :(得分:10)

我在俱乐部有点晚了但不想分享一个我发现的方法。首先,我已经为loadHTML()提供了正确的版本来接受这些不错的选项,但是LIBXML_HTML_NOIMPLIED并没有在我的系统上运行。用户也会报告解析器的问题(例如herehere)。

我实际创建的解决方案非常简单。

要加载的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像往常一样工作,只需注意现在有多个文档元素,因此不是单个根节点:

$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~prelension + 2(cli)(建于2014年12月21日20:28:53)

答案 6 :(得分:9)

这是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());

答案 7 :(得分:5)

使用此功能

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

答案 8 :(得分:4)

在撰写本文时(2012年6月),其他所有解决方案都无法完全满足我的需求,因此我编写了一个处理以下案例的解决方案:

  • 接受没有标签的纯文本内容以及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

答案 9 :(得分:3)

如果 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标记(内容的根目录)之外,

<body>将包含完整处理过的HTML代码,而不包含所有这些HTML包装。然后你可以使用正则表达式或修剪函数将它从最终字符串中删除(在saveHTML之后),或者像上面的情况一样,迭代它的所有孩子,将它们的内容保存到临时变量{{1并返回它(我认为更安全)。

答案 10 :(得分:2)

与其他成员一样,我首先沉浸在@Alessandro Vendruscolo回答的简单和强大的力量中。简单地将一些标记的常量传递给构造函数的能力似乎太好了。对我来说是。我有LibXML和PHP的正确版本,但无论它仍然将HTML标记添加到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);

节点删除,在DOM中没有结构化顺序会变得混乱。同样,代码片段无法预先确定DOM结构。

我开始这个旅程想要一个简单的方法来执行DOM遍历JQuery如何做到这一点,或者至少以某种方式进行结构化数据集,无论是单链接,双链接还是树节点遍历。只要我能像HTML一样解析字符串,并且还具有沿途使用的节点实体类属性的惊人功能,我并不在乎。

到目前为止DOMDocument对象让我想要......就像许多其他程序员一样......我知道我在这个问题上看到了很多挫折,所以我最后......(大约30个小时之后)尝试和失败类型测试)我找到了一种方法来实现这一切。我希望这有助于某人...

首先,我对一切都愤世嫉俗...... 大声笑......

在与本人使用第三方课程所需的任何人达成协议之前,我会花一辈子的时间。我非常喜欢并且不是使用任何第三方类结构的粉丝,但我偶然发现了一个伟大的解析器。 (在我放弃之前在谷歌大约30次,所以如果你避开它就不要感到孤单,因为它在任何方面看起来都是非官方的跛脚......)

如果您正在使用代码片段并且需要代码清理且不受解析器的影响,而不使用额外的标记,则使用simplePHPParser

它很棒,很像JQuery。我没有经常留下深刻印象,但这个类使用了很多好的工具,而且我还没有解析错误。我非常喜欢能够做这门课所做的事。

您可以找到要下载的文件here,其启动说明here及其API here。我强烈建议使用这个类,其简单的方法可以{J}查找方法的方式使用.find(".className"),甚至可以使用getElementByTagName()getElementById()等熟悉的方法... < / p>

当您在此课程中保存节点树时,它根本不添加任何内容。您可以简单地说$doc->save();并将整个树输出到字符串而不用担心。

我将来会在所有非上限带宽项目中使用此解析器。

答案 11 :(得分:2)

我在运行PHP 5.6.25和LibXML 2.9的RHEL7上苦苦挣扎。 (我知道,2018年的旧东西,但那是Red Hat。)

我发现 Alessandro Vendruscolo 提出的备受推崇的解决方案通过重新排列标签来打破HTML。即:

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

变为:

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

这适用于他建议您使用的两个选项:LIBXML_HTML_NOIMPLIEDLIBXML_HTML_NODEFDTD

Alex 建议的解决方案解决了它的一半,但如果<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);

我是第一个同意这不是一个非常优雅的解决方案的人 - 但它确实有效。

答案 12 :(得分:2)

添加<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

答案 13 :(得分:2)

我也有这个要求,并且喜欢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);
    }
}

答案 14 :(得分:2)

我有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文档时保持代码“正常”非常重要

答案 15 :(得分:2)

我遇到了这个主题,找到了一种删除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));

答案 16 :(得分:1)

我的通用解决方案与 HTML 的加载方式无关:

function getNodeHtml(DOMNode $node, $outer = true) {
    $doc = new DOMDocument();
    $node = $node instanceof DOMDocument ? $node->documentElement : $node;
    foreach(($outer ? array($node) : $node->childNodes) as $n) {
        $doc->appendChild($doc->importNode($n->cloneNode(true), true));
    }
    return $doc->saveHTML();
}

示例结果:

  • <p>foo bar </p> ━▶ <p>foo bar </p>
  • <p>foo</p><p>bar</p> ━▶ <p>foo</p><p>bar</p>
  • <p>foo </p> <p> bar</p> ━▶ <p>foo </p> <p> bar</p>
  • Hello! ━▶ Hello!
  • <html><body><b>foo</b></body></html> ━▶ <html><body><b>foo</b></body></html>

答案 17 :(得分:0)

对于任何使用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;
  }
}

答案 18 :(得分:0)

我可能太晚了。但也许有人(像我一样)仍有这个问题。
所以,以上都没有对我有用。因为$ dom-&gt; loadHTML也会关闭打开的标签,不仅添加html和body标签 所以添加一个&lt; div>元素对我不起作用,因为我有时在html片段中喜欢3-4个未闭合的div 我的解决方案:

1.)添加标记以进行剪切,然后加载html文件

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

2。)对文件做任何你想做的事 3.)保存html

$new_html_piece = $dom->saveHTML();

4。)在退货之前,请删除&lt; p&gt; / p&gt;来自marker的标签,奇怪的是它只出现在[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解析器,然后,修复这个&#34;事情&#34;我必须使用正则表达式...重点是,不要使用正则表达式;)

答案 19 :(得分:0)

我也遇到了这个问题。

不幸的是,我觉得使用本主题中提供的任何解决方案并不舒服,所以我去检查一个能满足我的方法。

这是我编写的内容并且没有问题:

$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 :(得分:0)

这个库使得遍历/修改DOM变得简单,并且还可以为你删除doctype / html包装器:

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

答案 21 :(得分:0)

我的服务器获得了php 5.3,无法升级所以这些选项

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

不适合我。

为了解决这个问题,我告诉SaveXML函数打印Body元素,然后只需替换&#34; body&#34;用&#34; div&#34;

这是我的代码,希望它帮助某人:

<? 
$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是希伯来文的支持。

答案 22 :(得分:0)

#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);

答案 23 :(得分:0)

经过数百次测试和搜索,我找到了最佳实践。

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

答案 24 :(得分:0)

我遇到DOMDocument类的3个问题。

1-此类加载具有ISO编码和utf-8字符的html,但不会在输出中显示。

2-即使将‍‍ LIBXML_HTML_NOIMPLIED标志赋予loadHtml方法,直到我们的输入html不包含根标记,也无法正确解析它。

3-此类认为HTML5标签无效。

因此,我重写了此类以解决这些问题,并更改了一些方法。

class DOMEditor extends DOMDocument
{
    /**
     * Temporary wrapper tag , It should be an unusual tag to avoid problems
     */
    protected $tempRoot = 'temproot';

    public function __construct($version = '1.0', $encoding = 'UTF-8')
    {
        //turn off html5 errors
        libxml_use_internal_errors(true);
        parent::__construct($version, $encoding);
    }

    public function loadHTML($source, $options = LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD)
    {
        // this is a bitwise check if LIBXML_HTML_NOIMPLIED is set
        if ($options & LIBXML_HTML_NOIMPLIED) {
            // it loads the content with a temporary wrapper tag and utf-8 encoding
            parent::loadHTML("<{$this->tempRoot}>" . mb_convert_encoding($source, 'HTML', 'UTF-8') . "</{$this->tempRoot}>", $options);
        } else {
            // it loads the content with utf-8 encoding and default options
            parent::loadHTML(mb_convert_encoding($source, 'HTML', 'UTF-8'), $options);
        }
    }

    private function unwrapTempRoot($output)
    {
        if ($this->firstChild->nodeName === $this->tempRoot) {
            return substr($output, strlen($this->tempRoot) + 2, -strlen($this->tempRoot) - 4);
        }
        return $output;
    }

    public function saveHTML(DOMNode $node = null)
    {
        $html = html_entity_decode(parent::saveHTML($node));
        if (is_null($node)) {
            $html = $this->unwrapTempRoot($html);
        }
        return $html;
    }

    public function saveXML(DOMNode $node = null, $options = null)
    {
        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . PHP_EOL . $this->saveHTML($node);
    }

}

现在我使用DOMEditor代替了DOMDocument,到目前为止对我来说效果很好

        $editor = new DOMEditor();
        $editor->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        // works like a charm!
        echo $editor->saveHTML();

答案 25 :(得分:0)

您可以将整洁用于仅显示主体:

$tidy = new tidy();
$htmlBody = $tidy->repairString($html, [
  'indent' =>  true,
  'output-xhtml' => true,
  'show-body-only' => true
], 'utf8');

但是,请记住:整洁地删除一些标签,例如“真棒字体”图标:Problems Indenting HTML(5) with PHP

答案 26 :(得分:0)

这是对我有帮助的解决方案:

$content = str_replace(array('<html>','</html>') , '' , $doc->saveHTML());

答案 27 :(得分:0)

Alex回答是正确的,但可能会导致空节点出现以下错误:

  

传递给DOMNode :: removeChild()的参数1必须是。的实例   的DOMNode

这是我的小mod:

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

添加trim()也是删除空格的好主意。