PHP Conference Japan 2024

DOMDocument::loadHTML

(PHP 5, PHP 7, PHP 8)

DOMDocument::loadHTML 从字符串加载 HTML

描述

public DOMDocument::loadHTML(string $source, int $options = 0): bool

此函数解析字符串 source 中包含的 HTML。与加载 XML 不同,HTML 不必是良构的才能加载。

警告

此函数使用 HTML 4 解析器解析输入。现代 Web 浏览器使用的 HTML 5 的解析规则有所不同。根据输入,这可能会导致不同的 DOM 结构。因此,此函数不能安全地用于清理 HTML。

解析 HTML 时的行为可能取决于正在使用的 libxml 版本,尤其是在边缘情况和错误处理方面。对于符合 HTML5 规范的解析,请使用 PHP 8.4 中添加的 Dom\HTMLDocument::createFromString()Dom\HTMLDocument::createFromFile()

例如,某些 HTML 元素会在遇到时隐式地关闭父元素。自动关闭父元素的规则在 HTML 4 和 HTML 5 之间有所不同,因此 DOMDocument看到的最终 DOM 结构可能与 Web 浏览器看到的 DOM 结构不同,这可能会允许攻击者破坏生成的 HTML。

参数

source

HTML 字符串。

options

按位 OR libxml 选项常量

返回值

成功时返回 true,失败时返回 false

错误/异常

如果将空字符串作为 source 传递,则会生成警告。此警告不是由 libxml 生成的,无法使用 libxml 的错误处理函数进行处理。

虽然格式错误的 HTML 应该可以成功加载,但此函数在遇到错误标记时可能会生成 E_WARNING 错误。libxml 的错误处理函数 可用于处理这些错误。

变更日志

版本 描述
8.3.0 此函数现在具有暂定的 bool 返回类型。
8.0.0 静态调用此函数现在将抛出 Error。以前,会引发 E_DEPRECATED

示例

示例 #1 创建文档

<?php
$doc
= new DOMDocument();
$doc->loadHTML("<html><body>Test<br></body></html>");
echo
$doc->saveHTML();
?>

参见

添加注释

用户贡献的注释 19 条注释

mdmitry at gmail dot com
14 年前
您也可以使用此简单的技巧将 HTML 加载为 UTF-8

<?php

$doc
= new DOMDocument();
$doc->loadHTML('<?xml encoding="UTF-8">' . $html);

// 临时解决方案
foreach ($doc->childNodes as $item)
if (
$item->nodeType == XML_PI_NODE)
$doc->removeChild($item); // 删除临时代码
$doc->encoding = 'UTF-8'; // 插入正确的编码

?>
匿名
2 年前
如果 html 包含从 HTML5 开始采用的某些标签,例如“nav、section、footer 等”(在 PHP 8.1.6 中),loadHTML() 和 loadHTMLFile() 可能会始终生成警告。

尝试运行以下代码。

<?php

$file_name
= 'PHP Runtime Configuration - Manual.html'; // 请提前从 "https://php.net/manual/en/session.configuration.php" 下载此文件。

$doc = new DOMDocument();
$doc->loadHTMLFile($file_name); // 如果将 "LIBXML_NOERROR" 作为第二个参数,则不会出现错误
echo $doc->saveHTML();

// Warning: DOMDocument::loadHTMLFile(): Tag nav invalid in PHP Runtime Configuration - Manual.html, line: 63 in D:\xampp\htdocs\test\xml(dom)\loadHTML\index.php on line 6

