Я пытаюсь найти решение с регулярным выражением (прежде чем кто-нибудь скажет: я знаю, что я должен использовать библиотеку документов PHP DOM или что-то подобное, но давайте рассмотрим это как теоретический вопрос), ища ответы, и я наконец пришел с тем, что я покажу в конце этого вопроса.
Ниже следует лишь краткое изложение многих вещей, которые я пробовал раньше.
Прежде всего, я имею в виду под вложенными тегами одного и того же типа:
Text outside any div <div id="my_id"> bla bla <div> bla bla bla <div style="some style here"> lalalalala </div> </div> I'm trapped in a div! </div> more text outside divs <div>more divs here! <div id="justbeingannoying">radiohead rules</div> </div>
Теперь представьте, что я хочу удалить все div и их содержимое с помощью regex. Таким образом, предполагаемый результат:
Text outside any div more text outside divs
Первая идея будет соответствовать всем. Следующее регулярное выражение соответствует тегам div со свойствами (стиль, идентификатор и т. Д.):
/<div[^>]*>.*<\/div>/sig
Проблема, конечно, в том, что это будет соответствовать всем, начиная с начала первого «<div» и последнего «</ div>», поэтому он будет также соответствовать «больше текста за пределами div» (здесь: https: / /regex101.com/r/iR8mY2/1 ), который не хочет, чтобы мы (я) хотели.
Это можно решить с помощью модификатора U (Ungreedy)
/<div[^>]*>.*<\/div>/sigU
но тогда у нас будет проблема с меньшим, чем мы хотим: он будет соответствовать только от первого «<div» до первого «» (поэтому, если мы удалим совпадения, помимо некоторых несогласованных тегов, будет иметь текст " Я в ловушке в div! », Чего мы не хотим).
Итак, я нашел решение, которое работает как шарм для вложенных круглых скобок, квадратных скобок и т. Д .:
/\[([^\[\]]*+|(?R))*\]/si
В основном, это то, что нужно найти квадратную скобку открытия, а затем сопоставить что-либо *, которое не является ни открыванием, ни закрывающей квадратной скобкой * ИЛИ рекурсивной структурой этого, находя замыкающую квадратную скобку.
То, что я сейчас работаю, – это плохое решение: в основном, сначала я заменяю все открывающие теги квадратной скобкой (которая не может быть в моем коде, по другим причинам), затем закрывающий тег для закрывающей квадратной скобки, а затем я используйте предыдущее регулярное выражение. Я знаю, что это не очень элегантное решение.
Дело в том, что я действительно хочу знать, как это можно сделать только с одним регулярным выражением. Кажется очевидным, чем замена в предыдущем регулярном выражении «[» и «]» тегами html должна работать. Но это не так просто. Проблема заключается в отрицании символов («[^ …….]» не работает для строк типа «div». Похоже, что нечто подобное может быть достигнуто следующим образом:
.+?(?=<div>)
и, конечно же, то же самое для закрывающего тега
.+?(?=<\/div>
Вот как, более или менее, я пришел в это регулярное выражение
/<div((.+?(?=<\/div>)|.+?(?=<div>))|(?R))*<\/div>/gis
Который работает точно так же, как и первое регулярное выражение, которое я представил ранее: https://regex101.com/r/yU8pV3/1
Итак, вот мой вопрос: что не так с этим регулярным выражением ?
Спасибо!
Поскольку вопрос встречается с положительной реакцией, я отправлю ответ, объясняющий, что не так с вашим подходом, и покажет, как сопоставить текст, который не является конкретным текстом.
ОДНАКО , я хочу подчеркнуть: не используйте это для анализа реального, произвольного HTML-кода, поскольку регулярное выражение должно использоваться только в обычном тексте.
В вашем регулярном выражении содержится <div((.+?(?=<\/div>)|.+?(?=<div>))|(?R))*
часть ( <div((.+?(?=<\/?div>))|(?R))*
же, как <div((.+?(?=<\/?div>))|(?R))*
) перед сопоставлением закрывающей части <\/div>
. Когда у вас есть текст с разделителями, не полагайтесь на обычное ленивое / жадное сопоставление точек (если только не используется для разворачивания структуры цикла – когда вы знаете, что делаете). Что он делает, так это:
<div
– соответствует <div
буквально (также, в <diverse
из-за недостающей границы слова или а \s
после него) (
– Группа 1, которая соответствует:
(.+?(?=<\/div>)|.+?(?=<div>))
– соответствует любым символам 1+ (как можно меньше) до первого </div>
или к первому <div>
|
(?R)
– Recurse (т.е. вставка и использование) )*
– повторить группу 1 ноль или более раз. Проблема очевидна: часть (.+?(?=<\/?div>))
не исключает соответствия <div>
или </div>
, эта ветка ДОЛЖНА только соответствовать тексту NOT EQUAL для ведущих и конечных разделителей ,
Для сопоставления текста, отличного от некоторого определенного текста, используйте умеренный жадный токен .
<div\b[^<]*>((?:(?!<\/?div\b).)+|(?R))*<\/div>\s* ^^^^^^^^^^^^^^^^^^^
См. Демо-версию regex . Обратите внимание, что вы должны использовать модификатор DOTALL, чтобы иметь возможность сопоставлять текст по линиям новой строки. Группа захвата является избыточной, ее можно удалить.
Важно то, что (?:(?!<\/?div\b).)+
только 1 или более символам, которые не являются начальным символом последовательностей <div....>
или </div
. См. Мой выше связанный поток о том, как это работает.
Что касается производительности, умеренные жадные жетоны являются ресурсоемкими. Развернуть петлевую технику приходит на помощь:
<div\b[^<]*>(?:[^<]+(?:<(?!\/?div\b)[^<]*)*|(?R))*<\/div>\s*
См. Эту демонстрацию regex
Теперь токен выглядит как [^<]+(?:<(?!\/?div\b)[^<]*)*
: 1 + символы, отличные от <
за которыми следуют 0+ последовательности <
которые не соблюдаются с /div
или div
(как целое слово), а затем снова 0+ не <
s.
<div\b
все еще может совпадать с <div-tmp
, поэтому, возможно, <div(?:\s|>)
– лучший способ справиться с этим с помощью регулярного выражения. Тем не менее, разбор HTML с DOM намного проще .