php - Replace all matches within the tag

275

There is the following line:

$str = '<div class="hello"> Hello world &lt hello world?! </div>';

Need to find all the matches within the tag, while avoiding a match attribute values. Try something like:

$pattern = '/(.*)(hello)(.*)(?=<\/)/ui'; 
$replacement = '$1<span class="page_speed_748935785">$2</span>$3';

but yet there is only one "hello". What to do?

738

Answer

Solution:

(*SKIP)(*F) Syntax in Perl and PCRE (PHP, Delphi, R...)

With all the disclaimers about using regex to parse html, we can do this with a surprisingly simple regex:

<[^>]*>(*SKIP)(*F)|(hello)

Sample PHP Code:

$replaced = preg_replace('~<[^>]*>(*SKIP)(*F)|(hello)~i',
                        '<span class="page_speed_748935785">$1</span>',
                         $yourstring);

In the regex demo, see the substitutions at the bottom.

Explanation

This problem is a classic case of the technique explained in this question to "regex-match a pattern, excluding..."

The left side of the alternation matches complete|<tags> then deliberately fails, after which the engine skips to the next position in the string. The right side captureshello (case-insensitive to Group 1, and we know they are the right ones because they were not matched by the expression on the left.

Reference

96

Answer

Solution:

Wrapping text matches into another element is a pretty basic operation, though the code is somewhat tricky:

$html = <<<EOS
<div class="hello"> Hello world &lt; hello world?! </div>
EOS;

$dom = new DOMDocument;
$dom->loadHTML($html);

$search = 'hello';

foreach ($dom->getElementsByTagName('div') as $element) {
    foreach ($element->childNodes as $node) { // iterate all direct descendants
        if ($node->nodeType == 3) { // and look for text nodes in particular
            if (($pos = strpos($node->nodeValue, $search)) !== false) {
                // we split the text up in: <prefix> match <postfix>
                $postfix = substr($node->nodeValue, $pos + strlen($search));
                $node->nodeValue = substr($node->nodeValue, 0, $pos);

                // insert <postfix> behind the current text node
                $textNode = $dom->createTextNode($postfix);
                if ($node->nextSibling) {
                    $node->parentNode->insertBefore($textNode, $node->nextSibling);
                } else {
                    $node->parentNode->appendChild($textNode);
                }

                // wrap match in an element and insert it    
                $wrapNode = $dom->createElement('span', $search);
                $element = $node->parentNode->insertBefore($wrapNode, $textNode);
            }
        }
    }
}

echo $dom->saveHTML(), "\n";

People are also looking for solutions to the problem: php - I would like to show equal image sizes for responsive bootstrap 3 template

Source

Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.