Подтвердить что ты не робот

Получить, какая группа захвата соответствовала результату, используя Delphi TRegex

Я написал регулярное выражение, задачей которого является возвращение всех совпадений к трем альтернативным группам захвата. Моя цель - узнать, какая группа захвата произвела каждый матч. PCRE, похоже, может предоставить эту информацию. Но я еще не смог принудить класс TRegEx в Delphi XE8 получить значимую информацию группы захвата для совпадений. Я не могу утверждать, что возглавляю класс регулярных выражений, а TRegEx для меня новичок, поэтому кто знает, какие ошибки я делаю.

Регулярное выражение (команда regex101.com):

(?'word'\b[a-zA-Z]{3,}\b)|(?'id'\b\d{1,3}\b)|(?'course'\b[BL]\d{3}\b)

Этот тестовый текст:

externship L763 clinic 207 B706 b512

дает пять совпадений в тестовых средах. Но простая тестовая программа, которая проходит TGroupCollection каждого TMatch в TMatchCollection, показывает странные результаты о группах: все совпадения имеют более одной группы (2, 3 или 4) с каждой группой Success true и часто совпадающий текст дублируется в нескольких группах или пуст. Таким образом, эта структура данных (ниже) не то, что я ожидаю:

Using TRegEx
Regex: (?'word'\b[a-zA-Z]{3,}\b)|(?'id'\b\d{1,3}\b)|(?'course'\b[BL]\d{3}\b)
Text: externship L763 clinic 207 B706 b512

5 matches
 'externship' with 2 groups:
    length 10 at 1 value 'externship' (Sucess? True)
    length 10 at 1 value 'externship' (Sucess? True)
 'L763' with 4 groups:
    length 4 at 12 value 'L763' (Sucess? True)
    length 0 at 1 value '' (Sucess? True)
    length 0 at 1 value '' (Sucess? True)
    length 4 at 12 value 'L763' (Sucess? True)
 'clinic' with 2 groups:
    length 6 at 17 value 'clinic' (Sucess? True)
    length 6 at 17 value 'clinic' (Sucess? True)
 '207' with 3 groups:
    length 3 at 24 value '207' (Sucess? True)
    length 0 at 1 value '' (Sucess? True)
    length 3 at 24 value '207' (Sucess? True)
 'B706' with 4 groups:
    length 4 at 28 value 'B706' (Sucess? True)
    length 0 at 1 value '' (Sucess? True)
    length 0 at 1 value '' (Sucess? True)
    length 4 at 28 value 'B706' (Sucess? True)

Мой простой тестовый бегун:

program regex_tester;
{$APPTYPE CONSOLE}
{$R *.res}
uses
  System.SysUtils,
  System.RegularExpressions,
  System.RegularExpressionsCore;

var
  Matched     : Boolean;
  J           : integer;
  Group       : TGroup;
  Match       : TMatch;
  Matches     : TMatchCollection;
  RegexText,
  TestText    : String;
  RX          : TRegEx;
  RXPerl      : TPerlRegEx;

begin
  try
    RegexText:='(?''word''\b[a-zA-Z]{3,}\b)|(?''id''\b\d{1,3}\b)|(?''course''\b[BL]\d{3}\b)';
    TestText:='externship L763 clinic 207 B706 b512';

    RX:=TRegex.Create(RegexText);

    Matches:=RX.Matches(TestText);

    Writeln(Format(#10#13#10#13'Using TRegEx'#10#13'Regex: %s'#10#13'Text: %s'#10#13,[RegexText, TestText]));

    Writeln(Format('%d matches', [Matches.Count]));
    for Match in Matches do
    begin
      Writeln(Format(' ''%s'' with %d groups:', [Match.Value,Match.Groups.Count]));

      for Group in Match.Groups do
        Writeln(Format(#9'length %d at %d value ''%s'' (Sucess? %s)', [Group.Length,Group.Index,Group.Value,BoolToStr(Group.Success, True)]));
    end;

    RXPerl:=TPerlRegEx.Create;
    RXPerl.Subject:=TestText;
    RXPerl.RegEx:=RegexText;

    Writeln(Format(#10#13#10#13'Using TPerlRegEx'#10#13'Regex: %s'#10#13'Text: %s'#10#13,[RXPerl.Regex, RXPerl.Subject]));

    Matched:=RXPerl.Match;
    if Matched then
    repeat
      begin
        Writeln(Format(' ''%s'' with %d groups:', [RXPerl.MatchedText,RXPerl.GroupCount]));
        for J:=1 to RXPerl.GroupCount do
          Writeln(Format(#9'length %d at %d, value ''%s''',[RXPerl.GroupLengths[J],RXPerl.GroupOffsets[J],RXPerl.Groups[J]]));

        Matched:=RXPerl.MatchAgain;
      end;
    until Matched=false;

  except
      on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Я, конечно, ценю толчок в правильном направлении. Если TRegEx сломан, я могу, конечно, использовать альтернативу - или я могу отпустить воспринимаемую элегантность решения, вместо этого используя три более простых теста, чтобы найти бит информации, в которой я нуждаюсь.

Добавлена ​​информация и интерпретация

Как отмечает @andrei-galatyn, TRegEx использует TPerlRegEx для своей работы. Поэтому я добавил раздел моей тестовой программы (вывод ниже), где я тоже экспериментирую с этим. Это не так удобно использовать как TRegEx, но его результат - это то, что должно быть, - и без проблем с структурами данных TRegEx, разбитыми на TGroup. Какой бы класс я ни использовал, последний индекс группы (меньше 1 для TRegEx) - это группа захвата, которую я хочу.

По пути мне напомнили, что массивы Pascal часто основаны на 1, а не на 0.

Using TPerlRegEx
Regex: (?'word'\b[a-zA-Z]{3,}\b)|(?'id'\b\d{1,3}\b)|(?'course'\b[BL]\d{3}\b)
Text: externship L763 clinic 207 B706 b512

 'externship' with 1 groups:
    length 10 at 1, value 'externship'
 'L763' with 3 groups:
    length 0 at 1, value ''
    length 0 at 1, value ''
    length 4 at 12, value 'L763'
 'clinic' with 1 groups:
    length 6 at 17, value 'clinic'
 '207' with 2 groups:
    length 0 at 1, value ''
    length 3 at 24, value '207'
 'B706' with 3 groups:
    length 0 at 1, value ''
    length 0 at 1, value ''
    length 4 at 28, value 'B706'
4b9b3361

Ответ 1

Внутренне Delphi использует класс TPerlRegEx и имеет такое описание для свойства GroupCount:

Число сопоставленных групп, хранящихся в массиве Groups. Этот номер - это номер группы захвата с наивысшим номером в вашем регулярном выражении, который фактически участвовал в последнем матче. Это может быть меньше количества групп захвата в вашем регулярном выражении.

например. когда регулярное выражение "(a) | (b)" соответствует "a", GroupCount будет равно 1. Если одно и то же регулярное выражение соответствует "b", GroupCount будет равно 2.

Класс TRegEx всегда добавляет еще одну группу (для всего выражения, которое я предполагаю). В вашем случае должно быть достаточно, чтобы проверить каждый матч следующим образом:

case Match.Groups.Count-1 of
  1: ; // "word" found
  2: ; // "id" found
  3: ; // "course" found
end;

Он не отвечает, почему группы заполнены странными данными, на самом деле этого достаточно, чтобы ответить на ваш вопрос.:)