正規表現の活用 (主に Delphi 2009 以降)

 文字列操作やファイル操作を行う場合には正規表現を使うと楽です。ただ、正規表現には膨大な機能があり、それをすべて紹介していたら本が一冊書けてしまいます。そこで、"Delphi で正規表現を使う" という点に絞って話を進めてみたいと思います。



SkRegExp と PerlRegEx

 Delphi で正規表現を使うには、SkRegExp 或いは PerlRegEx を使う事になると思います。両者共に .NET Framework の RegEx 互換ラッパークラスを持っているので、出来得る限りラッパークラスを使うようにすると、文法の違いに悩まなくて済むと思います。ただ、場合によってはコアクラスを使った方が効率がいい事もありますので、それぞれのコアクラスのメソッドやプロパティを覚えておいても損はないでしょう。

 相関はこのようになっています。詳細についてはコンテンツを読み進めていくうちに理解できると思いますので、最初はあまり気にしないで下さい。また、このコンテンツ中で紹介する PerlRegEx は Delphi XE 以降に標準添付のものとします。


入手方法

SkRegExp PerlRegEx

 Delphi 2005 以降で利用可能です (ラッパークラスは Delphi 2006 以降で利用可能)。ダウンロードは "小宮秀一's Software" から。ライブラリパスの通った所に置くか、プロジェクトに追加して利用します。

 Delphi XE 以降で標準添付です。それ以外のバージョンの Delphi で利用する場合には別途 "regular-expressions.info" から DL します。



名前空間

SkRegExp PerlRegEx

 コアクラスが TSkRegExp で、コレクションに対応したラッパークラスが TRegEx です。名前空間はそれぞれ SkRegExpW と SkRegularExpressions になります。

 コアクラスが TPerlRegEx で、コレクションに対応したラッパークラスが TRegEx です。名前空間はそれぞれ RegularExpressionsCore と RegularExpressions になります。

 よく解らない場合はコアクラスとラッパークラスの両方 uses して下さい。

 Delphi 2009 / 2010 をお使いの場合には、SkRegExp のラッパークラスを使うといいでしょう。Delphi XE 以前では SkRegExp のラッパークラス、Delphi XE またはそれ以降では標準のラッパークラス (PerlRegEx) を使うようにするには、

uses
  ... ,
  {$IFDEF CONDITIONALEXPRESSIONS}
    {$IF CompilerVersion >= 22.0}
    RegularExpressions,   // PerlRegEx
    {$ELSE}
    SkRegularExpressions, // SkRegExp
    {$IFEND}
  {$ENDIF}
  ... ;

 このようにします。ただ、Delphi のバージョンを通して SkRegExp 或いは PerlRegEx で統一した方が "正規表現の方言に悩まされずに済む" という事は言うまでもないでしょう。


注意点

SkRegExp PerlRegEx

 SkRegExpW と SkRegularExpressions はアーカイブが別になっていますので、それぞれ DL して下さい。

 ANSI 版 Delphi で使う場合、文字列は WideString で処理される事に注意して下さい。つまり、ANSI 版 Delphi だろうが、Unicode 版 Delphi であろうが、内部処理は UTF-16 という事です。

 ラッパークラスは UTF-16 で処理されますが、コアクラスは UTF-8 で処理される 事に注意が必要です。



文字列のマッチ

 正規表現文字列に一致するかしないかを判定してみます。

 関数版

SkRegExp PerlRegEx

 Edit1 に入力された文字列が某外食チェーン店の名前に一致するか?

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
begin
  if RegIsMatch(Exp, Edit1.Text, []) then
    ShowMessage('Match.')
  else
    ShowMessage('No match.');
end;

 Edit1 に入力された文字列が某外食チェーン店の名前に一致したらその文字列を表示する。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  MatchList: TStringList;
begin
  MatchList := TStringList.Create;
  try
    if RegMatch(Exp, Edit1.Text, MatchList, []) then
      ShowMessage(MatchList[0])
    else
      ShowMessage('No match.');
  finally
    MatchList.Free;
  end;
end;

 コアクラス版

SkRegExp PerlRegEx

 Edit1 に入力された文字列が某外食チェーン店の名前に一致するか?

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegExp: TSkRegExp;
begin
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
    if RegExp.Exec(Edit1.Text) then
      ShowMessage('Match.')
    else
      ShowMessage('No match.');
  finally
    RegExp.Free;
  end;
end;

 Edit1 に入力された文字列が某外食チェーン店の名前に一致したらその文字列を表示する。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegExp: TSkRegExp;
begin
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
    if RegExp.Exec(Edit1.Text) then
      ShowMessage(RegExp.Match[0])
    else
      ShowMessage('No match.');
  finally
    RegExp.Free;
  end;
end;

 Edit1 に入力された文字列が某外食チェーン店の名前に一致するか?

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegExp: TPerlRegEx;
begin
  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Edit1.Text);
    if RegExp.Match then
      ShowMessage('Match.')
    else
      ShowMessage('No match.');
  finally
    RegExp.Free;
  end;
end;

 Edit1 に入力された文字列が某外食チェーン店の名前に一致したらその文字列を表示する。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegExp: TPerlRegEx;
begin
  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Edit1.Text);
    if RegExp.Match then
      ShowMessage(UnicodeString(RegExp.MatchedText))
    else
      ShowMessage('No match.');
  finally
    RegExp.Free;
  end;
end;

 コアクラスは UTF-8 で処理しているので、キャストしないと警告が出ます。

 ラッパークラス版

SkRegularExpressions / RegularExpressions

 Edit1 に入力された文字列が某外食チェーン店の名前に一致するか?

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
begin
  if TRegEx.IsMatch(Edit1.Text, Exp, []) then
    ShowMessage('Match.')
  else
    ShowMessage('No match.');
end;

 以下のような記述も可能です。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
begin
  if TRegEx.Match(Edit1.Text, Exp, []).Success then
    ShowMessage('Match.')
  else
    ShowMessage('No match.');
end;

 Edit1 に入力された文字列が某外食チェーン店の名前に一致したらその文字列を表示する。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  Match: TMatch;
begin
  Match := TRegEx.Match(Edit1.Text, Exp, []);
  if Match.Success then
    ShowMessage(Match.Value)
  else
    ShowMessage('No match.');
end;

 指定する正規表現の方言以外に違いはなく、コードは同一となります。名前空間として SkRegularExpressions と RegularExpressions のどちらを使うかだけの違いです。



マッチ文字列の列挙

 次に、正規表現文字列に一致するすべての文字列を列挙してみます。

 コアクラス版

SkRegExp PerlRegEx

 Memo1 に入力された文字列中に一致する某外食チェーン店の名前があれば Memo2 に情報を列挙。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegExp: TSkRegExp;
  Dmy, mValue: String;
  mStart, mLength: Integer;
