RegEx:紧接在最后一个打开的括号之后的文本

时间:2013-06-12 12:24:18

标签: regex delphi

我对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(非常感谢新人)帮助我在下面的RegEx的最后一个左括号前找到文本:

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

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

任何人都可以帮忙吗?

2 个答案:

答案 0 :(得分:6)

这类似于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>parenthesized)[)]。这种技术称为"unrolling-the-loop"。它用于匹配任何具有结构

的东西
nnnsnnsnnnnsnnsnn

nnormal匹配且sspecial匹配。

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

另请注意,我的模式不会为正确的括号模式提供空字符串,但会失败。你可以通过省略外观来解决这个问题。实际上并不需要后视,因为引擎总会返回最左边的匹配。

编辑:根据您的两个示例判断,在最后一个不匹配的括号之后,您实际上并不想要所有,而只需要在第一个逗号之前的所有内容。您可以使用我的结果并在,上拆分或尝试Bohemian的回答。

进一步阅读:

编辑:我注意到您在问题中提到您正在使用Object Pascal。在这种情况下,您可能实际上并未使用PCRE,这意味着不支持递归。在这种情况下,问题可能没有完整的正则表达式解决方案。如果我们施加一个限制,例如“在最后一个不匹配的括号之后只能有一个嵌套级别”(如在所有示例中那样),那么我们就可以提出一个解决方案。同样,我将使用“展开循环”来匹配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

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

答案 1 :(得分:1)

使用展望未来:

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

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