?>
BychkovVV at mail dot ru
4 年前
如果您要从任何网站加载 html 内容(以“utf-8”编码),而 meta width content-type 不是 HEAD 的第一个子节点,则解析器将无法识别它(编码);因此您可以进行此修复
function domLoadHTML($html)
{$testDOM = new DOMDocument('1.0', 'UTF-8');
$testDOM->loadHTML($html);
$charset = NULL;
$searchInElemnt = function(&$item) use (&$searchInElemnt, &$charset)
{if($item->childNodes)
{foreach($item->childNodes as $childItem)
{switch($childItem->nodeName)}
{case 'html'}
case 'head'
$searchInElemnt($childItem);
break;
case 'meta'
$attributes = array();
foreach ($childItem->attributes as $attr)
{$attributes[mb_strtoupper($attr->localName)] = $attr->nodeValue;
}
if(array_key_exists('HTTP-EQUIV', $attributes) && (mb_strtoupper($attributes['HTTP-EQUIV']) == 'CONTENT-TYPE') && array_key_exists('CONTENT', $attributes) && preg_match('~[\s]*;[\s]*charset[\s]*=[\s]*([^\s]+)~', $attributes['CONTENT'], $matches))
{$charset = preg_replace('~[\s\']~', '', $matches[1]);
}
}
}
}
};
$searchInElemnt($testDOM);
if(isset($charset))
{$dom = new DOMDocument('1.0', $charset);
$dom->loadHTML('<?xml encoding="'.$charset.'">'.$html);
foreach ($dom->childNodes as $item)
if($item->nodeType == XML_PI_NODE)
{$dom->removeChild($item);
}
$dom->encoding = $charset;
}
else
{$dom = $testDOM;
}
return $dom;
};
Shane Harter
14 年前
DOMDocument 非常擅长处理不完美的标记,但在处理过程中会到处抛出警告。

这里没有很好的文档说明。解决方法是实现一个单独的装置来处理这些错误。

在调用 loadHTML 之前设置 libxml_use_internal_errors(true)。这将阻止错误冒泡到您的默认错误处理程序。然后您可以使用其他 libxml 错误函数来获取它们(如果您需要)。

您可以在此处找到更多信息 https://php.net/manual/en/ref.libxml.php
hanhvansu at yahoo dot com
17 年前
使用 loadHTML() 处理 UTF-8 页面时,您可能会遇到 dom 函数的输出与输入不一致的问题。例如,如果您想获得“Cạnh tranh”,您将收到“Cạnh tranh”。我建议我们在加载 UTF-8 页面之前使用 mb_convert_encoding
<?php
$pageDom
= new DomDocument();
$searchPage = mb_convert_encoding($htmlUTF8Page, 'HTML-ENTITIES', "UTF-8");
@
$pageDom->loadHTML($searchPage);

?>
obayed dot opu at gmail dot com
2 年前
要支持 HTML5,您必须通过将 `LIBXML_NOERROR` 作为 loadHTML 方法的选项来禁用 xml 错误处理。

示例

<?php
$doc
= new DOMDocument();
$doc->loadHTML("<html><body>Test<br><section>I'M UNSUPPORTED</section></body></html>", LIBXML_NOERROR);
echo
$doc->saveHTML();
?>
bigtree at DONTSPAM dot 29a dot nl
19 年前
加载字符集与 iso-8859-1 不同的 html 时要注意。由于此方法不会主动尝试找出要加载的 html 的编码方式(就像大多数浏览器一样),因此您必须在 html 头部指定它。例如,如果您的 html 使用 utf-8 编码,请确保在 html 的头部有一个 meta 标记

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>

如果您没有像这样指定字符集,所有高 ASCII 字节都将被 html 编码。仅将加载 html 的 dom 文档设置为 UTF-8 是不够的。
deepakrajpal dot com at gmail dot com
4 年前
如果我们加载 HTML5 标记,例如 <section>,<svg>,则会出现以下错误

DOMDocument::loadHTML(): Tag section invalid in Entity

在 loadHTML(); 之前,我们可以使用 libxml_use_internal_errors(true); 禁用标准 libxml 错误(并启用用户错误处理)。

这在 phpunit 自定义断言中非常有用,如下例所示(如果使用 phpunit 测试用例)

// 创建一个 DOMDocument
$dom = new DOMDocument();

// 修复 html5/svg 错误
libxml_use_internal_errors(true);

// 加载 html
$dom->loadHTML("<section></section>");
$htmlNodes = $dom->getElementsByTagName('section');

