7

I am parsing html in the $content variable with the DOMDocument to replace all iframes with images. The foreach is only replacing the ODD iframes. I have removed all the code in the foreach and found the piece of code causing this is: '$iframe->parentNode->replaceChild($link, $iframe);'

Why would the foreach be skipping all of the odd iframes?

The code:

        $count = 1;
        $dom = new DOMDocument;
        $dom->loadHTML($content);
        $iframes = $dom->getElementsByTagName('iframe');
        foreach ($iframes as $iframe) {

            $src = $iframe->getAttribute('src');
            $width = $iframe->getAttribute('width');
            $height = $iframe->getAttribute('height');

            $link = $dom->createElement('img');
            $link->setAttribute('class', 'iframe-'.self::return_video_type($iframe->getAttribute('src')).' iframe-'.$count.' iframe-ondemand-placeholderImg');
            $link->setAttribute('src', $placeholder_image);
            $link->setAttribute('height', $height);
            $link->setAttribute('width', $width);
            $link->setAttribute('data-iframe-src', $src);

            $iframe->parentNode->replaceChild($link, $iframe);

            echo "here:".$count;
            $count++;
        }

        $content = $dom->saveHTML();

        return $content;

This is the problem line of code

        $iframe->parentNode->replaceChild($link, $iframe);
9ete
  • 3,692
  • 1
  • 34
  • 32

2 Answers2

14

A DOMNodeList, such as that returned from getElementsByTagName, is "live":

that is, changes to the underlying document structure are reflected in all relevant NodeList... objects

So when you remove the element (in this case by replacing it with another one) it no longer exists in the node list, and the next one in line takes its position in the index. Then when foreach hits the next iteration, and hence the next index, one will be effectively skipped.

Don't remove elements from the DOM via foreach like this.


An approach that works instead would be to use a while loop to iterate and replace until your $iframes node list is empty.

Example:

while ($iframes->length) {
    $iframe = $iframes->item(0);

    $src = $iframe->getAttribute('src');
    $width = $iframe->getAttribute('width');
    $height = $iframe->getAttribute('height');

    $link = $dom->createElement('img');
    $link->setAttribute('class', 'iframe-'.self::return_video_type($iframe->getAttribute('src')).' iframe-'.$count.' iframe-ondemand-placeholderImg');
    $link->setAttribute('src', $placeholder_image);
    $link->setAttribute('height', $height);
    $link->setAttribute('width', $width);
    $link->setAttribute('data-iframe-src', $src);

    $iframe->parentNode->replaceChild($link, $iframe);

    echo "here:".$count;
    $count++;
}
user3942918
  • 25,539
  • 11
  • 55
  • 67
6

Faced this issue today, and guide by the answer, i make a simple code solution for you guys

$iframes = $dom->getElementsByTagName('iframe');
for ($i=0; $i< $iframes->length; $i++) {
    $iframe = $iframes->item($i);
    if("condition to replace"){
        // do some replace thing
        $i--;
    }
}

Hope this help.

takid1412
  • 296
  • 4
  • 8
  • $i-- isn't needed, and is messing up the for loop. – Kari Kääriäinen Dec 03 '20 at 22:31
  • @KariKääriäinen `$iframes` is reference value, if you replace one of `iframe` element, indexing of `$iframes` change. But `$i` is not, then you need minus `$i` to avoiding out-of-index errors and not skipping some elements. – takid1412 Dec 04 '20 at 02:58