我正在尝试遍历文档,并删除节点(在我的情况下是所有div),但没有xpath(我已经可以使用xpath执行此操作)。出于某种原因,只有第一个div被删除。有什么提示吗?
<?php
//my totally random html
$html = '<p> Great <div> dont want this</div> </p><p> some more</p><div>more crap here</div>';
$doc = new DOMDocument();
$doc->loadHTML($html);
iterate_children($doc );
print $doc->saveHTML();
function iterate_children(&$object){
//print_r($object);
if ($object->tagName == "div") {
$object->parentNode->removeChild($object);
iterate_children($object->parentNode);
}
else {
//if($object->hasChildNodes()) {
foreach($object->childNodes as $child) {
//
iterate_children($child);
//}
}
}
}
?>
答案 0 :(得分:4)
为什么只有第一个div被删除的原因可能是最简单的解释:
迭代所有子节点。此迭代首先将当前节点设置为第一个子节点(DOMNode::$firstChild
)。然后你处理那个孩子,完成后你继续下一个孩子(那时是DOMNode::$nextSibling
)。
但是,如果您现在从父
中删除当前节点$object->parentNode->removeChild($object);
迭代中的当前节点不再具有任何next-sibling(因为它已从其父节点中删除)。因此,在删除第一个 div 元素后, foreach 迭代会立即结束。
有不同的方法可以解决这个问题。使用纯PHP并且不使用任何xpath,您可以先存储要在数组中删除的所有节点,然后将其删除。在这种情况下,函数iterator_to_array
非常方便:
$divs = iterator_to_array($doc->getElementsByTagName('div'));
foreach ($divs as $div) {
$div->parentNode->removeChild($div);
}
这四行代码确实替换了(不工作)函数的所有迭代和递归逻辑(!)。
您还可以使用CachingIterator来修复您的函数,该Iterator Garden在迭代当前元素时已经内部已经具有下一个元素(当前元素被缓存)。它不会失效,因为当你从父节点删除当前节点时,下一个节点已经被提取。
粗略地为您的代码更改以下行:
foreach($object->childNodes as $child) {
iterate_children($child);
}
为:
$children = $object->childNodes;
$children = new IteratorIterator($children);
$children = new CachingIterator($children, CachingIterator::TOSTRING_USE_KEY);
foreach ($children as $child) {
iterate_children($child);
}
但请注意,此代码仅用于演示目的。如果你要复制&amp;将它粘贴到您的示例中,它会崩溃,因为您的代码中存在其他一些问题,这些问题会因此而变得严重。
此代码仍然具有实际上不必要的递归,因为您可以按文档顺序迭代节点。为此,我在development branch中有一个 DOMNodeIterator 。该库在{{3}}中也有一个简单的 DOMElementFilter 。由于下一个兄弟的问题在这里是相同的,使用这两个也需要再次 CachingTerator :
$divs = new CachingIterator(new DOMElementFilter(new DOMNodeIterator($doc), 'div'), CachingIterator::TOSTRING_USE_KEY);
foreach ($divs as $div) {
$div->parentNode->removeChild($div);
}
此代码与iterator_to_array
示例非常相似。通常,迭代器使您能够创建更多可重复使用的代码,这要归功于它们的装饰性质。
我希望这有助于你理解为什么会这样,并且还展示了一些解决这个问题的方法。
出于完整性原因,这里的代码具有更好的错误处理和遍历逻辑:
function iterate_children(DOMNode $node)
{
if ($node instanceof DOMElement and $node->tagName == "div") {
$parent = $node->parentNode;
$parent->removeChild($node);
return;
}
$children = $node->childNodes;
if (!$children) {
return;
}
$children = new IteratorIterator($children);
$children = new CachingIterator($children, CachingIterator::TOSTRING_USE_KEY);
foreach ($children as $child) {
iterate_children_old($child);
}
}
这里没有递归和数组的实现:
<?php
/**
* PHPDom iterate through document and remove nodes without XPath
*/
/my totally random html
$html = '<p> Great <div> dont want this</div> </p><p> some more</p><div>more crap here</div>';
$doc = new DOMDocument();
$doc->recover = true;
$saved = libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_use_internal_errors($saved);
$divs = iterator_to_array($doc->getElementsByTagName('div'));
foreach ($divs as $div) {
$div->parentNode->removeChild($div);
}
echo $doc->saveHTML();