begin
  Memo2.Lines.Clear;
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
    if RegExp.Exec(Memo1.Lines.Text) then
      repeat
        mValue  := RegExp.Match[0];
        mStart  := RegExp.MatchPos[0];
        mLength := RegExp.MatchLen[0];
        Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
        Memo2.Lines.Add(Dmy);
      until (not RegExp.ExecNext);
  finally
    RegExp.Free;
  end

 Memo1 に入力された文字列中に一致する某外食チェーン店の名前があれば Memo2 に情報を列挙。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegExp: TPerlRegEx;
  Dmy, mValue: String;
  mStart, mLength: Integer;
begin
  Memo2.Lines.Clear;
  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Memo1.Lines.Text);
    if RegExp.Match then
      repeat
        mValue  := UnicodeString(RegExp.MatchedText);
        mStart  := RegExp.MatchedOffset; // UTF-8 での開始位置
        mLength := RegExp.MatchedLength; // UTF-8 でのエレメント長
        Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
        Memo2.Lines.Add(Dmy);
      until (not RegExp.MatchAgain);
  finally
    RegExp.Free;
  end;
end;

 コアクラスは UTF-8 で処理しているので、キャストしないと警告が出ます。また、マッチ開始位置やマッチ長は UTF-8 ベースなので、SelStart や SelLength に直接使う事はできません

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegExp: TPerlRegEx;
  Dmy, mValue: String;
  mStart, mLength: Integer;
  U8: UTF8String;
begin
  Memo2.Lines.Clear;
  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Memo1.Lines.Text);
    if RegExp.Match then
      repeat
        U8 := UTF8String(Memo1.Lines.Text);
        mValue  := UnicodeString(RegExp.MatchedText);
        mStart  := RegExp.MatchedOffset; // UTF-8 での開始位置
        mLength := RegExp.MatchedLength; // UTF-8 でのエレメント長
        U8 := Copy(U8, mStart, mLength);
        Dmy := Format('%s (%s)', [mValue, UnicodeString(U8)]);
        Memo2.Lines.Add(Dmy);
      until (not RegExp.MatchAgain);
  finally
    RegExp.Free;
  end;
end;

 UTF8String で処理すればマッチ位置情報も使えない事はないですが...。

 ラッパークラス版

SkRegularExpressions / RegularExpressions

 Memo1 に入力された文字列中に一致する某外食チェーン店の名前があれば Memo2 に情報を列挙。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  Match: TMatch;
  Dmy, mValue: String;
  mStart, mLength: Integer;
begin
  Memo2.Lines.Clear;
  for Match in TRegEx.Matches(Memo1.Lines.Text, Exp) do
    begin
      mValue  := Match.Value;
      mStart  := Match.Index;
      mLength := Match.Length;
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      Memo2.Lines.Add(Dmy);
    end;
end;

 for in do を使わないやり方は以下のようになります。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  i: Integer;
  Collection: TMatchCollection;
  Dmy, mValue: String;
  mStart, mLength: Integer;
begin
  Memo2.Lines.Clear;
  Collection := TRegEx.Matches(Memo1.Lines.Text, Exp);
  for i:=0 to Collection.Count-1 do
    begin
      mValue  := Collection.Item[i].Value;
      mStart  := Collection.Item[i].Index;
      mLength := Collection.Item[i].Length;
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      Memo2.Lines.Add(Dmy);
    end;
end;

 本来は以下のような記述も可能なハズです (QC#87752 です。XE2 で解決されています。SkRegularExpressions は最新版で動作します)。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  Match: TMatch;
  Dmy, mValue: String;
  mStart, mLength: Integer;
begin
  Memo2.Lines.Clear;
  Match := TRegEx.Match(Memo1.Lines.Text, Exp);
  while Match.Success do
    begin
      mValue  := Match.Value;
      mStart  := Match.Index;
      mLength := Match.Length;
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      ShowMessage(Dmy);
      Match := Match.NextMatch;
    end;
end;

 .NET の RegEx の記述方法とは若干異なる事になりますが、TRegEx.Match を直接呼び出さなければ (クラスメソッドの方を使わなければ) QC#87752 を回避できるようです。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '[吉|\x{20BB7}]野[屋|家]';
var
  RegEx: TRegEx;
  Match: TMatch;
  Dmy, mValue: String;
  mStart, mLength: Integer;
begin
  Memo2.Lines.Clear;
  RegEx := TRegEx.Create(Exp);
  Match := RegEx.Match(Memo1.Lines.Text);
  while Match.Success do
    begin
      mValue  := Match.Value;
      mStart  := Match.Index;
      mLength := Match.Length;
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      ShowMessage(Dmy);
      Match := Match.NextMatch;
    end;
end;

 指定する正規表現の方言以外に違いはなく、コードは同一となります。名前空間として SkRegularExpressions と RegularExpressions のどちらを使うかだけの違いです。

 PerlRegEx のラッパークラスは UTF-16 ベースなのでコアクラスの時と違い、マッチ開始位置やマッチ長を SelStart や SelLength に使う事ができます。



文字列のグループマッチ

 正規表現文字列に一致するかしないかを判定し、それぞれの項目をグループとして取得してみます。

 正規表現では "()" で括られた部分を "グループ" として 1 から始まるインデックスで取り出す事ができ、さらに "(?<name>式)" のようにすると "名前付きグループ" にする事が可能で、name で指定した名前で取り出す事ができます。TDataSet.Fields / TDataSet.FieldByName のようなものです。

 関数版

SkRegExp PerlRegEx

 Edit1 に入力された文字列が日付文字列に一致するかをグループインデックスで取得し、Memo2 に表示。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(\d{2,4})\/(\d{1,2})\/(\d{1,2})';
var
  MatchList: TStringList;
  mDate: string;
  mYear, mMonth, mDay: Word;
begin
  Memo2.Lines.Clear;
  MatchList := TStringList.Create;
  try
    if RegMatch(Exp, Edit1.Text, MatchList, []) then
      begin
        mDate  := MatchList[0];           // All
        mYear  := StrToInt(MatchList[1]); // Group 1
        mMonth := StrToInt(MatchList[2]); // Group 2
        mDay   := StrToInt(MatchList[3]); // Group 3
        Memo2.Lines.Add(mDate);
        Memo2.Lines.Add(Format('Year  = %d', [mYear ]));
        Memo2.Lines.Add(Format('Month = %d', [mMonth]));
        Memo2.Lines.Add(Format('Day   = %d', [mDay  ]));
      end
    else
      Memo2.Lines.Add('No match.');
  finally
    MatchList.Free;
  end;
end;

 コアクラス版

SkRegExp PerlRegEx

 Edit1 に入力された文字列が日付文字列に一致するかをグループインデックスで取得し、Memo2 に表示。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(\d{2,4})\/(\d{1,2})\/(\d{1,2})';
var
  RegExp: TSkRegExp;
  mDate: string;
  mYear, mMonth, mDay: Word;
begin
  Memo2.Lines.Clear;
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
    if RegExp.Exec(Edit1.Text) then
      begin
        mDate  := RegExp.Match[0];
        mYear  := StrToInt(RegExp.Match[1]);
        mMonth := StrToInt(RegExp.Match[2]);
        mDay   := StrToInt(RegExp.Match[3]);
        Memo2.Lines.Add(mDate);
        Memo2.Lines.Add(Format('Year  = %d', [mYear ]));
        Memo2.Lines.Add(Format('Month = %d', [mMonth]));
        Memo2.Lines.Add(Format('Day   = %d', [mDay  ]));
      end
    else
      Memo2.Lines.Add('No match.');
  finally
    RegExp.Free;
  end;
end;

 Edit1 に入力された文字列が日付文字列に一致するかをグループ名で取得し、Memo2 に表示。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Year>\d{2,4})\/(?<Month>\d{1,2})\/(?<Day>\d{1,2})';
