2015-04-20 1 views
0

Я пытаюсь выполнить итерацию документа и удалить узлы (в моем случае все div), но без xpath (я могу уже сделать это с xpath). По какой-то причине удаляется только первый div. Какие-нибудь советы?PHPDom перебирать документы и удалять узлы без XPath

<?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

почему вы используете ссылку здесь? вы не работаете с переменным псевдонимом здесь, и нет никаких изменений в значении переменной параметра ?! – hakre

+0

Хороший звонок. У меня были некоторые хромые рассуждения, когда я использовал эту ссылку, но теперь не помню. Было уже поздно, и я отчаялся ... :) – giorgio79

ответ

2

Причина, почему только первый DIV получает удален, возможно, самый простой объяснил так:

Вы итерацию всех потомков-узлов. Эта итерация начинается с установки текущего узла на первое дочернее устройство (DOMNode::$firstChild). Затем вы обрабатываете этого ребенка, а когда закончите, вы продолжаете следующий ребенок (то есть 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); 
} 

, но обратите внимание, что этот код только для демонстрационных целей. Если вы скопировали бы &, то это приведет к сбою, потому что у вас есть другие проблемы в вашем коде, которые стали бы серьезными с такими изменениями.

Этот код по-прежнему будет иметь рекурсию, которая на самом деле не нужна, поскольку вы могли бы перебирать узлы в порядке документа. Для этого у меня есть DOMNodeIterator в Iterator Garden. В этой библиотеке также есть DOMElementFilter в development branch. Поскольку проблема с следующего собрата то же самое здесь, используя те два требует 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(); 
+0

Ничего себе, что код красив. Спасибо! – giorgio79