Я пытаюсь написать программу, которая проверяет документы, написанные на языке разметки, похожем на BBcode.
Этот язык разметки имеет как совпадающие ([b]bold[/b] text
), так и несоответствующие (today is [date]
) теги. К сожалению, использование другого языка разметки не вариант.
Однако, мое регулярное выражение действует не так, как я хочу. Кажется, что он всегда останавливается на первом соответствующем закрывающем теге, вместо того, чтобы отождествлять этот вложенный тег с рекурсивным (?R)
.
Я использую модуль regex
, который поддерживает (?R)
, а не re
.
Мои вопросы:
Как эффективно использовать рекурсивное регулярное выражение для сопоставления вложенных тегов без завершения первого тега?
Если есть метод лучше, чем регулярное выражение, то что это за метод?
Вот регулярное выражение, как только я его построю:
\[(b|i|u|h1|h2|h3|large|small|list|table|grid)\](?:((?!\[\/\1\]).)*?|(?R))*\[\/\1\]
Вот тестовая строка, которая не работает должным образом:
[large]test1 [large]test2[/large] test3[/large]
(должно соответствовать всей этой строке, но останавливается перед test3)
Вот регулярное выражение на regex101.com: https://regex101.com/r/laJSLZ/1
Этот тест не должен завершаться в миллисекундах или даже секундах, но он должен иметь возможность проверять около 100 файлов длиной от 1000 до 10000 символов в каждый раз за время, разумное для сборки Travis-CI.
Вот как выглядит логика, использующая это регулярное выражение, для контекста:
import io, regex # https://pypi.org/project/regex/
# All the tags that must have opening and closing tags
matching_tags = 'b', 'i', 'u', 'h1', 'h2', 'h3', 'large', 'small', 'list', 'table', 'grid'
# our first part matches an opening tag:
# \[(b|i|u|h1|h2|h3|large|small|list|table|grid)\]
# our middle part matches the text in the middle, including any properly formed tag sets in between:
# (?:((?!\[\/\1\]).)*?|(?R))*
# our last part matches the closing tag for our first match:
# \[\/\1\]
pattern = r'\[(' + '|'.join(matching_tags) + r')\](?:((?!\[\/\1\]).)*?|(?R))*\[\/\1\]'
myRegex = re.compile(pattern)
data = ''
with open('input.txt', 'r') as file:
data = '[br]'.join(file.readlines())
def validate(text):
valid = True
for node in all_nodes(text):
valid = valid and is_valid(node)
return valid
# (Only important thing here is that I call this on every node, this
# should work fine but the regex to get me those nodes does not.)
# markup should be valid iff opening and closing tag counts are equal
# in the whole file, in each matching top-level pair of tags, and in
# each child all the way down to the smallest unit (a string that has
# no tags at all)
def is_valid(text):
valid = True
for tag in matching_tags:
valid = valid and text.count(f'[{tag}]') == text.count(f'[/{tag}]')
return valid
# this returns each child of the text given to it
# this call:
# all_nodes('[b]some [large]text to[/large] validate [i]with [u]regex[/u]![/i] love[/b] to use [b]regex to [i]do stuff[/i][/b]')
# should return a list containing these strings:
# [b]some [large]text to[/large] validate [i]with [u]regex[/u]![/i] love[/b]
# [large]text to[/large]
# [i]with [u]regex[/u]![/i]
# [u]regex[/u]
# [b]regex to [i]do stuff[/i][/b]
# [i]do stuff[/i]
def all_nodes(text):
matches = myRegex.findall(text)
if len(matches) > 0:
for m in matches:
result += all_nodes(m)
return result
exit(0 if validate(data) else 1)