我使用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>两次。

07-24 17:28