使用.net正则表达式平衡匹配捕获内部项目

时间:2009-09-01 17:55:21

标签: .net regex

我在.net Regexes的平衡匹配中找到了以下资源:

根据我在这些中读到的内容,以下示例应该有效:

这个正则表达式应该在一个角括号组中找到一个“a”,无论多深。它应匹配“<a>”,“<<a>>”,“<a<>>”,“<<>a>”,“<<><a>>”等。

(?<=
    ^
    (
        (
            <(?<Depth>)
            |
            >(?<-Depth>)
        )
        [^<>]*?
    )+?
)
(?(Depth)a|(?!))

匹配字符串“&lt;&lt;&gt; a&gt;”

中的“a”

虽然它适用于字符串“<a<>>”和“<<a>>”,但我无法使其与“&gt;”后面的“a”匹配。

根据我读过的解释,前两个“&lt;”应该增加深度两次,然后是第一个“&gt;”应该减少一次。此时,(?(深度)a |(?!))应该执行“是”选项,但正则表达式从未在此处进行过。

考虑以下正则表达式,它没有进行这样的检查,仍然无法匹配有问题的字符串:

(?<=
    ^
    (
        (
            <(?<Depth>)
            |
            >(?<-Depth>)
        )
        [^<>]*?
    )+?
)
a

我错过了什么,或者正则表达式引擎是否正常工作?

4 个答案:

答案 0 :(得分:5)

如果你想找到一对平衡括号内的每个'a',我会建议这种方法:

Regex r = new Regex(@"
    <
      (?>
         [^<>a]+
       |
         (a)
       |
         <(?<N>)
       |
         >(?<-N>)
      )+
    (?(N)(?!))
    >
", RegexOptions.IgnorePatternWhitespace);
string target = @"012a<56a8<0a2<4a6a>>012a<56789a>23456a";
foreach (Match m in r.Matches(target))
{
  Console.WriteLine("{0}, {1}", m.Index, m.Value);
  foreach (Capture c in m.Groups[1].Captures)
  {
    Console.WriteLine("{0}, {1}", c.Index, c.Value);
  }
}

结果:

9, <0a2<4a6a>>
11, a
15, a
17, a
24, <56789a>
30, a

在捕获可能包含的任何a的过程中,它会继续并匹配整个括号分隔(子)字符串,而不是与条件相关。与您的方法不同,它可以从更大的字符串中提取任意数量的括号中的子字符串,并从每个子字符串中提取任意数量的a

答案 1 :(得分:4)

你需要记住,为了满足嵌入式正则表达式,lookbehind只会扫描尽可能远的地方。您的lookbehind中的表达式只需要匹配一个尖括号,因此它只能看到最新的一个。如果它是 left 尖括号,(?<Depth>)会将空字符串推送到该捕获组所代表的堆栈上。但如果它是正确的尖括号......

  

值得一提的是,如果尝试弹出(&lt; -N&gt;)时没有命名组N,那么它将失败... *

换句话说,它不是条件表达式 - (?(Depth)a|(?!)) - 这使你的正则表达式失败(正如你所观察到的),它是“减少”“计​​数器”的尝试。据我所知,你的正则表达式完全等同于

(?<=<[^<>]*)a

所以,回答你的问题,.NET的平衡构造匹配不会被打破。拜占庭是的,但是坏了,没有。 :d

答案 2 :(得分:1)

通常可以安全地假设数百万人使用的图书馆中的课程没有任何重大错误:D

以下正则表达式将匹配&lt;&gt; a

的所有上述版本
var pattern =  "(" +
                       "((?'Open'<)[a]?)+" +
                       "((?'Close-Open'>)[a]?)+" +
                     ")*" +
                     "(?(Open)(?!))$";

答案 3 :(得分:1)

完全修订的答案(前两个评论是针对先前的,不完整的答案):

我已经想出了如何以一种我可以在结果上“替换所有”的方式来实现这一目标。

string input = @"a<a<<a>>a<a>a>a<a>a";
Regex reg = new Regex(@"
    (?<=
        <
        [^<>]*
        (?(ReverseDepth)(?!))
        (?:
            (?:
                <(?<-ReverseDepth>)
                |
                >(?<ReverseDepth>)
            )
            [^<>]*
        )*
    )
    a
    ", RegexOptions.IgnorePatternWhitespace);
Console.WriteLine(reg.Replace(input, "b"));

这会产生以下输出:

a<b<<b>>b<b>b>a<b>a

我现在意识到我的问题没有说明这一点,但我从来没有真正关心该组是否完全关闭,因为我将要应用它的文本是预先验证的xml。但是,为了匹配我对问题的回答,并阻止"<a"中的'a'匹配,可以使用以下正则表达式代替我在此提供的正则表达式:

Regex reg = new Regex(@"
    (?<=
        <
        [^<>]*
        (?(ReverseDepth)(?!))
        (?:
            (?:
                <(?<-ReverseDepth>)
                |
                >(?<ReverseDepth>)
            )
            [^<>]*
        )*
    )
    a
    (?=
        (?:
            (?:
                <(?<Depth>)
                |
                >(?<-Depth>)
            )
            [^<>]*
        )*
        (?(Depth)(?!))
        [^<>]*
        >
    )
    ", RegexOptions.IgnorePatternWhitespace);