DomDocument removeChild在foreach中重新索引dom

DomDocument removeChild in foreach reindexing the dom

本文关键字:索引 dom 新索引 removeChild foreach DomDocument      更新时间:2023-09-26

我正在尝试删除具有data-spotid属性的p标签

        $dom = new DOMDocument();
        @$dom->loadHTML($description);
        $pTag = $dom->getElementsByTagName('p');
        foreach ($pTag as $value) {
            /** @var DOMElement $value */
            $id = $value->getAttribute('data-spotid');
            if ($id) {
                $value->parentNode->removeChild($value);
            }
        }

但当我移除child时,它正在重新索引dom。假设我删除了8个项目,第一个它将重新索引它,第二个元素将成为第一个,它不会删除它,它将转到第二个,现在是第三个元素。

DomNode::removeChild文档上的几条注释中提到了这一点,问题显然是foreach上的迭代器指针无法处理在循环子数组(或其他)时从父数组中删除项的事实。

建议的修复方法是首先循环遍历主节点,并将要删除的子节点推送到其自己的数组中,然后循环遍历"待删除"数组,并从其父节点中删除这些子节点。示例:

$dom = new DOMDocument();
@$dom->loadHTML($description);
$pTag = $dom->getElementsByTagName('p');
$spotid_children = array();
foreach ($pTag as $value) {
    /** @var DOMElement $value */
    $id = $value->getAttribute('data-spotid');
    if ($id) {
        $spotid_children[] = $value; 
    }
}
foreach ($spotid_children as $spotid_child) {
    $spotid_child->parentNode->removeChild($spotid_child); 
}

我们可以这样使用:

        $dom = new DOMDocument();
        @$dom->loadHTML($description);
        $pTag = $dom->getElementsByTagName('p');
        $count = count($pTag)
        for($i = 0; $i < $count; $i++) {
            /** @var DOMElement $value */
            $value = $pTag[$i];
            $id = $value->getAttribute('data-spotid');
            if ($id) {
                $i--;$count--;
                $value->parentNode->removeChild($value);
            }
        }

正如我所评论的,简单的解决方案是将迭代器强制转换为数组。例如:

$elements = iterator_to_array($elements);

但是,如果我们谈论性能,更好的方法是只选择所需的节点。整洁的副作用,去除问题也消失了。

例如:

<?php
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->loadXML(<<<__XML
<?xml version="1.0" encoding="UTF-8"?>
<root>
    <element>1</element>
    <element attr="a">2</element>
    <element>3</element>
    <element>4</element>
    <element attr="a">5</element>
    <element attr="a">6</element>
    <element>7</element>
    <element>8</element>
</root>
__XML
);
$xpath = new DOMXPath($doc);
$elements = $xpath->query('//element[@attr]');
foreach ($elements as $element) {
    $element->parentNode->removeChild($element);
}
echo $doc->saveXML();

演示:https://3v4l.org/CM9Fv

(假设$dom包含需要过滤掉的(dom)段落)。让我们试试一些好的老JavaScript:

$ptag = $dom.all.tags("p");
$ptag = [].slice.call($ptag);
$i = 0; 
while($ptag[$i]){
'data-spotid' in $ptag[$i].attributes ? $ptag[$i++].outerHTML = "" : 0
}

注意:我使用outerHTML来销毁不需要的元素,以避免调用它的父节点和重新定位我们已经拥有的感兴趣的节点。最近的Firefox版本终于支持它了(11+)。MDN ref

为了简洁起见,我还使用了shortall.tags()语法;Firefox可能还不支持它,所以您可能想回到"getElementsByTagName()"调用。