使用 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>
标签的任何属性都不会保留。
我有一个包含多个 <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>
标签的任何属性都不会保留。