var
  RegExp: TSkRegExp;
  mDate: string;
  mYear, mMonth, mDay: Word;
begin
  Memo2.Lines.Clear;
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
    if RegExp.Exec(Edit1.Text) then
      begin
        mDate  := RegExp.Match[0];
        mYear  := StrToInt(RegExp.NamedGroup['Year' ]);
        mMonth := StrToInt(RegExp.NamedGroup['Month']);
        mDay   := StrToInt(RegExp.NamedGroup['Day'  ]);
        Memo2.Lines.Add(mDate);
        Memo2.Lines.Add(Format('Year  = %d', [mYear ]));
        Memo2.Lines.Add(Format('Month = %d', [mMonth]));
        Memo2.Lines.Add(Format('Day   = %d', [mDay  ]));
      end
    else
      Memo2.Lines.Add('No match.');
  finally
    RegExp.Free;
  end;
end;

 Edit1 に入力された文字列が日付文字列に一致するかをグループインデックスで取得し、Memo2 に表示。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(\d{2,4})\/(\d{1,2})\/(\d{1,2})';
var
  RegExp: TPerlRegEx;
  mDate: string;
  mYear, mMonth, mDay: Word;
begin
  Memo2.Lines.Clear;
  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Edit1.Text);
    if RegExp.Match then
      begin
        mDate  := UnicodeString(RegExp.MatchedText);
        mYear  := StrToInt(UnicodeString(RegExp.Groups[1]));
        mMonth := StrToInt(UnicodeString(RegExp.Groups[2]));
        mDay   := StrToInt(UnicodeString(RegExp.Groups[3]));
        Memo2.Lines.Add(mDate);
        Memo2.Lines.Add(Format('Year  = %d', [mYear ]));
        Memo2.Lines.Add(Format('Month = %d', [mMonth]));
        Memo2.Lines.Add(Format('Day   = %d', [mDay  ]));
      end
    else
      Memo2.Lines.Add('No match.');
  finally
    RegExp.Free;
  end;
end;

 Edit1 に入力された文字列が日付文字列に一致するかをグループ名で取得し、Memo2 に表示。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Field1>\d{2,4})\/(?<Field2>\d{1,2})\/(?<Field3>\d{1,2})';
var
  RegExp: TPerlRegEx;
  Index: Integer;
  mDate: string;
  mYear, mMonth, mDay: Word;
begin
  Memo2.Lines.Clear;
  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Edit1.Text);
    if RegExp.Match then
      begin
        mDate  := UnicodeString(RegExp.MatchedText);

        Index  := RegExp.NamedGroup(UTF8String('Field1'));
        mYear  := StrToInt(UnicodeString(RegExp.Groups[Index]));

        Index  := RegExp.NamedGroup(UTF8String('Field2'));
        mMonth := StrToInt(UnicodeString(RegExp.Groups[Index]));

        Index  := RegExp.NamedGroup(UTF8String('Field3'));
        mDay   := StrToInt(UnicodeString(RegExp.Groups[Index]));

        Memo2.Lines.Add(mDate);
        Memo2.Lines.Add(Format('Year  = %d', [mYear ]));
        Memo2.Lines.Add(Format('Month = %d', [mMonth]));
        Memo2.Lines.Add(Format('Day   = %d', [mDay  ]));
      end
    else
      Memo2.Lines.Add('No match.');
  finally
    RegExp.Free;
  end;
end;

 この正規表現文字列の場合、名前付きグループのグループ名に何故か year や day を含められません (QC#93333)。

const
  Exp = '(?<Year>\d{2,4})\/(?<Month>\d{1,2})\/(?<Day>\d{1,2})';

 これだと NamedGroup の戻り値がおかしくなります。なお、QC#93333 は XE2 で修正されています。

 ラッパークラス版

SkRegularExpressions / RegularExpressions

 Edit1 に入力された文字列が日付文字列に一致するかをグループインデックスで取得し、Memo2 に表示。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(\d{2,4})\/(\d{1,2})\/(\d{1,2})';
var
  Match: TMatch;
  mDate: string;
  mYear, mMonth, mDay: Word;
begin
  Memo2.Lines.Clear;
  Match := TRegEx.Match(Edit1.Text, Exp, []);
  if Match.Success then
    begin
      mDate  := Match.Value;                     // All
      mYear  := StrToInt(Match.Groups[1].Value); // Group 1
      mMonth := StrToInt(Match.Groups[2].Value); // Group 2
      mDay   := StrToInt(Match.Groups[3].Value); // Group 3
      Memo2.Lines.Add(mDate);
      Memo2.Lines.Add(Format('Year  = %d', [mYear ]));
      Memo2.Lines.Add(Format('Month = %d', [mMonth]));
      Memo2.Lines.Add(Format('Day   = %d', [mDay  ]));
    end
  else
    Memo2.Lines.Add('No match.');
end;

 Edit1 に入力された文字列が日付文字列に一致するかをグループ名で取得し、Memo2 に表示。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Field1>\d{2,4})\/(?<Field2>\d{1,2})\/(?<Field3>\d{1,2})';
var
  Match: TMatch;
  mDate: string;
  mYear, mMonth, mDay: Word;
begin
  Memo2.Lines.Clear;
  Match := TRegEx.Match(Edit1.Text, Exp, []);
  if Match.Success then
    begin
      mDate  := Match.Value;                            // All
      mYear  := StrToInt(Match.Groups['Field1'].Value); // Group 1
      mMonth := StrToInt(Match.Groups['Field2'].Value); // Group 2
      mDay   := StrToInt(Match.Groups['Field3'].Value); // Group 3
      Memo2.Lines.Add(mDate);
      Memo2.Lines.Add(Format('Year  = %d', [mYear ]));
      Memo2.Lines.Add(Format('Month = %d', [mMonth]));
      Memo2.Lines.Add(Format('Day   = %d', [mDay  ]));
    end
  else
    Memo2.Lines.Add('No match.');
