PHP DOMDocument节点层级与给定HTML层级不符的原因及解决方法
问题
有一段格式正确的HTML字符串,加载到PHP DOMDocument对象后,读取到的节点树与原HTML层级不匹配,具体问题:
- table节点被包含在#text节点中
- 第二个td节点位于第一个td节点内部
- 第二个tr节点位于第一个tr节点内部
- 第四个td节点位于第三个td节点内部
- 文本'after'被包含在table节点中
问题原因
核心原因是传入DOMDocument的HTML字符串里,所有HTML标签的<和>都是转义后的实体字符(<和>),而DOMDocument的loadHTML方法默认只会解析真实的HTML标签,不会自动解码这些实体。
这导致整个字符串被DOMDocument当作纯文本处理,原本的标签结构全部失效,所有内容被合并成文本节点,看起来的层级错乱其实是文本内容里的转义字符被当成了文本的一部分,而非实际的标签层级。
修复方法
需要先把转义后的HTML实体解码为真实的HTML标签,再传递给DOMDocument加载,同时可以添加参数禁用不必要的错误提示:
修改后的核心代码
$dom = new \DOMDocument(); // 解码转义的HTML实体,还原真实标签 $decodedHtml = htmlspecialchars_decode($html); // 加载解码后的HTML,禁用错误警告(避免自动补全html/head标签时的提示) $dom->loadHTML($decodedHtml, LIBXML_NOERROR | LIBXML_NOWARNING);
完整修复后的代码示例
//Formatted so you can easily see the format $html = "<body> <div style='border:1px solid blue; padding:10px;' > This is a <b>bold <span style='color:red'>red test</span></b> a table <table style='display:inline-block; border:1px solid green; padding:0'> <tr><td>Head 1</td><td>Head 2</td></tr> <tr><td>Value 1</td><td>Value 2</td></tr> </table> after </div> After div </body>"; //Formatted with all tabs and line feeds stripped $html = "<body><div style='border:1px solid blue; padding:10px;' >This is a <b>bold <span style='color:red'>red test</span></b> a table<table style='display:inline-block; border:1px solid green; padding:0'><tr><td>Head 1</td><td>Head 2</td></tr><tr><td>Value 1</td><td>Value 2</td></tr></table>after</div>After div</body>"; $nNxtLvl = 0; function processChildNodes($node) { global $nNxtLvl; $lvl = $nNxtLvl; for($i=0; $i < $lvl; $i++) { echo " "; } echo $node->nodeName; if($node->nodeName == "#text") echo " " . $node->nodeValue; echo "<br>"; $cNodes = $node->childNodes; if (!empty($cNodes)) { foreach ($cNodes as $cNode) { $nNxtLvl++; processChildNodes($cNode); } } $nNxtLvl = $lvl; } $dom = new \DOMDocument(); // 修复步骤:解码实体后加载HTML $decodedHtml = htmlspecialchars_decode($html); $dom->loadHTML($decodedHtml, LIBXML_NOERROR | LIBXML_NOWARNING); $ls = $dom->getElementsByTagName('body'); $elBody = $ls[0]; $ls = $elBody->childNodes; for($i=0; $i < count($ls); $i++) { processChildNodes($ls->item($i)); }
补充说明
htmlspecialchars_decode()会把<、>这类实体字符还原成<、>,让DOMDocument能正确识别HTML标签结构- 添加
LIBXML_NOERROR | LIBXML_NOWARNING参数是为了抑制DOMDocument自动补全HTML结构(比如自动添加<html>、<head>标签)时产生的警告,不影响实际解析结果
内容的提问来源于stack exchange,提问作者user4563519
相关产品推荐
相关产品推荐

