Regarding the possibility of an infinite loop when altering the document tree while iterating:
We can see what really happens by investigating the keys of the NodeList. They are numeric indices starting at 0. Suppose we call getElementsByTagName('*') and receive a NodeList like this:
$nodes[0] = <root>
$nodes[1] = <first>
Iteration via foreach:
foreach ($nodes as $node)
can be re-written like this:
for ($i=0; $i < count($nodes); $i++)
On the first iteration, $nodes[$i] refers to <root>
If we happen to insert a new node immediately before the current node:
insertBefore(<infinite>, $nodes[$i])
$nodes[$i] stops referring to <root> and now refers to <infinite>; the new element takes the place of the old one (in the indexed ordering) and all subsequent nodes shift up by one.
On the next iteration, $i increments by one, so $nodes[$i] refers to <root> and the process repeats.
Here is PHP code to demonstrate:
<?php
$dom = new DOMDocument();
$rootNode = $dom->createElement('root');
$dom->appendChild($rootNode);
$firstNode = $dom->createElement('first');
$rootNode->appendChild($firstNode);
$allnodes = $dom->getElementsByTagName('*');
for ($i=0; ; $i++) {
$node = $allnodes->item($i);
echo "before: {$node->nodeName} ";
$newNode = $dom->createElement('infinite');
$node->parentNode->insertBefore($newNode, $node);
$node = $allnodes->item($i);
echo "after: {$node->nodeName} <br/>";
if ($i > 4) break; }
?>
Thus, if you are able to know, within the scope of the loop, that a node has been inserted, and use for instead of foreach, you can explicitly increment the iterator once more and "skip" the node that you already visited (if that's what you want; at any rate, that's what I wanted).
"Can" doesn't mean "should," of course; this sounds like a kludge (violating the sanctity of the iterator), but use your best judgment.