Stuart
Stuart

Reputation: 688

Wrap substrings in <strong> tags without creating nested tags while replacing

I have an array of words which I am using to highlight words in a string, some of the words however might appear as part of a phrase, and so I would like the phrase to take precedence over the single word in the highlight:

For example:

$seo = ['apple', 'apple tree', 'orchard'];

$description = "In my orchard I have a large Apple Tree";

Desired effect:

In my <strong>orchard</strong> I have a large <strong>Apple Tree</strong>

In my own first attempt, I looped through the array running preg_replace() against the string, but I am getting nested highlights like this <strong><strong>Apple</strong> Tree</strong>.

Upvotes: 0

Views: 620

Answers (2)

mickmackusa
mickmackusa

Reputation: 47854

Sort your array of replaceable words by length descending before calling preg_replace(). This will ensure that longer strings are matched before shorter words and therefore avoid the nested replacement problem that you were encountering.

Code: (Demo)

$seo = ['apple', 'apple tree', 'orchard'];
$description = "In my orchard I have a large Apple Tree";

array_multisort(array_map('strlen', $seo), SORT_DESC, $seo);

var_export(
    preg_replace(
        '#\b(?:' . implode('|', $seo) . ')\b#i',
        '<strong>$0</strong>',
        $description
    )
);

Upvotes: 0

mario
mario

Reputation: 145482

First, you shouldn't use a loop to replace each word individually, but a regex alternatives list (foo|bar|thingy).

  $rx_words = implode("|", array_map("preg_quote", $words));
  $text = preg_replace("/\b($rx_words)\b/i", 

(Actually that preg_quote misses the second param, but as long as you don't have forward slashes in the keywords, it'll work.)

Then you can also make it safer with an assertion:

  $text = preg_replace("/(?<!<strong>)\b($rx_words)\b/i",

So it will ignore words that are already wrapped. That's just a workaround, but quite often sufficient.

Upvotes: 1

Related Questions