Как исправить регулярное выражение BBcode

У меня есть регулярное выражение, которое захватывает теги BBcode. Работает отлично, за исключением небольшого глюка.

Вот текущее выражение:

\[([^=\[\]]+)[=\x22']*([^ \[\]]*)['\x22]*\](.+)\[/\1\]

Вот некоторый текст, с которым он успешно сопоставляется, и группы, которые он создает:

[url=http://www.google.com]Перейти в Google![/url]< br> 1: URL
2: http://www.google.com
3: Иди в гугл!

[img]http://www.somesite.com/someimage.jpg[/img]
1: img
2: NULL
3: http://www.somesite.com/someimage.jpg

[quote][quote]первая вложенная кавычка[/quote][quote]вторая вложенная кавычка[/quote][/quote]
1: кавычка
2: NULL
3: [quote]первая вложенная кавычка [/quote][quote]вторая вложенная цитата[/quote]

Все это здорово. Я могу обрабатывать вложенные теги, запуская третью группу совпадений с тем же регулярным выражением и рекурсивно обрабатывать все вложенные теги. Проблема в примере с использованием тегов [quote]. Обратите внимание, что третья группа совпадений представляет собой набор из двух тегов кавычек, поэтому мы ожидаем два совпадения. Однако мы получаем одно совпадение, например:

[quote]первая вложенная кавычка[/quote][quote]вторая вложенная кавычка[/quote]
1: кавычка
2: NULL
3: первая вложенная кавычка[/quote][quote]вторая вложенная кавычка

Аааа! Это совсем не то, чего мы хотели. Есть довольно простой способ исправить это, я изменяю регулярное выражение следующим образом:

\[([^=\[\]]+)[=\x22']*([^ \[\]]*)['\x22]*\](.+)\[/\1\]

К этому:

\[([^=\[\]]+)[=\x22']*([^ \[\]]*)['\x22]*\](((?!\[/\1\]).)+)\[/\1\]

Добавляя ((?!\[/\1\]).), мы аннулируем все совпадение, если 3-я группа совпадений содержит закрывающий тег BBcode. Итак, теперь это работает, мы получаем два совпадения:

[quote]первая вложенная цитата[/quote][quote]вторая вложенная цитата[/quote]

[quote]первая вложенная цитата[/quote]
1: цитата
2: NULL
3: первая вложенная цитата

[quote]вторая вложенная кавычка[/quote]
1: цитата
2: NULL 3: вторая вложенная кавычка

Я был счастлив, что исправил это, но теперь у нас другая проблема. Это новое регулярное выражение терпит неудачу в первом, где мы вкладываем два тега цитаты в один тег цитаты большего размера. Получаем два совпадения вместо одного:

[quote][quote]первая вложенная цитата[/quote][quote]вторая вложенная цитата[/quote][/quote]

[quote][quote]первая вложенная кавычка[/quote]
1: кавычка
2: NULL
3: [quote]первая вложенная кавычка

[quote]вторая вложенная кавычка[/quote]
1: кавычка
2: NULL
3: вторая вложенная кавычка

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

Какие-либо предложения? Если я смогу преодолеть этот пробел, у меня должно получиться довольно мощное выражение BBcode.


person Chev    schedule 10.08.2011    source источник


Ответы (1)


Используя группы балансировки, вы можете создать регулярное выражение следующим образом:

(?>
  \[ (?<tag>[^][/=\s]+) \s*
  (?: = \s* (?<val>[^][]*) \s*)?
  ]
)

(?<content>
  (?>
    \[(?<innertag>[^][/=\s]+)[^][]*]
    |
    \[/(?<-innertag>\k<innertag>)]
    |
    [^][]+
  )*
  (?(innertag)(?!))
)

\[/\k<tag>]

Упрощено по примеру Коби.


В следующих:

[foo=bar]baz[/foo]
[b]foo[/b]
[i][i][foo=bar]baz[/foo]foo[/i][/i]
[i][i][i][i]foo[/i][/i][/i][i][i]foo[/i][/i][/i]
[quote][quote][b][img]foo[/img][b]bold[/b][b][b]deep[/b][/b][/b][/quote]bar[quote]baz[/quote][/quote]

Он находит эти совпадения:

  • [foo=bar]baz[/foo]
  • [b]foo[/b]
  • [i][i][foo=bar]baz[/foo]foo[/i][/i]
  • [i][i][i][i]foo[/i][/i][/i][i][i]foo[/i][/i][/i]
  • [quote][quote][b][img]foo[/img][b]bold[/b][b][b]deep[/b][/b][/b][/quote]bar[quote]baz[/quote][/quote]

Полный пример на http://ideone.com/uULOs

(Старая версия http://ideone.com/AXzxW)

person Qtax    schedule 11.08.2011
comment
Хороший. Вы можете немного упростить его и исключить некоторые вложенные квантификаторы: ideone.com/AXzxW . Если вы не возражаете против пробелов и слов между тегами, вы можете еще больше упростить его: ideone.com/82lLX . Да, мне нравятся балансирующие группы. - person Kobi; 12.08.2011
comment
Какая у вас первая ссылка? Вы вставили мою исходную ссылку. Ваш второй пример (даже если он не охватывает необходимые части) - хороший способ его структурирования. И да, балансирующие группы — хорошая функция. - person Qtax; 12.08.2011
comment
Ой, извини. Это должен был быть этот: ideone.com/cvlNM. Что касается второго - по общему признанию, есть место для улучшения. Простого (?=\[) на старте достаточно для захвата только тегов, а также можно добавить несколько групп для захвата имен и содержимого других тегов — .Net позволяет тонко настраивать свои захваты. - person Kobi; 12.08.2011
comment
Коби, +1, гораздо лучший способ написать это. Я включу это в ответ, если вы не возражаете. :-) - person Qtax; 12.08.2011
comment
Отличный ответ. Работает идеально. Как вы, ребята, всему этому учитесь? Если я хочу стать гуру регулярных выражений .NET, куда мне идти? - person Chev; 12.08.2011
comment
@Alex, прочитайте руководство, изучая каждую функцию. Хотя я думаю, что единственное, что особенного в регулярном выражении .NET, - это эти группы балансировки (наложение захватов) (и отсутствие некоторых функций). Я видел, что у Коби в блоге есть несколько хороших примеров балансирующих групп, которые вы можете проверить. Я думаю, «Освоение регулярных выражений» тоже может быть хорошим чтением. - person Qtax; 13.08.2011
comment
@Qtax, что вы имеете в виду, когда читаете руководство? Какой мануал? - person Chev; 13.08.2011
comment
@Alex, я имел в виду документацию по регулярному выражению .NET. - person Qtax; 13.08.2011