Delphi 如何处理可能为空的TRegEx命名捕获组?

Delphi 如何处理可能为空的TRegEx命名捕获组?,delphi,delphi-10-seattle,Delphi,Delphi 10 Seattle,我有一个带有命名捕获组的正则表达式,其中最后一个组是可选的。当可选组为空时,我不知道如何迭代组并正确处理可选组;我得到一个ElistotofBounds例外 正则表达式解析由外部系统生成的文件,我们通过电子邮件接收该文件,该文件包含有关已发给供应商的支票的信息。该文件以管道分隔;下面的代码中有一个示例 program Project1; {$APPTYPE CONSOLE} uses System.SysUtils, System.RegularExpressions, System.R

我有一个带有命名捕获组的正则表达式,其中最后一个组是可选的。当可选组为空时,我不知道如何迭代组并正确处理可选组;我得到一个ElistotofBounds例外

正则表达式解析由外部系统生成的文件,我们通过电子邮件接收该文件,该文件包含有关已发给供应商的支票的信息。该文件以管道分隔;下面的代码中有一个示例

program Project1;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.RegularExpressions, System.RegularExpressionsCore;
{
  File format (pipe-delimited): 
   Check #|Batch|CheckDate|System|Vendor#|VendorName|CheckAmount|Cancelled (if voided - optional)
}
const 
  CheckFile = '201|3001|12/01/2015|1|001|JOHN SMITH|123.45|'#13 +
              '202|3001|12/01/2015|1|002|FRED JONES|234.56|'#13 +
              '103|2099|11/15/2015|2|001|JOHN SMITH|97.95|C'#13 ;

var
  RegEx: TRegEx;
  MatchResult: TMatch;
begin
  try
    RegEx := TRegEx.Create(
      '^(?<Check>\d+)\|'#10 +
      '  (?<Batch>\d{3,4})\|'#10 +
      '  (?<ChkDate>\d{2}\/\d{2}\/\d{4})\|'#10 +
      '  (?<System>[1-3])\|'#10 +
      '  (?<PayID>[0-9X]+)\|'#10 +
      '  (?<Payee>[^|]+)\|'#10 +
      '  (?<Amount>\d+\.\d+)\|'#10 +
      '(?<Cancelled>C)?$',
      [roIgnorePatternSpace, roMultiLine]);
    MatchResult := RegEx.Match(CheckFile);
    while MatchResult.Success do
    begin
      WriteLn('Check: ', MatchResult.Groups['Check'].Value);
      WriteLn('Dated: ', MatchResult.Groups['ChkDate'].Value);
      WriteLn('Amount: ', MatchResult.Groups['Amount'].Value);
      WriteLn('Payee: ', MatchResult.Groups['Payee'].Value);
      // Problem is here, where Cancelled is optional and doesn't 
      // exist (first two lines of sample CheckFile.)
      // Raises ERegularExpressionError 
      // with message 'Index out of bounds (8)' exception.
      WriteLn('Cancelled: ', MatchResult.Groups['Cancelled'].Value);
      WriteLn('');
      MatchResult := MatchResult.NextMatch;
    end;
    ReadLn;
  except
    // Regular expression syntax error.
    on E: ERegularExpressionError do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
