使用Delphi的TRegex

时间:2015-06-12 13:39:23

标签: regex delphi

我写了一个正则表达式,其工作是将所有匹配返回到其三个备选捕获组。我的目标是了解每个匹配产生哪个捕获组。 PCRE似乎能够产生这些信息。但是我还没有能够强制Delphi XE8中的TRegEx类来为匹配产生有意义的捕获组信息。我不能声称自己是正则表达式的主管,TRegEx对我来说是新的,所以谁知道我正在犯的错误。

正则表达式(regex101.com workpad)是:

(?'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的{​​{1}}显示有关群组的奇怪结果:所有匹配都有多个群组(2,3或4)与每个群组的TMatchCollection为真,并且匹配的文本通常在多个组中重复或为空。所以这个数据结构(下面)不是我所期望的:

Success

我的简单测试运行器是:

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. 被破坏了,我当然可以使用另一种选择 - 或者我可以放弃解决方案的优雅,而是使用三个更简单的测试来找到我需要的信息。

增加了信息和解释

正如@ andrei-galatyn所说,TRegEx使用TRegEx进行工作。所以我在我的测试程序(下面的输出)中添加了一个部分,我也在那里进行实验。它不像TPerlRegEx那样方便,但它的结果应该是 - 并且没有TRegEx破坏的TRegEx数据结构的问题。无论我使用哪个类,最后一组的索引(TRegEx少于1)都是我想要的捕获组。

一路上我被提醒说Pascal数组通常基于1而不是0。

TGroup

2 个答案:

答案 0 :(得分:4)

内部Delphi使用类TPerlRegEx,它对GroupCount属性有这样的描述:

存储在Groups数组中的匹配组数。此数字是正则表达式中实际参与最后一次匹配的编号最高的捕获组的编号。它可能小于正则表达式中的捕获组数。

E.g。当正则表达式#34;(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;

它没有回答为什么群体充满了奇怪的数据,实际上似乎足以回答你的问题。 :)

答案 1 :(得分:0)

我意识到这已经太迟了4年,但是对于其他可能会搜索到它的人来说,这就是我发现的内容(至少在Delphi RIO中,我没有测试较早的版本,但是下面的来源URL表示为XE),但是:

https://www.regular-expressions.info/delphi.html(重要的部分用粗斜体表示)

  

TMatch记录提供了一些属性,其中包含有关   比赛。成功指示是否找到匹配项。如果这是False,则全部   其他属性和方法无效。值返回匹配的   串。索引和长度指示输入字符串中的位置,并且   比赛的时间长度。群组会传回TGroupCollection记录,   将每个TGroup记录存储在其默认Item []属性中   捕获组。您可以使用Item []的数字索引进行编号   捕获组, 和命名捕获组的字符串索引

例如,如果您这样命名您的匹配组:

(?'MatchName'[^\/\?]*)

哪个字符串匹配到/或?字符,然后可以按如下名称引用该捕获组:

Matches := TRegEx.Matches(StrToMatch, RegexString, [ roExplicitCapture ] );
for Match in Matches do begin
  // ... maybe some logic here to determine if this is the match you want ...
  try
    whatGotMatched := Match.Groups['MatchName'].Value;
  except
    whatGotMatched := '';
  end;
end;

您如何精确地进入“比赛”中的比赛组取决于您的RegEx等结构,创建的比赛数等。我将Match.Groups['MatchName'].Value放在try-except块中,因为如果比赛找不到,您将在访问.Value时生成超出范围的索引错误,因为它仍然会说匹配将在字符X处进行,为0个字符。您还可以在尝试访问Match.Groups['MatchName'].Length之前检查.Value是否为0以获得更好的结果...但是,如果您按名称引用它,则不必专门破译哪些组与哪些匹配,等等。 ..只需索取想要的命名匹配项,如果匹配,就可以得到它。

编辑:它们引用.Length以帮助在抛出关于索引的错误超出范围之前进行验证。但是,如果您的搜索是在不匹配命名搜索的情况下构造的,因此根本没有返回,则对于无效的匹配名称,您将收到错误提示,因此您可能仍需要为此进行捕获。