end;

 指定する正規表現の方言以外に違いはなく、コードは同一となります。名前空間として SkRegularExpressions と RegularExpressions のどちらを使うかだけの違いです。



グループマッチ文字列の列挙

 次に、正規表現文字列に一致するすべての文字列をグループでも列挙してみます。

 コアクラス版

SkRegExp PerlRegEx

 Memo1 に入力された文字列中に一致する日付文字列があれば Memo2 に情報を列挙。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Year>\d{2,4})\/(?<Month>\d{1,2})\/(?<Day>\d{1,2})';
var
  i: Integer;
  RegExp: TSkRegExp;
  Dmy, mValue: String;
  mStart, mLength: Integer;
  gName: String;
begin
  Memo2.Lines.Clear;
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
//  RegExp.MultiLine := True;
    if RegExp.Exec(Memo1.Lines.Text) then
      repeat
        mValue  := RegExp.Match[0];
        mStart  := RegExp.MatchPos[0];
        mLength := RegExp.MatchLen[0];
        Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
        Memo2.Lines.Add('------------------------------');
        Memo2.Lines.Add(Dmy);
        Memo2.Lines.Add('------------------------------');
        Memo2.Lines.Add('');
        for i:=1 to RegExp.GroupCount do
          begin
            // Group
            gName   := RegExp.GroupNameFromIndex[i];
            Memo2.Lines.Add(Format('  Group : ''%s'' [%d]', [gName, i]));
            // Value
            mValue  := RegExp.Match[i];
            Memo2.Lines.Add(Format('  Value : ''%s''', [mValue]));
            // MatchPos
            mStart  := RegExp.MatchPos[i];
            Memo2.Lines.Add(Format('  Start : %d', [mStart]));
            // MatchLength
            mLength := RegExp.MatchLen[i];
            Memo2.Lines.Add(Format('  Length: %d', [mLength]));
            Memo2.Lines.Add('');
          end;
      until (not RegExp.ExecNext);
  finally
    RegExp.Free;
  end
end;

 Memo1 に入力された文字列中に一致する日付文字列があれば Memo2 に情報を列挙。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Field1>\d{2,4})\/(?<Field2>\d{1,2})\/(?<Field3>\d{1,2})';
var
  i: Integer;
  RegExp: TPerlRegEx;
  Dmy, mValue: String;
  mStart, mLength: Integer;
begin
  Memo2.Lines.Clear;
  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Memo1.Lines.Text);
//  RegExp.Options := [preMultiLine];
    if RegExp.Match then
      repeat
        mValue  := UnicodeString(RegExp.MatchedText);
        mStart  := RegExp.MatchedOffset;
        mLength := RegExp.MatchedLength;
        Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
        Memo2.Lines.Add('------------------------------');
        Memo2.Lines.Add(Dmy);
        Memo2.Lines.Add('------------------------------');
        Memo2.Lines.Add('');
        for i:=1 to RegExp.GroupCount do
          begin
            // Group
            Memo2.Lines.Add(Format('  Group :  [%d]', [i]));
            // Value
            mValue  := UnicodeString(RegExp.Groups[i]);
            Memo2.Lines.Add(Format('  Value : ''%s''', [mValue]));
            // MatchPos
            mStart  := RegExp.GroupOffsets[i];
            Memo2.Lines.Add(Format('  Start : %d', [mStart]));
            // MatchLength
            mLength := RegExp.GroupLengths[i];
            Memo2.Lines.Add(Format('  Length: %d', [mLength]));
            Memo2.Lines.Add('');
          end;
      until (not RegExp.MatchAgain);
  finally
    RegExp.Free;
  end
end;

 コアクラスは UTF-8 で処理しているので、キャストしないと警告が出ます。また、マッチ開始位置やマッチ長は UTF-8 ベースなので、SelStart や SelLength に直接使う事はできません。TSkRegExp と違い、グループ結果からグループ名を取得する事はできません。

 グループをループ変数で回して取得していますが、もちろん名前付きグループで取得する事も可能です。

 ラッパークラス版

SkRegularExpressions / RegularExpressions

 Memo1 に入力された文字列中に一致する日付文字列があれば Memo2 に情報を列挙。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Field1>\d{2,4})\/(?<Field2>\d{1,2})\/(?<Field3>\d{1,2})';
var
  i: Integer;
  Match: TMatch;
  Dmy, mValue: String;
  mStart, mLength: Integer;
  gName: String;
begin
  Memo2.Lines.Clear;
//for Match in TRegEx.Matches(Memo1.Lines.Text, Exp, [roMultiLine]) do
  for Match in TRegEx.Matches(Memo1.Lines.Text, Exp) do
    begin
      mValue  := Match.Value;  // or Match.Groups.Item[0].Value
      mStart  := Match.Index;  // or Match.Groups.Item[0].Index
      mLength := Match.Length; // or Match.Groups.Item[0].Length
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add(Dmy);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add('');
      for i:=1 to Match.Groups.Count-1 do
        begin
          // Group
//        gName   := Match.Groups.Item[i].GroupName; // SkRegExp only
          gName   := '';
          Memo2.Lines.Add(Format('  Group : ''%s'' [%d]', [gName, i]));
          // Value
          mValue  := Match.Groups.Item[i].Value;
          Memo2.Lines.Add(Format('  Value : ''%s''', [mValue]));
          // MatchPos
          mStart  := Match.Groups.Item[i].Index;
          Memo2.Lines.Add(Format('  Start : %d', [mStart]));
          // MatchLength
          mLength := Match.Groups.Item[i].Length;
          Memo2.Lines.Add(Format('  Length: %d', [mLength]));
          Memo2.Lines.Add('');
        end;
    end;
end;

 for in do を使わないやり方は以下のようになります。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Field1>\d{2,4})\/(?<Field2>\d{1,2})\/(?<Field3>\d{1,2})';
var
  i, l: Integer;
  Collection: TMatchCollection;
  Dmy, mValue: String;
  mStart, mLength: Integer;
  gName: String;
begin
  Memo2.Lines.Clear;
//Collection := TRegEx.Matches(Memo1.Lines.Text, Exp, [roMultiLine]);
  Collection := TRegEx.Matches(Memo1.Lines.Text, Exp);
  for i:=0 to Collection.Count-1 do
    begin
      mValue  := Collection.Item[i].Value;  // or Collection.Item[i].Groups.Item[0].Value
      mStart  := Collection.Item[i].Index;  // or Collection.Item[i].Groups.Item[0].Index
      mLength := Collection.Item[i].Length; // or Collection.Item[i].Groups.Item[0].Length
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add(Dmy);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add('');
      for l:=1 to Collection.Item[i].Groups.Count-1 do
        begin
          // Group
