PHPDom 遍历文档并删除没有 XPath 的节点
PHPDom iterate through document and remove nodes without XPath
我正在尝试遍历文档并删除节点(在我的例子中是所有 divs),但没有 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 被删除的原因可能是最简单的解释方式:
您遍历所有子节点。此迭代首先将当前节点设置为第一个子节点 (DOMNode::$firstChild
). Then you process that child and when done you continue to the next child (that is then DOMNode::$nextSibling
)。
但是如果你现在从父节点中删除当前节点
$object->parentNode->removeChild($object);
迭代中的当前节点不再有任何下一个兄弟节点(因为它已从其父节点中删除)。因此,foreach 迭代在您删除第一个 div 元素后立即结束。
有多种方法可以解决这个问题。使用纯 PHP 并且不使用任何 xpath,您可以先将要删除的所有节点存储在一个数组中,然后再删除它们。在这种情况下,函数 iterator_to_array
非常方便:
$divs = iterator_to_array($doc->getElementsByTagName('div'));
foreach ($divs as $div) {
$div->parentNode->removeChild($div);
}
这四行代码确实替换了您的(不起作用的)函数 (!) 的所有迭代和递归逻辑。
您还可以通过使用 CachingIterator 来修复您的函数,它在迭代当前元素(当前元素已缓存)时内部已经有下一个元素。它不会失效,因为当您从父节点中删除当前节点时,下一个节点已经被获取。
您的代码大概会更改以下行:
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);
}
但请注意,此代码仅用于演示目的。如果您将其复制并粘贴到您的示例中,它会崩溃,因为您的代码中存在一些其他问题,这些问题会因此类更改而变得严重。
此代码仍将具有实际上不必要的递归,因为您可以按文档顺序迭代节点。为此,我在 Iterator Garden. That library also has a simple DOMElementFilter in the development branch 中有一个 DOMNodeIterator。由于下一个兄弟的问题在这里是一样的,使用这两个需要再次使用 CachingITerator:
$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();
我正在尝试遍历文档并删除节点(在我的例子中是所有 divs),但没有 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 被删除的原因可能是最简单的解释方式:
您遍历所有子节点。此迭代首先将当前节点设置为第一个子节点 (DOMNode::$firstChild
). Then you process that child and when done you continue to the next child (that is then DOMNode::$nextSibling
)。
但是如果你现在从父节点中删除当前节点
$object->parentNode->removeChild($object);
迭代中的当前节点不再有任何下一个兄弟节点(因为它已从其父节点中删除)。因此,foreach 迭代在您删除第一个 div 元素后立即结束。
有多种方法可以解决这个问题。使用纯 PHP 并且不使用任何 xpath,您可以先将要删除的所有节点存储在一个数组中,然后再删除它们。在这种情况下,函数 iterator_to_array
非常方便:
$divs = iterator_to_array($doc->getElementsByTagName('div'));
foreach ($divs as $div) {
$div->parentNode->removeChild($div);
}
这四行代码确实替换了您的(不起作用的)函数 (!) 的所有迭代和递归逻辑。
您还可以通过使用 CachingIterator 来修复您的函数,它在迭代当前元素(当前元素已缓存)时内部已经有下一个元素。它不会失效,因为当您从父节点中删除当前节点时,下一个节点已经被获取。
您的代码大概会更改以下行:
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);
}
但请注意,此代码仅用于演示目的。如果您将其复制并粘贴到您的示例中,它会崩溃,因为您的代码中存在一些其他问题,这些问题会因此类更改而变得严重。
此代码仍将具有实际上不必要的递归,因为您可以按文档顺序迭代节点。为此,我在 Iterator Garden. That library also has a simple DOMElementFilter in the development branch 中有一个 DOMNodeIterator。由于下一个兄弟的问题在这里是一样的,使用这两个需要再次使用 CachingITerator:
$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();