如果递归正则表达式中的else未按预期工作


If-else in recursive regex not working as expected

我使用正则表达式来解析一些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))