Если еще в рекурсивное регулярное выражение не работает, как ожидалось
Я использую регулярное выражение для разбора некоторого BBCode, поэтому регулярное выражение должно работать рекурсивно, чтобы также соответствовать тегам внутри других. Большая часть кода BBCode имеет аргумент, и иногда он цитируется, хотя и не всегда.
Упрощенный эквивалент регулярного выражения, которое я использую (с тегами стиля html для уменьшения необходимого экранирования), таков:
'~<(")?a(?(1)1)> #Match the tag, and require a closing quote if an opening one provided
([^<]+ | (?R))* #Match the contents of the tag, including recursively
</a>~x'
Однако, если у меня есть тестовая строка, которая выглядит следующим образом:
<"a">Content<a>Also Content</a></a>
Он соответствует только <a>Also Content</a>
, потому что когда он пытается соответствовать из первого тега, то первая группа соответствия, 1
, устанавливается в "
, и это не перезаписывается, когда регулярное выражение выполняется рекурсивно для соответствия внутреннему тегу, что означает, что, поскольку оно не заключено в кавычки, оно не совпадает и это регулярное выражение не выполняется.
Если вместо этого я последовательно использую или не использую кавычки, это работает нормально, но я не могу быть уверен, что это будет иметь место с содержанием, которое я должен разобрать. Есть ли какой-нибудь способ обойти это?
Полное регулярное выражение, которое я использую, чтобы соответствовать [spoiler]content[/spoiler]
, [spoiler=option]content[/spoiler]
и [spoiler="option"]content[/spoiler]
, есть
"~[spoilers*+ #Match the opening tag
(?:=s*+("|')?((?(1)(?!\1).|[^]]){0,100})(?(1)\1))?+s*] #If an option exists, match that
(?: *(?:n|<br />))?+ #Get rid of an extra new line before the start of the content if necessary
((?:[^[n]++ #Capture all characters until the closing tag
|n(?![spoiler]) Capture new line separately so backtracking doesn't run away due to above
|[(?!/?spoiler(?:s*=[^]*])?) #Also match all tags that aren't spoilers
|(?R))*+) #Allow the pattern to recurse - we also want to match spoilers inside spoilers,
# without messing up nesting
n? #Get rid of an extra new line before the closing tag if necessary
[/spoiler] #match the closing tag
~xi"
Есть еще пара других ошибок с ним, хотя.
2 ответа:
Самое простое решение-использовать вместо этого альтернативы:
Но если вы действительно не хотите повторять эту часть<(?:a|"a")> ([^<]++ | (?R))* </a>
a
, Вы можете сделать следующее:<("?)a\1> ([^<]++ | (?R))* </a>
Я только что поместил условное
?
внутрь группы. На этот раз группа захвата всегда совпадает, но совпадение может быть пустым, и условное условие больше не нужно.Примечание: я применил притяжательный Квантор к
[^<]
, чтобы избежать катастрофическое отступление .
В вашем случае я считаю, что лучше сопоставить общий тег, чем конкретный. Сопоставьте все теги, а затем решите в своем коде, что делать с этим совпадением.
Вот полное регулярное выражение:
Обратите внимание, что я добавил опцию\[ (?<tag>\w+) \s* (?:=\s* (?: (?<quote>["']) (?<arg>.{0,100}?) \k<quote> | (?<arg>[^\]]+) ) )? \] (?<content> (?:[^[]++ | (?R) )*+ ) \[/\k<tag>\]
J
(PCRE_DUPNAMES
), чтобы иметь возможность использовать(?<arg>
...)
дважды.
(?(1)...)
проверяет только, определена ли группа 1, поэтому условие истинно, как только группа определена в первый раз. Вот почему вы получаете этот результат (он не связан с уровнем рекурсии или чем-то еще).Поэтому, когда
<a>
достигается в рекурсии, механизм регулярных выражений пытается соответствовать<a">
и терпит неудачу.Если вы хотите использовать условный оператор, вы можете написать
<("?)a(?(1)\1)>
вместо этого. Таким образом, Группа 1 переопределяется каждый раз.Очевидно, вы можете написать свой паттерн более эффективным способом, как это:
~<(?:a|"a")>[^<]*+(?:(?R)[^<]*)*+</a>~
Для вашей конкретной задачи я буду использовать такой шаблон, чтобы соответствовать любым тегам:
$pattern = <<<'EOD' ~ \[ (?<tag>\w+) \s* (?: = \s* (?| " (?<option>[^"]*) " | ' ([^']*) ' | ([^]\s]*) ) # branch reset feature )? \s* ] (?<content> [^[]*+ (?: (?R) [^[]*)*+ ) \[/\g{tag}] ~xi EOD;
Если вы хотите наложить определенный тег на уровне земли, вы можете добавить
(?(R)|(?=spoiler\b))
перед именем тега.