使用相同的组</thispartonly>捕获<thispartonly>和(thisPartOnly)

时间:2010-07-02 12:46:14

标签: java regex capturing-group

假设我们有以下输入:

<amy>
(bob)
<carol)
(dean>

我们还有以下正则表达式:

<(\w+)>|\((\w+)\)

现在我们得到两场比赛(as seen on rubular.com):

  • <amy>匹配,\1捕获amy\2失败
  • (bob)匹配,\2捕获bob\1失败

这个正则表达式完成了我们想要的大部分内容,包括:

  • 它恰当地匹配打开和关闭括号(即没有混合)
  • 它捕捉了我们感兴趣的部分

然而,它确实有一些缺点:

  • 重复捕获图案(即“主”部分)
    • 在这种情况下只有\w+,但一般来说这可能非常复杂,
      • 如果涉及反向引用,则必须为每个备用重新编号!
      • 重复使维护成为一场噩梦! (如果它改变了怎么办?)
  • 这些组基本上是重复的
    • 根据哪些备用匹配,我们必须查询不同的组
      • 在这种情况下只有\1\2,但通常“主要”部分可以拥有自己的捕获组!
    • 这不仅不方便,而且可能存在不可行的情况(例如,当我们使用的自定义正则表达式框架仅限于查询一个组时)
  • 如果我们还希望匹配{...}[...]等,情况会很快恶化。

所以问题很明显:如何在不重复“主要”模式的情况下实现这一目标?

  

注意:大多数情况下我对java.util.regex味道感兴趣,但欢迎其他口味。


附录

这部分没有什么新内容;它只用一个例子说明了上面提到的问题。

让我们将上面的例子带到下一步:我们现在想要匹配这些:

<amy=amy>
(bob=bob)
[carol=carol]

但不是这些:

<amy=amy)   # non-matching bracket
<amy=bob>   # left hand side not equal to right hand side

使用替代技术,我们有以下工作(as seen on rubular.com):

<((\w+)=\2)>|\(((\w+)=\4)\)|\[((\w+)=\6)\]

如上所述:

  • 主要模式不能简单重复;反向引用必须重新编号
  • 重复也意味着维护噩梦,如果它发生了变化
  • 根据哪些备用匹配,我们必须查询\1 \2\3 \4\5 \6

6 个答案:

答案 0 :(得分:5)

在进行真正的比赛之前,您可以使用前瞻来“锁定”组号。

String s = "<amy=amy>(bob=bob)[carol=carol]";
Pattern p = Pattern.compile(
  "(?=[<(\\[]((\\w+)=\\2))(?:<\\1>|\\(\\1\\)|\\[\\1\\])");
Matcher m = p.matcher(s);

while(m.find())
{
  System.out.printf("found %s in %s%n", m.group(2), m.group());
}

输出:

found amy in <amy=amy>
found bob in (bob=bob)
found carol in [carol=carol]

它仍然很难看,但每次进行更改时都不必重新计算所有组号。例如,要添加对大括号的支持,它只是:

"(?=[<(\\[{]((\\w+)=\\2))(?:<\\1>|\\(\\1\\)|\\[\\1\\]|\\{\\1\\})"

答案 1 :(得分:3)

在preg(Perl Regex库)中,这将与您的示例匹配,\3将捕获内部:

((<)|\()(\w+)(?(2)>|\))

但它在JS中不起作用 - 你没有指定方言......

它取决于条件运算符(?(2)...|...),它基本上表示如果2是非空捕获,则在管道之前匹配,否则在管道之后匹配。在这种形式中,管道不是交替(“或”)。