程序项目1;
{$APPTYPE控制台}
使用
System.SysUtils、System.RegularExpressions、System.RegularExpressionsCore;
{
文件格式(以管道分隔):
支票#|批次|支票日期|系统|供应商#|供应商名称|支票金额|已取消(如果作废-可选)
}
常数
检查文件='201 | 3001 | 12/01/2015 | 1 | 001 |约翰·史密斯| 123.45 |''13+
“202 | 3001 | 12/01/2015 | 1 | 002 |弗雷德·琼斯| 234.56 |”+
‘103 | 2099 | 11/15/2015 | 2 | 001 |约翰·史密斯| 97.95 | C’| 13;
变量
正则表达式:TRegEx;
匹配结果:TMatch;
开始
尝试
RegEx:=TRegEx.Create(
“^(?\d+)\”10+
“(?\d{3,4})”10+
“(?\d{2}\/\d{2}\/\d{4})”10+
'  (?[1-3])\|'#10 +
“(?[0-9X]+)\ |”+
'  (?[^|]+)\|'#10 +
“(?\d+\.\d+\\”10+
“(?C)?$”,
[roIgnorePatternSpace,roMultiLine]);
MatchResult:=RegEx.Match(检查文件);
而结果。成功是什么
开始
WriteLn('Check:',MatchResult.Groups['Check'].Value);
WriteLn('Dated:',MatchResult.Groups['ChkDate'].Value);
WriteLn('Amount:',MatchResult.Groups['Amount'].Value);
WriteLn('Payee:',MatchResult.Groups['Payee'].Value);
//问题就在这里,取消是可选的,而不是
//存在(示例检查文件的前两行。)
//升高压力错误
//消息“索引超出范围(8)”异常。
WriteLn('Cancelled:',MatchResult.Groups['Cancelled'].Value);
书面语(“”);
MatchResult:=MatchResult.NextMatch;
结束;
ReadLn;
除了
//正则表达式语法错误。
关于E:eRegularRexpressionError do
Writeln(E.ClassName,“:”,E.Message);
结束;
结束。
我已尝试检查
MatchResult.Groups['Cancelled'].Index
是否小于
MatchResult.Groups.Count
,尝试检查
MatchResult.Groups['Cancelled'].Length>0
,并检查
MatchResult.Groups['Cancelled'].Value'
是否成功


当可选捕获组不匹配时,如何正确处理该组?

如果结果中不存在请求的命名组,则引发
ERegularExpressionError
异常。这是出于设计(尽管异常消息的措辞具有误导性)。如果在
try/except
块之后移动
ReadLn()
,您将在进程退出之前在控制台窗口中看到异常消息。引发异常时,代码未等待用户输入

由于您的其他组不是可选的,您可以简单地测试
MatchResult.groups.Count
是否足够大以容纳
取消的
组(被测试的字符串位于索引0处的组中,因此它包含在
计数中):

或:

顺便说一句,您的循环还缺少对
NextMatch()
的调用,因此您的代码陷入了无休止的循环中

while MatchResult.Success do
begin
  ...
  MatchResult := MatchResult.NextMatch; // <-- add this
end;
匹配结果时。是否成功
开始
...

MatchResult:=MatchResult.NextMatch;// 您还可以避免使用可选组,并强制取消组,包括C或nothing。只需将正则表达式的最后一行更改为

'(?<Cancelled>C|)$'

哎呀!当我把它浓缩成一个MCVE在这里发布时,我一定错过了下一个匹配行。我将在中编辑它,这样它就不会产生误导。我一回到办公桌就会测试这个代码。谢谢,雷米,太好了。检查
MatchResult.Groups.Count>8
有效。我不知道测试字符串在索引0中。再次感谢你,雷米。我第一次尝试的时候也没有,而且也没有记录。不过,我在调试器中看到了它。当
取消的
组存在时,它位于索引8,而不是索引7.Wow。被否决的人能解释我错过了什么吗?我认为问题很清楚,代码是一个完整的MCVE,可以编译、运行和复制问题。好吧,抱歉@Ken Whitemake it non-optional,很遗憾,它与前两行不匹配。我需要匹配所有行,因为我正在处理整个文件;我需要取消标志,如果它存在,但在任何情况下,我需要每一行的其余部分。你改变了我写的正则表达式吗?我只有Delphi XE3,但是您的测试应用程序输出了所有三行。我的观点是正确的。我错过了您在C之后添加的额外交替运算符(|),它不允许任何内容也匹配。我最初的只读看到了可选(?)的删除。您的解决方案也有效。我认为Remy的答案是可以接受的,因为他的帖子直接回答了我关于直接在代码中处理不存在的值的问题。虽然您的也解决了这个问题,但它也需要修改正则表达式才能工作。我对你的答案投了赞成票。:-)
while MatchResult.Success do
begin
  ...
  MatchResult := MatchResult.NextMatch; // <-- add this
end;
'(?<Cancelled>C|)$'
if MatchResult.Groups['Cancelled'].Value = 'C' then
  DoSomething;