if ($htmlNodes->length == 0) {
$this->assertFalse(TRUE);
} else {
$this->assertTrue(TRUE);
}
finkenb2 at mail dot lib dot msu dot edu
9 年前
警告:这在使用 SVG 等 HTML5 元素时效果不佳。网络上的大多数建议都是为了使其与 HTML5 一起工作而关闭错误。
fr at felix-riesterer dot de
8 年前
记住:如果您使用 HTML5 文档类型和 meta 元素,如下所示

<meta charset=utf-8">

您的 HTML 代码将被解释为 ISO-8859-something,并且非 ASCII 字符将被转换为 HTML 实体。但是,HTML4 样式版本将有效(正如 10 年前“bigtree at 29a”所指出的那样)

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
cake at brothercake dot com
11 年前
请注意,此函数实际上并不理解 HTML——它使用 SGML 的通用规则修复标记汤输入,因此它创建了格式良好的标记,但不知道允许哪些元素上下文。

例如,对于第一个元素未关闭的输入,例如

<span>hello <div>world</div>

loadHTML 将将其更改为此,这是格式良好的但无效的

<span>hello <div>world</div></span>
Errol
15 年前
应该注意的是,当在 body 标记内提供任何文本时
在包含元素之外,DOMDocument 将该
文本封装到段落标记 (<p>) 中。

例如
<?php
$doc
= new DOMDocument();
$doc->loadHTML("<html><body>Test<br><div>Text</div></body></html>");
echo
$doc->saveHTML();
?>

将产生
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<p>Test<br></p>
<div>Text</div>
</body></html>


<?php
$doc
= new DOMDocument();
$doc->loadHTML(
"<html><body><i>Test</i><br><div>Text</div></body></html>");
echo
$doc->saveHTML();
?>

将产生
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<i>Test</i><br><div>Text</div>
</body></html>
romain dot lalaut at laposte dot net
17 年前
请注意,即使使用 <html xmlns="http://www.w3.org/1999/xhtml">,此类文档的元素也没有命名空间
kerim-yagmurcu at gmx dot de
7 年前
对于那些想要获取外部 URL 类元素的人,我有两个有用的函数。在这个例子中,我们得到了'<h3 class="r">'
谷歌搜索返回的元素(搜索结果标题)

1. 检查 URL(如果它是可访问的,存在的)
<?php
# URL 检查
function url_check($url) {
$headers = @get_headers($url);
return
is_array($headers) ? preg_match('/^HTTP\\/\\d+\\.\\d+\\s+2\\d\\d\\s+.*$/',$headers[0]) : false;
};
?>

2. 清理要获取的元素(删除所有标签、制表符、换行符等)
<?php
# 清理字符串的函数
function clean($text){
$clean = html_entity_decode(trim(str_replace(';','-',preg_replace('/\s+/S', " ", strip_tags($text)))));// 删除所有内容
return $clean;
echo
'\n';// 换行
}
?>

完成之后,我们可以使用以下方法输出搜索结果标题
<?php
$searchstring
= 'djceejay';
$url = 'http://www.google.de/webhp#q='.$searchstring;
if(
url_check($url)){
$doc = new DomDocument;
$doc->validateOnParse = true;
$doc->loadHtml(file_get_contents($url));
$output = clean($doc->getElementByClass('r')->textContent);
echo
$output . '<br>';
}else{
echo
'URL 不可访问!';// URL 无法调用时抛出消息
}
?>
jamesedwardcooke+php at gmail dot com
16 年前
使用 loadHTML() 会自动设置 DOMDocument 实例的 doctype 属性(设置为 html 中的 doctype,或默认为 4.0 Transitional)。如果使用 DOMImplementation 设置 doctype,它将被覆盖。

我假设可以设置它,然后使用我定义的 doctype 加载 html(以便在运行时决定 doctype),并且在尝试找出我的 doctype 去哪里时遇到了巨大的麻烦。希望这对其他人有所帮助。
divinity76+spam at gmail dot com
4 年前
如果你想去除所有“只包含空白字符的DOMText元素”,可以尝试

<?php

