Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP DOMDocument parentNode->replaceChild causing foreach to skip next item

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);
like image 544
9ete Avatar asked Jan 23 '15 22:01

9ete


2 Answers

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++;
}
like image 75
user3942918 Avatar answered Oct 30 '22 10:10

user3942918


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.

like image 5
takid1412 Avatar answered Oct 30 '22 10:10

takid1412