Tibor
Tibor

Reputation: 139

Wrapping images in WordPress with DOMDocument

I’m trying to add a div wrapper around each image in my WordPress blog and trying to achieve this using DOMDocment in my functions.php. Unfortunately, only the closing tag of my wrapper is applied to the ouput. The opening tag is missing for all images.

My function looks like this:

function lazyload($content){
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML(utf8_decode($content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();


$div = $dom->createElement('div');
$images = $dom->getElementsByTagName('img');

foreach ($images as $image) {
    $div_clone = $div->cloneNode();
    $image->parentNode->replaceChild($div_clone, $image);
    $div_clone->appendChild($image);
}

$html = $dom->saveHTML();
return $html; 
}

This occurs only when wrapping img. I tried to wrap each paragraph tag instead and all of them got opening and closing wrapper tags.

Has anybody an idea how to fix this? Instead of return $html I tried also echo $html. In ths case both tags were applied, but the captions were echoed as short tags instead of the parsed html elements.

Upvotes: 0

Views: 375

Answers (1)

codtex
codtex

Reputation: 6548

Hello_ friend.

I achieved what you are trying to do with a helper DOMDocument object like this:

function lazyload($content) {
    $dom = new DOMDocument();
    libxml_use_internal_errors(true);
    $dom->loadHTML(utf8_decode($content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    libxml_clear_errors();

    // This object will hold the updated content
    $newContent = new DOMDocument();

    $images = $dom->getElementsByTagName('img');

    foreach ($images as $image) {

        $div = $newContent->createElement('div');
        $div->appendChild($newContent->importNode($image));
        $newContent->appendChild($div);

    }

    $html = $newContent->saveHTML();
    return $html;
} 

// Test
$html = '<img alt="image 1"><img alt="image 2"><img alt="image 3"><img alt="image 4"><img alt="image 5"><img alt="image 6"><img alt="image 7"><img alt="image 8">';
echo lazyload($html);

// OUTPUT
<div>
    <img alt="image 1">
</div>
<div>
    <img alt="image 2">
</div>
<div>
    <img alt="image 3">
</div>
<div>
    <img alt="image 4">
</div>
<div>
    <img alt="image 5">
</div>
<div>
    <img alt="image 6">
</div>
<div>
    <img alt="image 7">
</div>
<div>
    <img alt="image 8">
</div>

NOTE: In your code I see you are using DOMDocument::createElement, but I never see you to append this newly created <div> to the DOM and documentation says:

public DOMElement DOMDocument::createElement ( string $name [, string $value ] )

This function creates a new instance of class DOMElement. This node will not show up in the document unless it is inserted with (e.g.) DOMNode::appendChild().

Let me know if the function that I edited is working for you.

Good Luck!

EDIT

your statement was a good point. If you want to use it on whole your DOM content you should take another approach:

function lazyload($content) {
    $dom = new DOMDocument();
    libxml_use_internal_errors(true);
    $dom->loadHTML(utf8_decode($content), LIBXML_HTML_NODEFDTD);
    libxml_clear_errors();

    $images = $dom->getElementsByTagName('img');

    foreach ($images as $image) {

        $div = $dom->createElement('div');
        $div->appendChild($image->cloneNode());

        // optional
        $div->setAttribute('class', 'your-lazy-loading-img-class');

        // replace img with the wrapper that is holding the <img>
        $image->parentNode->replaceChild($div, $image);
    }

    return $dom->saveHTML();
}


// Test
$html = '
    <h1 class="h1">Heading 1</h1>
    <div class="multiple-images-wrapper">
        <div class="inner-wrapper"><img alt="image 1"></div>
        <img alt="image 2">
        <img alt="image 3">
    </div>

    <h2>Heading 2</h2>
    <img alt="image 4">
    <img alt="image 5">';

// htmlspecialchars is only for testing purpose 
echo htmlspecialchars(lazyload($html));
exit;

OUTPUT

<html>
    <body>
        <h1 class="h1">Heading 1</h1> 
        <div class="multiple-images-wrapper"> 
            <div class="inner-wrapper">
                <div class="your-lazy-loading-img-class">
                    <img alt="image 1">
                </div>
            </div>
            <div class="your-lazy-loading-img-class">
                <img alt="image 2">
            </div>
            <div class="your-lazy-loading-img-class">
                <img alt="image 3">
            </div>
        </div>
        <h2>Heading 2</h2> 
        <div class="your-lazy-loading-img-class">
            <img alt="image 4">
        </div>
        <div class="your-lazy-loading-img-class">
            <img alt="image 5">
        </div>
        <div class="your-lazy-loading-img-class">
            <img alt="image 6">
        </div>
        <div class="your-lazy-loading-img-class">
            <img alt="image 7">
        </div>
        <div class="your-lazy-loading-img-class">
            <img alt="image 8">
        </div>
    </body>
</html>

NOTE

See that I omitted LIBXML_HTML_NOIMPLIED because with this flag seems that DOMDocument is not functioning as expected (for me). If you change to $dom->loadHTML(utf8_decode($content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); you will see output like this:

<h1 class="h1">Heading 1
    <div class="multiple-images-wrapper"> 
        <div class="inner-wrapper">
            <div class="your-lazy-loading-img-class">
                <img alt="image 1">
            </div>
        </div> 
        <div class="your-lazy-loading-img-class">
            <img alt="image 2">
        </div> 
        <div class="your-lazy-loading-img-class">
            <img alt="image 3">
        </div> 
    </div>
    <h2>Heading 2</h2>
    <div class="your-lazy-loading-img-class">
        <img alt="image 4">
    </div>
    <div class="your-lazy-loading-img-class">
        <img alt="image 5">
    </div>
</h1>

So according to me if you want a reliable HTML avoid using LIBXML_HTML_NOIMPLIED and then if you don't need <html><body></body></html> simply clear those tags from the returned string with some basic technique like string replace or string cut to position.

Upvotes: 1

Related Questions