myol
myol

Reputation: 9828

Wrap <img> elements in <div> but allow for <a> tags

I have a function that scans for img tags in a string using DOMDocument and wraps them in a div.

    $str = 'string containing HTML';
    $doc = new DOMDocument();
    $doc->loadHtml(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8'));

    $tags = $doc->getElementsByTagName('img');

    foreach ($tags as $tag) {
        $div = $doc->createElement('div');
        $tag->parentNode->insertBefore($div, $tag);
        $div->appendChild($tag);
    }

    return $str;

However, when an img is wrapped in a tags, the a tags are removed and 'replaced' with the div. How can I keep the a tags?

Currently,

<a href="http://google.com"><img src="srctoimg"/></a>

results in;

<div><img src="srctoimg"/></div>

rather than;

<div><a href="http://google.com"><img src="srctoimg"/></a></div>

Is there a 'wildcard' I can pass in with the second argument to insertBefore() or how can I achieve this?

Upvotes: 2

Views: 1280

Answers (5)

mhall
mhall

Reputation: 3701

You can do this with an XPath query.

'//*[img/parent::a or (self::img and not(parent::a))]'

This will get the parent for any img tag that has an a parent, as well as any image tag itself for any img tag that does not have an immediate a parent.

This way you don't have to change the code within your loop.

$str = <<<EOS
<html>
<body>
    Image with link:
    <a href="http://google.com">
        <img src="srctoimg"/>
    </a>

    Image without link:
    <img src="srctoimg"/>
</body>
</html>
EOS;

$doc = new DOMDocument();
$doc->loadHtml(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8'));

$xpath = new DOMXPath($doc);
$tags  = $xpath->query(
    '//*[img/parent::a or (self::img and not(parent::a))]'
);

foreach ($tags as $tag) {
    $div = $doc->createElement('div');
    $tag->parentNode->insertBefore($div, $tag);
    $div->appendChild($tag);
}

echo $doc->saveHTML();

Output (indented for clarity):

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<body>
    Image with link:
    <div>
        <a href="http://google.com">
            <img src="srctoimg">
        </a>
    </div>

    Image without link:
    <div>
        <img src="srctoimg">
    </div>
</body>
</html>

Upvotes: 4

Saty
Saty

Reputation: 22532

Hi I provide this solution using jquery. I know you ask this question in php.

<a href="http://google.com"><img src="srctoimg"/></a>
<script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
  <script>
 $('a').replaceWith(function(){
    return $('<div/>', {
        html: this.innerHTML
    })
})
</script>

Upvotes: 0

gia
gia

Reputation: 757

Try with this inside your foreach

$parent = $tag->parentNode;
if( $parent->tagName == 'a' )
{
    $parent->parentNode->insertBefore($div, $parent);
    $div->appendChild($parent);
}
else
{
    $tag->parentNode->insertBefore($div, $tag);
    $div->appendChild($tag);
}

Upvotes: 2

user2560539
user2560539

Reputation:

Using xpath

$str = 'string containing HTML';
        $doc = new DOMDocument();
        $doc->loadHtml(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8'));
        $xpath = new DOMXPath($doc);

        $tags0 = $xpath->query('//a/img'); // get all <img> in <a> tag
        $tags1 = $xpath->query('//img[not(parent::a)]'); // get all <img> except those with parent <a> tag
        $tags = array_merge($tags0,$tags1); // merge the 2 arrays
        foreach ($tags as $tag) {
            $div = $doc->createElement('div');
            $tag->parentNode->insertBefore($div, $tag);
            $div->appendChild($tag);
        }

        return $str;

Upvotes: 1

Lydia Ralph
Lydia Ralph

Reputation: 1473

It might be simplest to just use an if clause:

foreach ($tags as $tag) {
    $div = $doc->createElement('div');
    $x = $tag->parentNode;

    // Parent node is not 'a': insert before <img>
    if($tag->parentNode->tag != 'a') {
      $tag->parentNode->insertBefore($div, $tag);
    }
    // Parent node is 'a': insert before <a>
    else{
      $tag->parentNode->parentNode->insertBefore($div, $tag);
    }

    $div->appendChild($tag);
}

Upvotes: 1

Related Questions