【问题标题】:RegEx: text immediately after the last opened parenthesis正则表达式:最后一个打开的括号之后的文本
【发布时间】:2013-06-12 12:24:17
【问题描述】:

我对RegEx有点了解,但目前还远远超出我的能力。

我需要帮助才能在最后一个没有匹配右括号的左括号之后立即找到文本/表达式。

用于开发中的开源软件(Object Pascal)的CallTip。

下面是一些例子:

------------------------------------
Text                  I need
------------------------------------
aaa(xxx               xxx
aaa(xxx,              xxx
aaa(xxx, yyy          xxx
aaa(y=bbb(xxx)        y=bbb(xxx)
aaa(y <- bbb(xxx)     y <- bbb(xxx)
aaa(bbb(ccc(xxx       xxx
aaa(bbb(x), ccc(xxx   xxx
aaa(bbb(x), ccc(x)    bbb(x)
aaa(bbb(x), ccc(x),   bbb(x)
aaa(?, bbb(??         ??
aaa(bbb(x), ccc(x))   ''
aaa(x)                ''
aaa(bbb(              ''
------------------------------------

For all text above the RegEx proposed by @Bohemian
(?<=\()(?=([^()]*\([^()]*\))*[^()]*$).*?(?=[ ,]|$)(?! <-)(?<! <-)
matches all cases.

For the below (I found these cases when implementing the RegEx in the software) not
------------------------------------
New text              I need
------------------------------------
aaa(bbb(x, y)         bbb(x, y)
aaa(bbb(x, y, z)      bbb(x, y, z)
------------------------------------

是否可以针对这些情况编写 RegEx (PCRE)?

在上一篇文章 (RegEx: Word immediately before the last opened parenthesis) 中,Alan Moore(非常感谢新来的)帮助我在下面的正则表达式中找到最后一个左括号之前的文本:

\w+(?=\((?:[^()]*\([^()]*\))*[^()]*$)

但是,我无法在之后立即进行适当的调整。

有人可以帮忙吗?

【问题讨论】:

  • 优秀测试用例+1

标签: regex delphi


【解决方案1】:

这类似于this problem。而且由于您使用的是PCRE,使用递归语法,实际上有一个解决方案。

/
(?(DEFINE)                # define a named capture for later convenience
  (?P<parenthesized>      # define the group "parenthesized" which matches a
                          # substring which contains correctly nested
                          # parentheses (it does not have to be enclosed in
                          # parentheses though)
    [^()]*                # match arbitrarily many non-parenthesis characters
    (?:                   # start non capturing group
      [(]                 # match a literal opening (
      (?P>parenthesized)  # recursively call this "parenthesized" subpattern
                          # i.e. make sure that the contents of these literal ()
                          # are also correctly parenthesized
      [)]                 # match a literal closing )
      [^()]*              # match more non-parenthesis characters
    )*                    # repeat
  )                       # end of "parenthesized" pattern
)                         # end of DEFINE sequence

# Now the actual pattern begins

(?<=[(])                  # ensure that there is a literal ( left of the start
                          # of the match
(?P>parenthesized)?       # match correctly parenthesized substring
$                         # ensure that we've reached the end of the input
/x                        # activate free-spacing mode

这个模式的要点显然是parenthesized 子模式。我也许应该详细说明一下。它的结构是这样的:

(normal* (?:special normal*)*)

其中normal[^()]special[(](?P&gt;parenthesized)[)]。这种技术称为"unrolling-the-loop"。它用于匹配任何具有结构的东西

nnnsnnsnnnnsnnsnn

其中nnormal 匹配,sspecial 匹配。

在这种特殊情况下,事情有点复杂,因为我们也使用递归。 (?P&gt;parenthesized) 递归地使用 parenthesized 模式(它是它的一部分)。您可以查看 (?P&gt;...) 语法有点像反向引用 - 除了引擎不会尝试匹配组 ... 匹配的内容,而是再次应用它的子模式。

另外请注意,我的模式不会为您提供正确括号模式的空字符串,但会失败。您可以通过省略后视来解决此问题。向后看实际上是没有必要的,因为引擎总是会返回最左边的匹配项。

编辑:从您的两个示例来看,您实际上并不希望在最后一个不匹配的括号之后的所有内容,而只是在第一个逗号之前的所有内容。您可以使用我的结果并拆分 , 或尝试 Bohemian 的答案。

进一步阅读:

编辑:我注意到您在问题中提到您正在使用 Object Pascal。在这种情况下,您可能实际上并未使用 PCRE,这意味着不支持递归。在这种情况下,该问题就没有完整的正则表达式解决方案。如果我们施加一个限制,例如“在最后一个不匹配的括号之后只能再有一个嵌套级别”(就像在您的所有示例中一样),那么我们可以提出一个解决方案。同样,我将使用“unrolling-the-loop”来匹配 xxx(xxx)xxx(xxx)xxx 形式的子字符串。

(?<=[(])         # make sure we start after an opening (
(?=              # lookahead checks that the parenthesis is not matched
  [^()]*([(][^()]*[)][^()]*)*
                 # this matches an arbitrarily long chain of parenthesized
                 # substring, but allows only one nesting level
  $              # make sure we can reach the end of the string like this
)                # end of lookahead
[^(),]*([(][^()]*[)][^(),]*)*
                 # now actually match the desired part. this is the same
                 # as the lookahead, except we do not allow for commas
                 # outside of parentheses now, so that you only get the
                 # first comma-separated part

如果您曾经添加一个输入示例,例如 aaa(xxx(yyy()),您想在其中匹配 xxx(yyy()),那么这种方法将无法匹配它。事实上,任何不使用递归的正则表达式都无法处理任意嵌套级别。

由于您的正则表达式不支持递归,因此您可能最好不使用正则表达式。即使我的最后一个正则表达式与您当前的所有输入示例匹配,它也确实很复杂,可能不值得麻烦。不如这样:逐个字符地遍历字符串并维护一堆括号位置。然后下面的伪代码为您提供最后一个不匹配的( 之后的所有内容:

while you can read another character from the string
    if that character is "(", push the current position onto the stack
    if that character is ")", pop a position from the stack
# you've reached the end of the string now
if the stack is empty, there is no match
else the top of the stack is the position of the last unmatched parenthesis;
     take a substring from there to the end of the string

要获取直到第一个未嵌套逗号的所有内容,您可以再次遍历该结果:

nestingLevel = 0
while you can read another character from the string
    if that character is "," and nestingLevel == 0, stop
    if that character is "(" increment nestingLevel
    if that character is ")" decrement nestingLevel
take a substring from the beginning of the string to the position at which
  you left the loop

这两个短循环在未来对其他人来说将更容易理解,并且比正则表达式解决方案灵活得多(至少一个没有递归)。

【讨论】:

  • 感谢您的回答。但是,就我对 RegEx 的了解而言,这有点复杂。我使用免费工具(ExpressoEditPadPro)来构建和测试正则表达式。两者都不理解带有 cmets 和多行的 RegEx。拜托,你能写成一行吗?
  • @jcfaria 你只需要删除 cmets 和元素之间的所有空格:(?(DEFINE)(?P&lt;parenthesized&gt;[^()]*(?:[(](?P&gt;parenthesized)[)][^()]*)*))(?&lt;=[(])(?P&gt;parenthesized)?$
  • 我已经完成了,但是这两个工具在这个 RegEx 中都显示了错误:(?P&gt;parenthesized)? 非法组语法,零次或一次重复
  • @jcfaria 那么你的工具没有使用 PCRE 并且不支持递归。事实上,Expresso 使用 .NET 风格(它支持balancing groups),而 EditPad Pro 可能使用与 RegexBuddy 相同的引擎,它也不支持 PCRE 的递归。如果您最终将我的表达式与 PCRE 一起使用,它将起作用。否则不能,因为递归是 PCRE 独有的。此外,如果您既不使用 .NET 也不使用 PCRE,由于嵌套结构,您无法完全解决问题。
  • @jcfaria 我刚刚注意到您在问题中提到您正在使用 Object Pascal。我想您正在使用TRegExpr engine?这不是 PCRE,它只实现了 Perl 正则表达式风格的一个子集。例如,它不支持递归(尽管它也没有提到环视)。我将使用有限的替代方法编辑我的答案。
【解决方案2】:

使用前瞻:

(?<=\()(?=([^()]*\([^()]*\))*[^()]*$).*?(\(.*?\))?(?=[ ,]|$)(?! <-)(?<! <-)

请参阅 this running on rubular 通过问题中发布的所有测试用例。

【讨论】:

  • 这对于在哪里找到结果不太一致。对于输入aaa(y( &lt;- bbb(xxx),结果在捕获组1 中,但对于aaa(bbb(x), ccc(x),结果在组0 中(即实际匹配)。如果所需的结果包含更深的嵌套级别,它也不起作用。
  • @m.buettner 以前的版本只在那个测试用例上失败了。我添加了一个展望来修复它。查看已编辑的答案和 rubular 链接
  • @Bohemian,非常感谢您的关注!您的新 RegEx 版本确实适用于所有原始测试用例。但是,在开发软件中实现您的 RegEx 并对其进行测试时,我只发现了两个不匹配的案例(请参阅编辑的原始问题)。请问,你能做一个小的调整来匹配这些新案例吗?
  • @jcfaria 我做到了!这个正则表达式现在通过了你所有的测试用例。我添加了另一个可选术语,它在目标末尾的括号中抓取任何文本。顺便说一句,这是我编写过的最长的正则表达式 :)
  • @Bohemian,非常感谢您提供这个正则表达式!它确实在所有测试用例中都通过了。我正在阅读的一本书说长正则表达式完全符合我们的要求......似乎就是这样! ;)
猜你喜欢
  • 2012-05-14
  • 1970-01-01
  • 2017-06-28
  • 1970-01-01
  • 1970-01-01
  • 2021-10-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多