//        gName   := Collection.Item[i].Groups.Item[l].GroupName; // SkRegExp only
          gName   := '';
          Memo2.Lines.Add(Format('  Group : ''%s'' [%d]', [gName, l]));
          // Value
          mValue  := Collection.Item[i].Groups.Item[l].Value;
          Memo2.Lines.Add(Format('  Value : ''%s''', [mValue]));
          // MatchPos
          mStart  := Collection.Item[i].Groups.Item[l].Index;
          Memo2.Lines.Add(Format('  Start : %d', [mStart]));
          // MatchLength
          mLength := Collection.Item[i].Groups.Item[l].Length;
          Memo2.Lines.Add(Format('  Length: %d', [mLength]));
          Memo2.Lines.Add('');
        end;
    end;
end;

 本来は以下のような記述も可能なハズです (QC#87752 です。XE2 で解決されています。SkRegularExpressions は最新版で動作します)。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Field1>\d{2,4})\/(?<Field2>\d{1,2})\/(?<Field3>\d{1,2})';
var
  i: Integer;
  Match: TMatch;
  Dmy, mValue: String;
  mStart, mLength: Integer;
  gName: String;
begin
  Memo2.Lines.Clear;
//Match := TRegEx.Match(Memo1.Lines.Text, Exp, [roMultiLine]);
  Match := TRegEx.Match(Memo1.Lines.Text, Exp);
  while Match.Success do
    begin
      mValue  := Match.Value;  // or Match.Groups.Item[0].Value
      mStart  := Match.Index;  // or Match.Groups.Item[0].Index
      mLength := Match.Length; // or Match.Groups.Item[0].Length
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add(Dmy);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add('');
      for i:=1 to Match.Groups.Count-1 do
        begin
          // Group
//        gName   := Match.Groups.Item[i].GroupName; // SkRegExp only
          gName   := '';
          Memo2.Lines.Add(Format('  Group : ''%s'' [%d]', [gName, i]));
          // Value
          mValue  := Match.Groups.Item[i].Value;
          Memo2.Lines.Add(Format('  Value : ''%s''', [mValue]));
          // MatchPos
          mStart  := Match.Groups.Item[i].Index;
          Memo2.Lines.Add(Format('  Start : %d', [mStart]));
          // MatchLength
          mLength := Match.Groups.Item[i].Length;
          Memo2.Lines.Add(Format('  Length: %d', [mLength]));
          Memo2.Lines.Add('');
        end;
      Match := Match.NextMatch;
    end;
end;

 .NET の RegEx の記述方法とは若干異なる事になりますが、TRegEx.Match を直接呼び出さなければ (クラスメソッドの方を使わなければ) QC#87752 を回避できるようです。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Field1>\d{2,4})\/(?<Field2>\d{1,2})\/(?<Field3>\d{1,2})';
var
  i: Integer;
  RegEx: TRegEx;
  Match: TMatch;
  Dmy, mValue: String;
  mStart, mLength: Integer;
  gName: String;
begin
  Memo2.Lines.Clear;
//RegEx := TRegEx.Create(Exp, [roMultiLine]);
  RegEx := TRegEx.Create(Exp);
  Match := RegEx.Match(Memo1.Lines.Text);
  while Match.Success do
    begin
      mValue  := Match.Value;  // or Match.Groups.Item[0].Value
      mStart  := Match.Index;  // or Match.Groups.Item[0].Index
      mLength := Match.Length; // or Match.Groups.Item[0].Length
      Dmy := Format('%s (%d, %d)', [mValue, mStart, mLength]);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add(Dmy);
      Memo2.Lines.Add('------------------------------');
      Memo2.Lines.Add('');
      for i:=1 to Match.Groups.Count-1 do
        begin
          // Group
//        gName   := Match.Groups.Item[i].GroupName; // SkRegExp only
          gName   := '';
          Memo2.Lines.Add(Format('  Group : ''%s'' [%d]', [gName, i]));
          // Value
          mValue  := Match.Groups.Item[i].Value;
          Memo2.Lines.Add(Format('  Value : ''%s''', [mValue]));
          // MatchPos
          mStart  := Match.Groups.Item[i].Index;
          Memo2.Lines.Add(Format('  Start : %d', [mStart]));
          // MatchLength
          mLength := Match.Groups.Item[i].Length;
          Memo2.Lines.Add(Format('  Length: %d', [mLength]));
          Memo2.Lines.Add('');
        end;
      Match := Match.NextMatch;
    end;
end;

 指定する正規表現の方言以外に違いはなく、コードは同一となります。名前空間として SkRegularExpressions と RegularExpressions のどちらを使うかだけの違いです。

 SkregExp では TGroup に GroupName プロパティが存在するため、コアクラスの時同様にグループ名を取得する事ができます。

 PerlRegEx のラッパークラスは UTF-16 ベースなのでコアクラスの時と違い、マッチ開始位置やマッチ長を SelStart や SelLength に使う事ができます。

 グループをループ変数で回して取得していますが、もちろん名前付きグループで取得する事も可能です。


グループのキャプチャ (捕捉グループ)

 正規表現ではグループの繰り返しに一致した箇所をキャプチャ (捕捉グループ) として取り出す事が可能ですが、現時点ではラッパークラスで対応していないので割愛します。将来、ラッパークラスが対応した時に追記したいと思います。


文字列マッチに使うラッパークラスの(静的)メソッドとクラスメソッド

 ラッパークラスの IsMatch()Match() 或いは Matches() メソッドには静的メソッドとクラスメソッドがあり、前者は TRegEx のインスタンスを生成して利用し、後者はインスタンスを生成せずに利用します。

 IsMatch() メソッドは以下のようになっています。一致や比較等の単純なマッチに利用します。

// [メソッド]
function IsMatch(const Input: string): Boolean; 
function IsMatch(const Input: string; StartPos: Integer): Boolean;

// [クラスメソッド]
function IsMatch(const Input, Pattern: string): Boolean;
function IsMatch(const Input, Pattern: string; Options: TRegExOptions): Boolean;

 Match() メソッドは以下のようになっています。主に繰り返しのマッチに利用します。

// [メソッド]
function Match(const Input: string): TMatch;
function Match(const Input: string; StartPos: Integer): TMatch;
function Match(const Input: string; StartPos, Length: Integer): TMatch;

// [クラスメソッド]
function Match(const Input, Pattern: string): TMatch;
function Match(const Input, Pattern: string; Options: TRegExOptions): TMatch;

 Matches() メソッドは以下のようになっています。主に列挙のマッチに利用します。

// [メソッド]
function Matches(const Input: string): TMatchCollection;
function Matches(const Input: string; StartPos: Integer): TMatchCollection;

// [クラスメソッド]
function Matches(const Input, Pattern: string): TMatchCollection;
function Matches(const Input, Pattern: string; Options: TRegExOptions): TMatchCollection;

 静的メソッドの場合、正規表現パターンやオプションTRegEx.Create() で指定します。

 SkRegExp と PerlRegEx でラッパークラスに差異はないので、ソースコード中のリンクは DocWiki のものになっています。


文字列の置換

 正規表現文字列に一致する文字列を置換します。

 関数版

SkRegExp PerlRegEx

 Memo1 に入力されているすべての日付文字列を、今日の日付で置換します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\d{2,4}\/\d{1,2}\/\d{1,2}';
var
  Rep: string;
begin
  Rep := FormatDateTime('YYYY/MM/DD', Date);
  Memo2.Lines.Text := RegReplace(Exp, Memo1.Lines.Text, Rep, []);
end;

 Memo1 に入力されているすべての日付文字列を、その日付の翌日に置換します。

function TForm1.CalcTomorrow(ARegExp: TSkRegExp): String;
var
  s: string;
begin
  s := ARegExp.NamedGroup['Date'];
  result := FormatDateTime('YYYY/MM/DD', VarToDateTime(s) + 1);
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Date>\d{2,4}\/\d{1,2}\/\d{1,2})';
var
  mEVal: TSkRegExpReplaceFunction;