function loadHTML_noemptywhitespace(string $html, int $extra_flags = 0, int $exclude_flags = 0): DOMDocument
{
$flags = LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS | LIBXML_NONET;
$flags = ($flags | $extra_flags) & ~ $exclude_flags;

$domd = new DOMDocument();
$domd->preserveWhiteSpace = false;
@
$domd->loadHTML('<?xml encoding="UTF-8">' . $html, $flags);
$removeAnnoyingWhitespaceTextNodes = function (\DOMNode $node) use (&$removeAnnoyingWhitespaceTextNodes): void {
if (
$node->hasChildNodes()) {
// 警告:反向操作非常重要;如果正向操作,DOMNodeList 的索引可能会失效;
// 这就是我不使用 foreach() 的原因 - 除非你知道自己在做什么,否则不要更改
for ($i = $node->childNodes->length - 1; $i >= 0; --$i) {
$removeAnnoyingWhitespaceTextNodes($node->childNodes->item($i));
}
}
if (
$node->nodeType === XML_TEXT_NODE && !$node->hasChildNodes() && !$node->hasAttributes() && empty(trim($node->textContent))) {
//echo "Removing annoying POS";
// var_dump($node);
$node->parentNode->removeChild($node);
}
//elseif ($node instanceof DOMText) { echo "not removed"; var_dump($node, $node->hasChildNodes(), $node->hasAttributes(), trim($node->textContent)); }
};
$removeAnnoyingWhitespaceTextNodes($domd);
return
$domd;
}
Alex
14 年前
注意这个“陷阱”(按设计工作,但并非预期结果):如果你使用 loadHTML,则无法验证文档。验证仅适用于 XML。详情请见:http://bugs.php.net/bug.php?id=43771&edit=1
xuanbn at yahoo dot com
17 年前
如果你使用 loadHTML() 处理 utf HTML 字符串(例如越南语),你可能会遇到垃圾文本的结果,而有些文件却没问题。即使你的 HTML 已经有了 meta charset,例如

<meta http-equiv="content-type" content="text/html; charset=utf-8">

我发现,为了帮助 loadHTML() 正确处理 utf 文件,meta 标签应该放在最前面,在任何 utf 字符串出现之前。例如,这个 HTML 文件

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title> Vietnamese - Tiếng Việt</title>
</head>
<body></body>
</html>

当 <meta> 标签出现在 <title> 标签之前时,loadHTML() 将可以正常处理。

但是下面的文件 loadHTML() 将无法识别,因为 <title> 标签包含的 utf 字符串出现在 <meta> 标签之前。

<html>
<head>
<title> Vietnamese - Tiếng Việt</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body></body>
</html>
piopier
15 年前
这里有一个我编写的函数,它解决了使用 loadHTML 和 DOM 函数时关于字符集问题(UTF-8…)的先前说明。
它在 <head> 之后添加 charset meta 标签以改进自动编码检测,并将任何特定字符转换为 HTML 实体,因此 PHP DOM 函数/属性将返回正确的值。

<?php
mb_detect_order
("ASCII,UTF-8,ISO-8859-1,windows-1252,iso-8859-15");
function
loadNprepare($url,$encod='') {
$content = file_get_contents($url);
if (!empty(
$content)) {
if (empty(
$encod))
$encod = mb_detect_encoding($content);
$headpos = mb_strpos($content,'<head>');
if (
FALSE=== $headpos)
$headpos= mb_strpos($content,'<HEAD>');
if (
FALSE!== $headpos) {
$headpos+=6;
$content = mb_substr($content,0,$headpos) . '<meta http-equiv="Content-Type" content="text/html; charset='.$encod.'">' .mb_substr($content,$headpos);
}
$content=mb_convert_encoding($content, 'HTML-ENTITIES', $encod);
}
$dom = new DomDocument;
$res = $dom->loadHTML($content);
if (!
$res) return FALSE;
return
$dom;
}
?>

注意:它使用 mb_strpos/mb_substr 而不是 mb_ereg_replace,因为这在处理大型 html 页面时似乎更高效。
To Top