我使用regex来解析一些bbcode,因此regex必须递归地工作以匹配其他代码中的标记。大部分bbcode都有一个参数,有时它会被引用,尽管并不总是这样。
与我正在使用的regex(使用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]
的完整regex是"~\[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"
不过,还有一些其他的漏洞。
最佳答案
最简单的解决方案是使用替代方案:
<(?:a|"a")>
([^<]++ | (?R))*
</a>
但如果你真的不想重复这一部分,你可以做以下事情:
<("?)a\1>
([^<]++ | (?R))*
</a>
Demo
我刚刚把条件
a
放在组内。这一次,捕获组总是匹配,但是匹配可以是空的,并且不再需要条件。旁注:我对
?
使用了所有格量词来避免catastrophic backtracking。在你的例子中,我相信匹配一个通用的标签比匹配一个特定的标签要好。匹配所有标记,然后在代码中决定如何处理匹配。
这里有一个完整的正则表达式:
\[
(?<tag>\w+) \s*
(?:=\s*
(?:
(?<quote>["']) (?<arg>.{0,100}?) \k<quote>
| (?<arg>[^\]]+)
)
)?
\]
(?<content>
(?:[^[]++ | (?R) )*+
)
\[/\k<tag>\]
Demo
注意,我添加了
[^<]
选项(J
),以便能够使用PCRE_DUPNAMES
…(?<arg>
两次。