DomDocument removeChild em foreach reindexing the dom

Estou tentando excluir tags p com atributo data-spotid

  $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); } } 

mas quando estou removendo criança, ele está reindexando o dom. Deixe-me supor que eu tenho 8 itens que eu deletou 1 ° ele irá reindexá-lo e o 2º elemento se tornará 1º e ele não irá excluí-lo vai para o 2º que agora é o 3º elemento.

Isso é mencionado em alguns comentários sobre a DomNode::removeChild do DomNode::removeChild , com a aparência de ser como o ponteiro do iterador no foreach não conseguiu lidar com o fato de que você está removendo itens de uma matriz pai enquanto faz o loop pela lista de crianças (ou algo assim).

A correção recomendada é percorrer primeiro o nó principal e empurrar os nós infantis que deseja excluir para sua própria matriz e, em seguida, encaminhar essa matriz “a ser apagada” e excluir essas crianças de seus pais. Exemplo:

 $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); } 

Podemos usar assim:

  $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); } } 

Como eu comentei, a solução fácil seria simplesmente lançar o iterador em uma matriz. Por exemplo:

 $elements = iterator_to_array($elements); 

Mas, se estamos falando de desempenho, uma maneira melhor seria simplesmente selecionar apenas os nós necessários. Efeito colateral puro, o problema de remoção também desaparece.

Por exemplo:

 loadXML(<<<__xml   1 2 3 4 5 6 7 8  __XML ); $xpath = new DOMXPath($doc); $elements = $xpath->query('//element[@attr]'); foreach ($elements as $element) { $element->parentNode->removeChild($element); } echo $doc->saveXML(); 

Demo: https://3v4l.org/CM9Fv

(Supondo que o $ dom contém os parágrafos (DOM) que você precisa filtrar). Vamos tentar um bom e antigo 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 } 

NOTA: Estou usando o externalHTML para destruir elementos indesejados para evitar chamar seu pai e deslocalizar o nó de interesse que já temos. As versões recentes do Firefox finalmente o suportam (11+). MDN ref

Eu também estou usando a breve syntax all.tags () para brevidade; O Firefox pode não estar apoiando ainda, então você pode querer voltar para ‘getElementsByTagName ()’ chamada lá.