begin
  mEVal := CalcTomorrow;
  Memo2.Lines.Text := RegReplace(Exp, Memo1.Lines.Text, mEVal, []);
end;

 マッチ結果を元にした置換文字列を指定する等の複雑な置換の場合には、手続き型 TSkRegExpReplaceFunction を定義して割り当ててやる必要があります。

 コアクラス版

SkRegExp PerlRegEx

 Memo1 に入力されているすべての日付文字列を、今日の日付で置換します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\d{2,4}\/\d{1,2}\/\d{1,2}';
var
  RegExp: TSkRegExp;
  Rep: string;
begin
  Rep := FormatDateTime('YYYY/MM/DD', Date);
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
    Memo2.Lines.Text := RegExp.Replace(Memo1.Lines.Text, Rep);
  finally
    RegExp.Free;
  end
end;

 Memo1 に入力されているすべての日付文字列を、その日付の翌日に置換します。

function TForm1.CalcTomorrow(ARegExp: TSkRegExp): String;
var
  s: string;
begin
  s := ARegExp.NamedGroup['Date'];
  result := FormatDateTime('YYYY/MM/DD', VarToDateTime(s) + 1);
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Date>\d{2,4}\/\d{1,2}\/\d{1,2})';
var
  RegExp: TSkRegExp;
  mEVal: TSkRegExpReplaceFunction;
begin
  RegExp := TSkRegExp.Create;
  try
    RegExp.Expression := Exp;
    mEVal := CalcTomorrow;
    Memo2.Lines.Text := RegExp.Replace(Memo1.Lines.Text, mEVal);
  finally
    RegExp.Free;
  end
end;

 マッチ結果を元にした置換文字列を指定する等の複雑な置換の場合には、手続き型 TSkRegExpReplaceFunction を定義して割り当ててやる必要があります。

 Memo1 に入力されているすべての日付文字列を、今日の日付で置換します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\d{2,4}\/\d{1,2}\/\d{1,2}';
var
  RegExp: TPerlRegEx;
  Rep: string;
begin
  Rep := FormatDateTime('YYYY/MM/DD', Date);
  RegExp := TPerlRegEx.Create;
  try
    RegExp.Subject     := UTF8String(Memo1.Lines.Text);
    RegExp.RegEx       := UTF8String(Exp);
    RegExp.Replacement := UTF8String(Rep);
    RegExp.ReplaceAll;
    Memo2.Lines.Text := UnicodeString(RegExp.Subject);
  finally
    RegExp.Free;
  end
end;

 Memo1 に入力されているすべての日付文字列を、その日付の翌日に置換します。

procedure TForm1.RegExReplace(Sender: TObject; var ReplaceWith: UTF8String);
var
  Index: Integer;
  s: String;
begin
  Index := (Sender as TPerlRegEx).NamedGroup(UTF8String('Date'));
  s := UnicodeString((Sender as TPerlRegEx).Groups[Index]);
  s := FormatDateTime('YYYY/MM/DD', VarToDateTime(s) + 1);
  ReplaceWith := UTF8String(s);
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Date>\d{2,4}\/\d{1,2}\/\d{1,2})';
var
  RegExp: TPerlRegEx;
begin
  RegExp := TPerlRegEx.Create;
  try
    RegExp.OnReplace := RegExReplace;
    RegExp.Subject   := UTF8String(Memo1.Lines.Text);
    RegExp.RegEx     := UTF8String(Exp);
    RegExp.ReplaceAll;
    Memo2.Lines.Text := UnicodeString(RegExp.Subject);
  finally
    RegExp.Free;
  end
end;

 マッチ結果を元にした置換文字列を指定する等の複雑な置換の場合には、イベントハンドラ TPerlRegEx.OnReplace を記述する必要があります。

 コアクラスは UTF-8 で処理しているので、キャストしないと警告が出ます。

 ラッパークラス版

SkRegularExpressions / RegularExpressions

 Memo1 に入力されているすべての日付文字列を、今日の日付で置換します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\d{2,4}\/\d{1,2}\/\d{1,2}';
var
  Rep: string;
begin
  Rep := FormatDateTime('YYYY/MM/DD', Date);
  Memo2.Lines.Text := TRegEx.Replace(Memo1.Lines.Text, Exp, Rep, []);
end;

 Memo1 に入力されているすべての日付文字列を、その日付の翌日に置換します。

function TForm1.CalcTomorrow(const AMatch: TMatch): String;
var
  s: string;
begin
  s := AMatch.Groups.Item['Date'].Value;
  result := FormatDateTime('YYYY/MM/DD', VarToDateTime(s) + 1);
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '(?<Date>\d{2,4}\/\d{1,2}\/\d{1,2})';
var
  mEVal: TMatchEvaluator;
begin
  mEVal := CalcTomorrow;
  Memo2.Lines.Text := TRegEx.Replace(Memo1.Lines.Text, Exp, mEVal, []);
end;

 マッチ結果を元にした置換文字列を指定する等の複雑な置換の場合には、手続き型 TMatchEvaluator を定義して割り当ててやる必要があります。

 指定する正規表現の方言以外に違いはなく、コードは同一となります。名前空間として SkRegularExpressions と RegularExpressions のどちらを使うかだけの違いです。



文字列置換に使うラッパークラスの(静的)メソッドとクラスメソッド

 ラッパークラスの Replace() メソッドには静的メソッドとクラスメソッドがあり、前者は TRegEx のインスタンスを生成して利用し、後者はインスタンスを生成せずに利用します。

