使用DOMDocument处理HTML时内部div文本节点丢失问题排查
嵌套Div内容提取时文本节点丢失的原因及解决办法
问题场景
需要将用户粘贴到contenteditable div中产生的嵌套div内容提取到外层div,但执行代码后,部分文本节点(如示例中的Text2)丢失。
原始HTML
<div> Text1 <div> <p>para</p> Text2 </div> <div> Text3 </div> </div>
期望HTML
<div> Text1 <p>para</p> Text2 Text3 </div>
用户实现代码
$doc = new DOMDocument(); $doc->loadHTML("<div>Text1<div><p>para</p>Text2</div><div>Text3</div></div>", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $xpath = new DOMXPath($doc); foreach ($xpath->evaluate('/div/div[not(@*)]') as $node) { $frag = $doc->createDocumentFragment(); foreach ($node->childNodes as $child) { $frag->appendChild($child); } $node->replaceWith($frag); };
执行结果问题
运行后Text2丢失,得到的HTML为:
<div>Text1<p>para</p>Text3</div>
原因分析
核心问题在于DOM的childNodes是一个live集合:当你调用$frag->appendChild($child)时,该节点会从原DOM树的childNodes中被移除,导致集合的长度和节点索引动态变化。
以示例中包含<p>para</p>和Text2的内部div为例:
- 初始childNodes有两个节点:索引0是
<p>para</p>,索引1是Text2文本节点。 - 第一次循环取索引0的节点,append到fragment后,该节点从原集合中移除,此时Text2自动变成索引0。
- 循环指针移动到索引1,但此时集合长度已经变为1,循环结束,Text2节点被漏处理,最终没有被添加到fragment中,也就不会被替换到外层div里。
解决办法
方法1:将live集合转为静态数组遍历
通过iterator_to_array把childNodes转为静态数组,避免遍历过程中集合动态变化:
$doc = new DOMDocument(); $doc->loadHTML("Text1paraText2Text3", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $xpath = new DOMXPath($doc); foreach ($xpath->evaluate('/div/div[not(@*)]') as $node) { $frag = $doc->createDocumentFragment(); // 将live的childNodes转为静态数组 $children = iterator_to_array($node->childNodes); foreach ($children as $child) { $frag->appendChild($child); } $node->replaceWith($frag); }; echo $doc->saveHTML();
方法2:倒序遍历childNodes
从最后一个节点开始处理,移除节点不会影响前面节点的索引,同时用prepend保证节点顺序不变:
$doc = new DOMDocument(); $doc->loadHTML("Text1paraText2Text3", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $xpath = new DOMXPath($doc); foreach ($xpath->evaluate('/div/div[not(@*)]') as $node) { $frag = $doc->createDocumentFragment(); // 倒序遍历节点 for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { $child = $node->childNodes->item($i); $frag->prepend($child); } $node->replaceWith($frag); }; echo $doc->saveHTML();
两种方法都能正确保留所有子节点,得到期望的HTML结果。
内容的提问来源于stack exchange,提问作者mankowitz
相关产品推荐
相关产品推荐