UPDATE 抱歉,我完全错过了Java位:)无论如何,显然Java不支持条件构造;我不知道我该怎么做:(

另外,对于你的附录(即使它是错误的方言):

(?:(<)|(\()|\[)(\w+)=\3(?(1)>|(?(2)\)|]))

这个名字再次出现在\3中(我摆脱了第一个捕获的paren,但是我不得不再添加一个用于额外开放的paren检查)

答案 2 :(得分:3)

我能够想出的唯一解决方案的灵感来自于在不同的替代品上捕获空字符串的技术;稍后对这些组的反向引用可以作为伪条件。

因此,此模式适用于第二个示例(as seen on rubular.com):

                  __main__
                 /        \
(?:<()|\(()|\[())((\w+)=\5)(\1>|\2\)|\3\])
\_______________/          \_____________/
    \1   \2   \3

因此,对于每个左括号,我们分配一个捕获空字符串的组。然后,当我们尝试匹配结束括号时,我们会看到哪个组成功,并匹配相应的结束括号。

不必重复“主要”部分,但在Java中,反向引用可能必须重新编号。这在支持命名组的风格中不会出现问题。

答案 3 :(得分:0)

当你得到这样的东西时,使用一个正则表达式是一个愚蠢的限制,我只是不同意你的“维护噩梦”使用不止一个 - 重复一个相似但不同的表达可能几次比单个过于复杂的正则表达式更多可维护(嗯,更不可维护),甚至可能更好的性能。

但无论如何,如果你只是使用变量来组成你的正则表达式,那就没有重复了。

这是一些伪代码:

Brackets = "<>,(),[]"
CoreRegex = "(\w+)=\1"

loop CurBracket in Brackets.split(',')
{
    Input.match( Regex.quote(CurBracket.left(1)) & CoreRegex & Regex.quote(CurBracket.right(1)) )
}


(p.s.这只是为了提供一般性的想法 - 我可能在实际实现中使用已经转义的数组作为括号集。)

答案 4 :(得分:0)

Perl的这个例子可能会让你感兴趣:

$str = q/<amy=amy> (bob=bob) [carol=carol] <amy=amy) <amy=bob>/;
$re = qr/(?:<((\w+)=\2)>|\(((\w+)=\4)\)|\[((\w+)=\6)\])+/;
@list = ($str =~ /$re/g);
for(@list) {
    say $i++," = ",$_;
}

我只是通过(?:regex)+

包围你的正则表达式

答案 5 :(得分:0)

假设没有简单的方法来手动编写这个正则表达式,为什么不把它留给计算机呢? 你可以有一个函数,也许就像下面一样(我在这里使用C#语法,因为我对这里的正则表达式比在Java中更熟悉,但是将它改编为Java应该不会太困难。)

请注意,我将函数AdaptBackreferences()或多或少未实现的作为练习留给读者。它应该只调整反向引用编号。

    struct BracketPair {public string Open; public string Close;};

    static string[] MatchTextInBrackets(string text, string innerPattern, BracketPair[] bracketPairs) {
        StringBuilder sb  = new StringBuilder();

        // count number of catching parentheses of innerPattern here:
        int numberOfInnerCapturingParentheses = Regex.Match("", innerPattern).Groups.Count - 1;

        bool firstTime = true;
        foreach (BracketPair pair in bracketPairs) {
            // apply logic to change backreference numbering:
            string adaptedInnerPattern = AdaptBackreferences(innerPattern);
            if (firstTime) { firstTime = false; } else { sb.Append('|'); }
            sb.Append(pair.Open).Append("(").Append(adaptedInnerPattern).Append(")").Append(pair.Close);
        }
        string myPattern = sb.ToString();
        MatchCollection matches = Regex.Matches(text, myPattern);
        string[] result = new string[matches.Count];
        for(int i=0; i < matches.Count; i++) {
            StringBuilder mb = new StringBuilder();
            for(int j=0; j < bracketPairs.Length; j++) {
                mb.Append(matches[i].Groups[1 + j * (numberOfInnerCapturingParentheses + 1)]); // append them all together, assuming all exept one are empty
            }
            result[i] = mb.ToString();
        }
        return result;
    }

    static string AdaptBackreferences(string pattern) { return pattern; } // to be written