// [メソッド]
function Replace(const Input, Replacement: string): stringfunction Replace(const Input: string; Evaluator: TMatchEvaluator): stringfunction Replace(const Input, Replacement: string; Count: Integer): stringfunction Replace(const Input: string; Evaluator: TMatchEvaluator; Count: Integer): string// [クラスメソッド]
function Replace(const Input, Pattern, Replacement: string): stringfunction Replace(const Input, Pattern: string; Evaluator: TMatchEvaluator): stringfunction Replace(const Input, Pattern, Replacement: string; Options: TRegExOptions): stringfunction Replace(const Input, Pattern: string; Evaluator: TMatchEvaluator; Options: TRegExOptions): string;

 静的メソッドの場合、正規表現パターンやオプションTRegEx.Create() で指定します。

 SkRegExp と PerlRegEx でラッパークラスに差異はないので、ソースコード中のリンクは DocWiki のものになっています。


文字列の分割

 正規表現文字列に一致する文字列で分割します。

 関数版

SkRegExp PerlRegEx

 Edit1 に入力されているフルパス名をパス区切り文字で分割し、Memo2 に返します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  sStrings: TStringList;
begin
  sStrings := TstringList.Create;
  try
    RegSplit(Exp, Edit1.Text, sStrings, []);
    Memo2.Lines.Text := sStrings.Text;
  finally
    sStrings.Free;
  end;
end;

 コアクラス版

SkRegExp PerlRegEx

 Edit1 に入力されているフルパス名をパス区切り文字で分割し、Memo2 に返します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  RegExp: TSkRegExp;
  sStrings: TStringList;
begin
  RegExp := TSkRegExp.Create;
  sStrings := TstringList.Create;
  try
    RegExp.Expression := Exp;
    RegExp.Options := [];
    RegExp.Split(Edit1.Text, sStrings);
    Memo2.Lines.Text := sStrings.Text;
  finally
    sStrings.Free;
    RegExp.Free;
  end
end;

 Edit1 に入力されているフルパス名をパス区切り文字で分割し、Memo2 に返します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  RegExp: TPerlRegEx;
  sStrings: TStringList;
begin
  RegExp := TPerlRegEx.Create;
  sStrings := TstringList.Create;
  try
    RegExp.Subject     := UTF8String(Edit1.Text);
    RegExp.RegEx       := UTF8String(Exp);
    RegExp.Split(sStrings, 0);
    Memo2.Lines.Text := sStrings.Text;
  finally
    sStrings.Free;
    RegExp.Free;
  end
end;

 コアクラスは UTF-8 で処理しているので、キャストしないと警告が出ます。

 ラッパークラス版

 この Split() メソッドだけは戻り値の型に違いがあるため、若干記述方法が異なる事に注意が必要です。

SkRegularExpressions RegularExpressions

 Edit1 に入力されているフルパス名をパス区切り文字で分割し、Memo2 に返します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  s: String;
  sStrArr: SkRegularExpressions.TStringDynArray; // SkregExp
begin
  Memo2.Lines.Clear;
  sStrArr := TRegEx.Split(Edit1.Text, Exp, []);
  for s in sStrArr do
    Memo2.Lines.Add(s);
end;

 Edit1 に入力されているフルパス名をパス区切り文字で分割し、Memo2 に返します。

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  s: String;
  sStrArr: TArray<string>; // PerlRegEx 
begin
  Memo2.Lines.Clear;
  sStrArr := TRegEx.Split(Edit1.Text, Exp, []);
  for s in sStrArr do
    Memo2.Lines.Add(s);
end;

 両者を限りなく近い記述にする事は可能です。

SkRegularExpressions RegularExpressions

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  s: String;
  sStrArr: SkRegularExpressions.TStringDynArray;
begin
  Memo2.Lines.Clear;
  sStrArr := 
    SkRegularExpressions.TStringDynArray(TRegEx.Split(Edit1.Text, Exp, []));
  for s in sStrArr do
    Memo2.Lines.Add(s);
end;


procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  s: String;
  sStrArr: Types.TStringDynArray;
begin
  Memo2.Lines.Clear;
  sStrArr := 
    Types.TStringDynArray(TRegEx.Split(Edit1.Text, Exp, []));
  for s in sStrArr do
    Memo2.Lines.Add(s);
end;

 uses の順序を工夫すれば名前空間の指定を省略できるため、記述を全く同じにする事ができます...が、却ってコードが冗長になるので、コードの同一性に固執しないほうがいいと思います。


文字列分割に使うラッパークラスの(静的)メソッドとクラスメソッド

 ラッパークラスの Split() メソッドには静的メソッドとクラスメソッドがあり、前者は TRegEx のインスタンスを生成して利用し、後者はインスタンスを生成せずに利用します。

 RegularExpressions では、分割文字列の格納用にジェネリック配列が使われています。

// [メソッド]
function Split(const Input: string): TArray<string>;
function Split(const Input: string; Count: Integer): TArray<string>;
function Split(const Input: string; Count, StartPos: Integer): TArray<string>;

// [クラスメソッド]
function Split(const Input, Pattern: string): TArray<string>;
function Split(const Input, Pattern: string; Options: TRegExOptions): TArray<string>;

 SkRegularExpressions では、分割文字列の格納用に TStringDynArray (array of string) が使われています。裏を返せば、ジェネリクスが使えない Delphi XE 以前の環境でも同一コードで Split() を動作させる事ができるという事です。

// [メソッド]
function Split(const Input: REString): TStringDynArray;
function Split(const Input: REString; Count: Integer): TStringDynArray;
function Split(const Input: REString; Count, StartPos: Integer): TStringDynArray;

// [クラスメソッド]
function Split(const Input, Pattern: REString): TStringDynArray;
function Split(const Input, Pattern: REString; Options: TRegExOptions): TStringDynArray;

 静的メソッドの場合、正規表現パターンやオプションTRegEx.Create() で指定します。

 ソースコード中のリンクは DocWiki のものになっています。

TStringDynArray に関する注意事項

 Types 名前空間の TStringDynArray は

  TStringDynArray       = array of string;
  TWideStringDynArray   = array of WideString;

 このように定義されているので、ANSI 版 Delphi では AnsiString、Unicode 版 Delphi では UnicodeString という事になります。

 ...ですが、SkRegularExpressions 名前空間で定義されている TStringDynArray は、

  {$IFDEF UNICODE}
  ...
  REString = UnicodeString;
  {$ELSE}
  ...
  REString = WideString;
  {$ENDIF}

  TStringDynArray = array of REString;

 こう定義されており、ANSI 版 Delphi だろうが Unicode 版 Delphi だろうが、エレメント幅は 16bit となります。

 Unicode 版 Delphi に於いては、

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '\\';
var
  s: String;
  sStrArr: Types.TStringDynArray;
