Regex для соответствия всех слов вне тегов и с акцентами в переполнении стека

Я хочу отметить все слова внутри текста, кроме тех, которые находятся внутри тега.
Основано на идее от Вот, Мне удалось сделать следующее:

preg_replace("/(\b(\p{L}+)\b)(?!([^<]+)?>)/", "<mark>$1</mark>", $input);

Что отлично работает, за исключением некоторых странных поведений при использовании с акцентом. Примеры:

lorem ipsúm dolor <a href="#" title="sit">sit</a> amet consectetur
[OK] => <mark>lorem</mark> <mark>ipsúm</mark> <mark>dolor</mark> <a href="#" title="sit"><mark>sit</mark></a> <mark>amet</mark> <mark>consectetur</mark>

ação ipísum
[NOT OK] => <mark>a</mark>çã<mark>o</mark> <mark>ip</mark>í<mark>sum</mark>

Есть идеи, почему это происходит и как это исправить?
Спасибо

1

Решение

Это все так просто …

Пара вещей здесь:

  1. Вы хотите использовать модификатор UTF-8 u,
  2. Не относится к вашему образцу текста, но вы пропускаете графемы из букв с такими диакритическими знаками, как «è». Здесь кодируется как «е», а затем сочетая серьезный акцент. Чтобы соответствовать тем, вам нужно добавить некоторые дополнительные \p{M},

Таким образом, регулярное выражение растет:

$input = 'lorem <a href="#">foo</a> ação';

echo preg_replace(
'/\b((?:\p{L}\p{M}*)+)\b(?!([^<]+)?>)/u',
"<mark>$1</mark>",
$input
);

Выходы:

<mark>lorem</mark> <a href="#"><mark>foo</mark></a> <mark>ação</mark>

…Кроме

Пока все хорошо, правда? Давайте добавим это «ё» и посмотрим.

$input = 'lorem <a href="#">foo</a> ação evè';

Сетевые выходные данные:

<mark>lorem</mark> <a href="#"><mark>foo</mark></a> <mark>ação</mark> <mark>eve</mark>̀

Это не правильно. Оказывается сокращение слова границы \b все еще действует немного глупо, даже в режиме UTF-8. Таким образом, вы должны заменить его некоторыми негативными взглядами.

Пока мы на этом, давайте также использовать \pL на месте \p{L} поскольку фигурные скобки являются необязательными для однобуквенных категорий Unicode.


Положите все вместе:

$input = 'lorem <a href="#">foo</a> ação evè';

echo preg_replace(
'/(?<![\pL\pM])((?:\pL\pM*)+)(?![\pL\pM])(?!([^<]+)?>)/u',
"<mark>$1</mark>",
$input
);

Выходы:

<mark>lorem</mark> <a href="#"><mark>foo</mark></a> <mark>ação</mark> <mark>evè</mark>

Демо в https://eval.in/194139.

1

Другие решения

Использовать u модификатор:

$arr = Array('orem ipsúm dolor <a href="#" title="sit">sit</a> amet consectetur','ação ipísum');
foreach($arr as $input) {
echo preg_replace("/(\b(\p{L}+)\b)(?!([^<]+)?>)/u", "<mark>$1</mark>", $input),"\n";
//                                       here __^
}

Выход:

<mark>orem</mark> <mark>ipsúm</mark> <mark>dolor</mark> <a href="#" title="sit"><mark>sit</mark></a> <mark>amet</mark> <mark>consectetur</mark>
<mark>ação</mark> <mark>ipísum</mark>
0