我使用正则表达式来解析一些BBCode,因此正则表达式必须递归工作,才能匹配其他代码中的标记。大多数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]
的完整正则表达式是
"~'[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"
不过,它还有一些其他的bug。
最简单的解决方案是使用替代方案:
<(?: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>
。。。CCD_ 13两次。
(?(1)...)
只检查组1是否已定义,因此一旦第一次定义组,条件为true。这就是您获得此结果的原因(它与递归级别或其他级别无关)。
因此,当递归中达到<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))
。