PHP DOMDocument中getElementsByTagName遍历异常问题求助
问题原因分析
你遇到的这个问题其实是DOM操作里的常见坑——getElementsByTagName()返回的DOMNodeList是实时动态集合,而非静态数组。也就是说,当你在遍历过程中用replaceChild()修改DOM结构(把<img>替换成<amp-img>)时,这个集合会自动同步更新。
举个具体的例子:假设初始集合里有5个<img>,当你替换第1个非目标<img>后,原集合里的第2个元素会自动顶到第1个位置,但foreach的遍历指针已经走到了下一个位置,直接跳过了原本的第2个元素,最终就只处理了部分元素。
解决方案
这里有两种可靠的解决办法,任选其一即可:
方法1:将动态NodeList转为静态数组
把实时的DOMNodeList转换成普通数组,这样遍历的是集合的静态副本,DOM结构变化不会影响遍历过程。只需要在foreach前用iterator_to_array()做转换:
$dom = new DOMDocument(); $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $imgs_t = $dom->getElementsByTagName('img'); // 转换为静态数组,第二个参数设为false避免保留原索引(可选,但更稳妥) $imgsArray = iterator_to_array($imgs_t, false); $amp_img_src = '/images/front/what_is_dt_video.jpg'; $count_foreach = 0; foreach( $imgsArray as $img_t ){ $count_foreach++; $img_alt = ''; $tmp_img_src = $img_t->getAttribute('src'); $img_alt = $img_t->getAttribute('alt'); if($tmp_img_src != $amp_img_src){ $tmp_amp_img = $dom->createElement('amp-img'); $tmp_amp_img->setAttribute('width', 200); $tmp_amp_img->setAttribute('height', 200); $tmp_amp_img->setAttribute('src', $tmp_img_src); $tmp_amp_img->setAttribute('alt', $img_alt); $img_t->parentNode->replaceChild($tmp_amp_img, $img_t); } } var_dump($count_foreach); // 现在会输出5
方法2:反向遍历NodeList
从最后一个元素开始往前遍历,这样即使替换前面的元素,也不会影响还未处理的元素的索引位置:
$dom = new DOMDocument(); $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $imgs_t = $dom->getElementsByTagName('img'); $amp_img_src = '/images/front/what_is_dt_video.jpg'; $count_foreach = 0; // 从最后一个元素开始倒序遍历 for ($i = $imgs_t->length - 1; $i >= 0; $i--) { $img_t = $imgs_t->item($i); $count_foreach++; $img_alt = ''; $tmp_img_src = $img_t->getAttribute('src'); $img_alt = $img_t->getAttribute('alt'); if($tmp_img_src != $amp_img_src){ $tmp_amp_img = $dom->createElement('amp-img'); $tmp_amp_img->setAttribute('width', 200); $tmp_amp_img->setAttribute('height', 200); $tmp_amp_img->setAttribute('src', $tmp_img_src); $tmp_amp_img->setAttribute('alt', $img_alt); $img_t->parentNode->replaceChild($tmp_amp_img, $img_t); } } var_dump($count_foreach); // 现在会输出5
为什么这两种方法有效?
- 方法1通过
iterator_to_array()把动态集合快照成静态数组,遍历的是独立副本,DOM结构变化不会干扰数组内容。 - 方法2反向遍历,处理完后面的元素后,前面的元素即使被替换,已经处理过的索引不会被干扰,确保每个元素都能被访问到。
内容的提问来源于stack exchange,提问作者Raz Galstyan
相关产品推荐
相关产品推荐

