Окончательное решение для использования regex для удаления вложенных тэгов html того же типа?

Я пытаюсь найти решение с регулярным выражением (прежде чем кто-нибудь скажет: я знаю, что я должен использовать библиотеку документов 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

Итак, вот мой вопрос: что не так с этим регулярным выражением ?

Спасибо!

Solutions Collecting From Web of "Окончательное решение для использования regex для удаления вложенных тэгов html того же типа?"

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ

Поскольку вопрос встречается с положительной реакцией, я отправлю ответ, объясняющий, что не так с вашим подходом, и покажет, как сопоставить текст, который не является конкретным текстом.

ОДНАКО , я хочу подчеркнуть: не используйте это для анализа реального, произвольного 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 для ведущих и конечных разделителей ,

Решение (s)

Для сопоставления текста, отличного от некоторого определенного текста, используйте умеренный жадный токен .

 <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 намного проще .