我正在使用正则表达式来解析一些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]
,
"~\[spoiler\s*+ #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"
但是还有其他一些错误。
答案 0 :(得分:3)
最简单的解决方案是改用替代品:
<(?:a|"a")>
([^<]++ | (?R))*
</a>
但如果您真的不想重复a
部分,可以执行以下操作:
<("?)a\1>
([^<]++ | (?R))*
</a>
我刚将条件?
放入群组中。这次,捕获组总是匹配,但匹配可以为空,并且不再需要条件。
旁注:我已将占有量词应用于[^<]
以避免catastrophic backtracking。
在您的情况下,我认为匹配通用标签比使用特定标签更好。匹配所有标记,然后在代码中决定如何处理匹配。
这是一个完整的正则表达式:
\[
(?<tag>\w+) \s*
(?:=\s*
(?:
(?<quote>["']) (?<arg>.{0,100}?) \k<quote>
| (?<arg>[^\]]+)
)
)?
\]
(?<content>
(?:[^[]++ | (?R) )*+
)
\[/\k<tag>\]
请注意,我添加了J
选项(PCRE_DUPNAMES
),以便能够使用(?<arg>
... )
两次。
答案 1 :(得分:1)
(?(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))
。