PHPDom遍历文档并删除没有XPath的节点

PHPDom遍历文档并删除没有XPath的节点,php,dom,xpath,domdocument,Php,Dom,Xpath,Domdocument,我试图遍历一个文档,并删除节点(在我的例子中是所有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

我试图遍历一个文档,并删除节点(在我的例子中是所有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);
        //}
        }
    }
}

?>

删除第一个div的原因可能最简单的解释如下:

迭代所有子节点。此迭代首先将当前节点设置为第一个子节点()。然后处理该子项,完成后继续处理下一个子项(即)

但是如果现在从父节点中删除当前节点

$object->parentNode->removeChild($object);
迭代中的当前节点不再有任何下一个同级节点(因为它已从其父节点中删除)。因此,foreach迭代在删除第一个div元素后立即结束

有不同的方法来解决这个问题。使用纯PHP且不使用任何xpath,可以先将要删除的所有节点存储在一个数组中,然后删除它们。该功能在以下情况下非常方便:

$divs = iterator_to_array($doc->getElementsByTagName('div'));
foreach ($divs as $div) {
    $div->parentNode->removeChild($div);
}
这四行代码确实替换了(不工作的)函数(!)的所有迭代和递归逻辑

您还可以通过在迭代当前元素(缓存当前元素)时使用内部已包含下一个元素的来修复函数。它不会失效,因为从父节点移除当前节点的那一刻,下一个节点已经被获取

大致用于更改以下行的代码:

foreach($object->childNodes as $child) {            
    iterate_children($child);
}
致:

但请注意,此代码仅用于演示目的。如果您将其复制并粘贴到示例中,它将崩溃,因为您的代码中存在一些其他问题,这些问题将随着这样的更改而变得严重

这段代码仍然具有实际上不需要的递归,因为您可以按文档顺序迭代节点。为此,我有一个域节点编辑器。该库中还有一个简单的DOMElementFilter。由于下一个兄弟姐妹的问题在这里是相同的,因此使用这两个兄弟姐妹还需要一个cachingierator

$divs = new CachingIterator(new DOMElementFilter(new DOMNodeIterator($doc), 'div'), CachingIterator::TOSTRING_USE_KEY);
foreach ($divs as $div) {
    $div->parentNode->removeChild($div);
}
这段代码与
迭代器-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();
loadHTML($html);
libxml\u使用\u内部错误($saved);
$divs=迭代器到数组($doc->getElementsByTagName('div');
foreach($div作为$div){
$div->parentNode->removeChild($div);
}
echo$doc->saveHTML();

删除第一个div的原因可能最简单的解释如下:

迭代所有子节点。此迭代首先将当前节点设置为第一个子节点()。然后处理该子项,完成后继续处理下一个子项(即)

但是如果现在从父节点中删除当前节点

$object->parentNode->removeChild($object);
迭代中的当前节点不再有任何下一个同级节点(因为它已从其父节点中删除)。因此,foreach迭代在删除第一个div元素后立即结束

有不同的方法来解决这个问题。使用纯PHP且不使用任何xpath,可以先将要删除的所有节点存储在一个数组中,然后删除它们。该功能在以下情况下非常方便:

$divs = iterator_to_array($doc->getElementsByTagName('div'));
foreach ($divs as $div) {
    $div->parentNode->removeChild($div);
}
这四行代码确实替换了(不工作的)函数(!)的所有迭代和递归逻辑

您还可以通过在迭代当前元素(缓存当前元素)时使用内部已包含下一个元素的来修复函数。它不会失效,因为从父节点移除当前节点的那一刻,下一个节点已经被获取

大致用于更改以下行的代码:

foreach($object->childNodes as $child) {            
    iterate_children($child);
}
致:

但请注意,此代码仅用于演示目的。如果您将其复制并粘贴到示例中,它将崩溃,因为您的代码中存在一些其他问题,这些问题将随着这样的更改而变得严重

这段代码仍然具有实际上不需要的递归,因为您可以按文档顺序迭代节点。为此,我有一个域节点编辑器。该库中还有一个简单的DOMElementFilter。由于下一个兄弟姐妹的问题在这里是相同的,因此使用这两个兄弟姐妹还需要一个cachingierator

$divs = new CachingIterator(new DOMElementFilter(new DOMNodeIterator($doc), 'div'), CachingIterator::TOSTRING_USE_KEY);
foreach ($divs as $div) {
    $div->parentNode->removeChild($div);
}
这段代码与
迭代器-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();
loadHTML($html);
libxml\u使用\u内部错误($saved);
$divs=迭代器到数组($doc->getElementsByTagName('div');
foreach($div作为$div){
$div->parentNode->removeChild($div);
}
echo$doc->saveHTML();

您为什么在此处使用引用?您在这里不处理变量别名,参数变量的值也没有任何更改?!好电话。当我使用那个参照物时,我有一些站不住脚的理由,但现在记不起来了。天色已晚,我绝望了……:)你为什么在这里使用参考文献?您在这里不处理变量别名,参数变量的值也没有任何更改?!好电话。当我使用那个参照物时,我有一些站不住脚的理由,但不要