使用 DOMDocument 将多个 HTML 个实体保存为一个实体

Save multiple HTML bodies as one using DOMDocument

我有一个包含多个 <html><body><div>Content</div></body></html> 标签的字符串。我想获取所有内容并将它们加入一个有效的结构中。例如:

<html><body><div>Content</div></body></html>
<html><body><div>Content</div></body></html>
<html><body><div>Content</div></body></html>

应该是:

<html>
    <body>
        <div>Content</div>
        <div>Content</div>
        <div>Content</div>
    </body>
</html>

我当前的代码如下所示:

    libxml_use_internal_errors(true);
    $newDom = new DOMDocument();

    $newBody = "";

    $newDom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));

    $bodyTags = $newDom->getElementsByTagName("body");

    foreach($bodyTags as $body) {
        $newBody .= $newDom->saveHTML($body);
    }

$newBody 现在包含所有正文标签:

<body><div>Content</div></body>
<body><div>Content</div></body>
<body><div>Content</div></body>

如何只保存$newBody中每个body标签的HTML内容?

编辑:

基于@NigelRen 的回答,这是我的解决方案:

    libxml_use_internal_errors(true);
    $newDom = new DOMDocument();

    $newBody = '';
    $newDom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));

    $bodyTags = $newDom->getElementsByTagName("body");

    foreach($bodyTags as $body) {
        foreach ($body->childNodes as $node)   {
            $newBody .= $newDom->saveHTML($node);
        }
    }

    $newDom = new DOMDocument();
    $newDom->loadHTML(mb_convert_encoding($newBody, 'HTML-ENTITIES', 'UTF-8'));
    $newBody = $newDom->saveHTML();

您想要将多个 html 文档加载到单个 DOM 树中的想法必然意味着您的格式不正确 X/HTML。使用它会很棘手,因为 DOM 解析器会对您在这里的意思做出一些假设,这些假设不一定是直观的。 HTML 是一种混杂的语言,因此这需要一些技巧。

这里是它的要点。您获取每个 body 元素,递归遍历其节点列表,然后将每个元素重新创建到一个新文档中。

我会这样做:

class DOMExtended extends DOMDocument {
    public function walk(DOMNode $node, $skipParent = false) {
        if (!$skipParent) {
            yield $node;
        }
        if ($node->hasChildNodes()) {
            foreach ($node->childNodes as $n) {
                yield from $this->walk($n);
            }
        }
    }
}

$html = <<<'HTML'
    <html><body><div>Content 1</div></body></html>
    <html><body><div>Content 2</div></body></html>
    <html><body><div>Content 3</div></body></html>
HTML;

libxml_use_internal_errors(true);

// We'll load the html with multiple body tags here
$oldDom = new DOMExtended;

// We'll recreate the new html here
$newDom = new DOMExtended;
$main = $newDom->childNodes->item(1);
$htmlNode = new DOMElement('html');
$newDom->appendChild($htmlNode);


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

// extract all the body tags from the old dom
$bodyTags = $oldDom->getElementsByTagName('body');

foreach ($bodyTags as $bodyTag) {
    foreach ($oldDom->walk($bodyTag, true) as $childNode) {
        // recreate the child nodes in the newDom
        $name = $childNode->nodeName;
        if ($name === '#text') { // prevent textnodes
            continue;
        }
        $content = $childNode->nodeValue;
        $newNode = new DOMElement($name, $content);
        // append that node into the newDom
        $htmlNode->appendChild($newNode);
    }
}

// Here's the result
echo $newDom->saveHTML();

最终结果:

<html>
    <div>Content 1</div>
    <div>Content 2</div>
    <div>Content 3</div>
</html>

为了对树部分进行递归遍历,我添加了一个带有 DOMExtended 的小助手,它只是通过生成器对树进行递归遍历。

您可以通过将 html 代码放在 php 代码中来实现。您可以这样编写代码:

<?php
    echo '<html><body><div>Content</div></body></html>';
    *PHP code to be executed...*
?>

这很尴尬,因为当您使用 loadHTML() 时,它会尝试修复原始文档中的 HTML。这会创建一个与您想象的不同的结构。

BUT,如果你有文档的基本大纲,下面会把<body>标签的内容复制到一个新文档(代码中的注释)...

$html = '<html><body><div>Content1</div></body></html>
<html><body><div>Content2</div></body></html>
<html><body><div>Content3</div></body></html>';

libxml_use_internal_errors(true);
$newDom = new DOMDocument();

// New document with final code
$newBody = new DOMDocument();

$newDom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));

// Set up basic template for new doucument
$newBody->loadHTML("<html><body /></html>");
// Find where to add any new content
$addBody = $newBody->getElementsByTagName("body")[0];
// Find the existing content to add
$bodyTags = $newDom->getElementsByTagName("body");
foreach($bodyTags as $body) {
    // Add all of the contents of the <body> tag into the new document
    foreach ( $body->childNodes as $node )   {
        // Import the node to copy to the new document and add it in
        $addBody->appendChild($newBody->importNode($node, true));
    }
}
echo $newBody->saveHTML();

给出...

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div>Content1</div><div>Content2</div><div>Content3</div></body></html>

限制是 <body> 标签之外的任何内容和 <body> 标签的任何属性都不会保留。