Reputation: 688
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
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
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