Почему Group.Value всегда последняя совпадающая групповая строка? - PullRequest
0 голосов
/ 18 декабря 2009

Недавно я нашел один API C # Regex действительно раздражающим.

У меня есть регулярное выражение (([0-9]+)|([a-z]+))+. Я хочу найти все подходящие строки. Код как ниже.

string regularExp = "(([0-9]+)|([a-z]+))+";
string str = "abc123xyz456defFOO";

Match match = Regex.Match(str, regularExp, RegexOptions.None);
int matchCount = 0;

while (match.Success)
{
    Console.WriteLine("Match" + (++matchCount));

    Console.WriteLine("Match group count = {0}", match.Groups.Count);
    for (int i = 0; i < match.Groups.Count; i++)
    {
        Group group = match.Groups[i];
        Console.WriteLine("Group" + i + "='" + group.Value + "'");
    }

    match = match.NextMatch();
    Console.WriteLine("go to next match");
    Console.WriteLine();
}

Вывод:

Match1
Match group count = 4
Group0='abc123xyz456def'
Group1='def'
Group2='456'
Group3='def'
go to next match

Кажется, что вся group.Value является последней соответствующей строкой ("def" и "456"). Я потратил некоторое время, чтобы понять, что я должен рассчитывать на group.Captures вместо group.Value.

string regularExp = "(([0-9]+)|([a-z]+))+";
string str = "abc123xyz456def";
//Console.WriteLine(str);

Match match = Regex.Match(str, regularExp, RegexOptions.None);
int matchCount = 0;

while (match.Success)
{
    Console.WriteLine("Match" + (++matchCount));

    Console.WriteLine("Match group count = {0}", match.Groups.Count);
    for (int i = 0; i < match.Groups.Count; i++)
    {
        Group group = match.Groups[i];
        Console.WriteLine("Group" + i + "='" + group.Value + "'");

        CaptureCollection cc = group.Captures;
        for (int j = 0; j < cc.Count; j++)
        {
            Capture c = cc[j];
            System.Console.WriteLine("    Capture" + j + "='" + c + "', Position=" + c.Index);
        }
    }

    match = match.NextMatch();
    Console.WriteLine("go to next match");
    Console.WriteLine();
}

Будет выведено:

Match1
Match group count = 4
Group0='abc123xyz456def'
    Capture0='abc123xyz456def', Position=0
Group1='def'
    Capture0='abc', Position=0
    Capture1='123', Position=3
    Capture2='xyz', Position=6
    Capture3='456', Position=9
    Capture4='def', Position=12
Group2='456'
    Capture0='123', Position=3
    Capture1='456', Position=9
Group3='def'
    Capture0='abc', Position=0
    Capture1='xyz', Position=6
    Capture2='def', Position=12
go to next match

Теперь мне интересно, почему дизайн API такой. Почему Group.Value возвращает только последнюю найденную строку? Этот дизайн не выглядит хорошо.

1 Ответ

2 голосов
/ 18 декабря 2009

Основная причина историческая: регулярные выражения всегда работали таким образом, возвращаясь к Perl и не только. Но это не совсем плохой дизайн. Обычно, если вы хотите, чтобы каждое совпадение было таким, вы просто опускаете самый внешний квантификатор (+ в этом случае) и используете метод Matches() вместо Match(). Каждый язык с поддержкой регулярных выражений предоставляет способ сделать это: в Perl или JavaScript вы делаете совпадение в режиме /g; в Ruby вы используете метод scan; в Java вы вызываете find() несколько раз, пока он не вернет false. Точно так же, если вы выполняете операцию замены, вы можете подключить захваченные подстроки обратно, используя заполнители ($1, $2 или \1, \2, в зависимости от языка).

С другой стороны, я не знаю ни одного другого производного от Perl 5 варианта регулярных выражений, который предоставлял бы возможность извлекать промежуточные совпадения групп захвата, как .NET с его CaptureCollections. И я не удивлен: на самом деле очень редко вам действительно нужно захватывать все матчи за один раз. И подумайте обо всех возможностях хранения и / или обработки, которые могут потребоваться для отслеживания всех этих промежуточных совпадений. Это хорошая функция.

...