begin
  Memo2.Lines.Clear;
  sStrArr := Types.TStringDynArray(TRegEx.Split(Edit1.Text, Exp, []));
  for s in sStrArr do
    Memo2.Lines.Add(s);
end;

 このような "SkRegularExpressions.TStringDynArray -> Types.TStringDynArray" のキャストが可能ですが、ANSI 版 Delphi では "array of WideString -> array of AnsiString" となってしまうので不可能です。

 混乱しないようにするためには、SkRegularExpressions.TStringDynArray の方を利用するようにし、単に TStringDynArray とするのではなく、SkRegularExpressions.TStringDynArray のように名前空間を明示的に指定する事をオススメします。


文字列を正規表現形式にエスケープする

 例えば、"(ABC)" という文字列を検索したい場合には、正規表現形式では "\(ABC\)" としなければなりません。エスケープ機能は、このように、文字列中にエスケープしなければならない文字を正規表現形式でエスケープしてくれます。

 使い所が解りにくいかもしれませんが、普通の文字列検索をやりたい場合でも検索エンジンとして正規表現クラスを使いたい場合に重宝します。普通の文字列での検索/正規表現での検索と使い分けなくて済みますので。

 ...ただ、実際問題としては検索エンジンを2つ用意せざるを得ない事が多いと思います。何故なら正規表現はその構造上、逆順の検索が苦手だからです。正規表現文字列によっては正順での検索と逆順での検索の場合にマッチ文字列が一致しない事があるため、正規表現での逆順検索ができないアプリケーションも多いです (一旦、正順ですべてを検索し、前後に移動できるものはありますが)。

 関数版

SkRegExp PerlRegEx

 Edit1 に入力された文字が '(ABC)' だったらマッチ (正規表現形式だと '\(ABC\)' でなくてはならない事に注意)。

procedure TForm1.Button1Click(Sender: TObject);
var
  Exp: String;
  Input: String;
begin
  Exp := EncodeEscape(Edit1.Text);
  Input := '(ABC)';
  if RegIsMatch(Exp, Input, []) then
    ShowMessage('Match.')
  else
    ShowMessage('No match.');
end;

 コアクラス版

SkRegExp PerlRegEx

 Edit1 に入力された文字が '(ABC)' だったらマッチ。

procedure TfrmMain.Button1Click(Sender: TObject);
var
  Exp: String;
  Input: String;
begin
  Exp := TSkRegExp.EncodeEscape(Edit1.Text);
  Input := '(ABC)';
  if RegIsMatch(Exp, Input, []) then
    ShowMessage('Match.')
  else
    ShowMessage('No match.');
end;

 EncodeEscape() はクラスメソッドです。

procedure TfrmMain.Button1Click(Sender: TObject);
var
  Exp: String;
  Input: String;
begin
  Exp := TSkRegExp.EscapeRegExChars(Edit1.Text);
  Input := '(ABC)';
  if RegIsMatch(Exp, Input, []) then
    ShowMessage('Match.')
  else
    ShowMessage('No match.');
end;

 ver 2.3.0 からは EncodeEscape() の代わりに EscapeRegExChars() を使うことができます。TPerlRegEx の EscapeRegExChars() と互換性があります。

 Edit1 に入力された文字が '(ABC)' だったらマッチ。

procedure TForm1.Button1Click(Sender: TObject);
var
  Exp: String;
  Input: String;
  RegExp: TPerlRegEx;
begin
  Exp := TPerlRegEx.EscapeRegExChars(Edit1.Text);
  Input := '(ABC)';

  RegExp := TPerlRegEx.Create;
  try
    RegExp.RegEx   := UTF8String(Exp);
    RegExp.Subject := UTF8String(Input);
    if RegExp.Match then
      ShowMessage('Match.')
    else
      ShowMessage('No match.');
  finally
    RegExp.Free;
  end;
end;

 EscapeRegExChars() はクラスメソッドです。コアクラスは UTF-8 で処理しているので、キャストしないと警告が出ます。

 ラッパークラス版

SkRegularExpressions / RegularExpressions

 Edit1 に入力された文字が '(ABC)' だったらマッチ。

procedure TForm1.Button1Click(Sender: TObject);
var
  Exp: String;
  Input: String;
begin
  Exp := TRegEx.Escape(Edit1.Text);
  Input := '(ABC)';
  if TRegEx.IsMatch(Input, Exp, []) then
    ShowMessage('Match.')
  else
    ShowMessage('No match.');
end;

 指定する正規表現の方言以外に違いはなく、コードは同一となります。名前空間として SkRegularExpressions と RegularExpressions のどちらを使うかだけの違いです。

 ※ SkRegExp / PerlRegEx はいずれもコアクラスとラッパークラスでエスケープ処理に互換性がありません。SkRegExpW.EncodeEscape <> (TSkRegExp.EscapeRegExChars = TPerlRegEx.EscapeRegExChars) <> (SkRegularExpressions.TRegEx.Escape = RegularExpressions.TRegEx.Escape) です。エスケープ処理を統一したいのであれば、ラッパークラスの TRegEx.Escape() を使うようにしてください



正規表現形式から通常文字列へアンエスケープする

 これこそ使い所が解らない機能です (^^;A

 正規表現形式の文字列を 可能な限り 通常の文字列へ変換してくれますが、正規表現にはグループ指定や繰り返し指定があるので、"通常の文字列" に正確に戻すのは不可能だと思えるからです。

 多くの正規表現クラスでは Escape() の逆を行う Unescape() が実装されているのですが、現時点ではラッパークラスで対応していないので割愛します...ただ、RegularExpressions のコアクラスである PerlRegEx に実装されていないので、将来に於いても実装される可能性は低いと思います (SkRegExp には DecodeEscape という名前で実装されています)。


SkRegExp ver 2.x への対応

 SkRegExp ver 2.x では一部のプロパティが変更になっており、ここで紹介したソースコードの一部がコンパイルできません。

TSkRegExp.Match[n] TSkRegExp.Groups[n].Strings
TSkRegExp.MatchPos[n] TSkRegExp.Groups[n].Index
TSkRegExp.MatchLen[n] TSkRegExp.Groups[n].Length

 この表を元にソースコードを読み替えてください。ラッパークラスである SkregularExpressions を使う限りではこの差異を意識する必要はありません。


リンク

 正規表現関係のリンクを挙げておきます。

 SkRegExp も PerlRegEx も正規表現自体のルーツは Perl にありますから、文法などは Perl 周辺をあたってみるといいでしょう...書籍も豊富に揃ってると思います。ラッパークラスは .NET の正規表現クラスを模してあるので、.NET の資料が役に立つと思います。

 Delphi で正規表現を利用する際には .NET 互換ラッパークラスを使えば "いいとこ取りで資料には困らない" んですよね、実は。


 BACK