はじめに
Delphi で正規表現による検索/置換を使っていますか?
正規表現を使う
普通に正規表現を使うには、手前味噌ですが次の記事を参考にしてください。
検索
Delphi で文字列検索と言うと Pos()
ではないでしょうか?
program Search;
{$APPTYPE CONSOLE}
begin
var s := 'Hello, world.';
if Pos('world', s) > 0 then
Writeln('Match.')
else
Writeln('No match.');
end.
正規表現で書くとこうなります。
program Search;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := 'Hello, world.';
if TRegEx.IsMatch(s, 'world') then
Writeln('Match.')
else
Writeln('No match.');
end.
大文字/小文字を無視してヒットさせるようにするには TRegExOption を指定できるオーバーロードメソッドを使います。
program Search;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := 'Hello, world.';
if TRegEx.IsMatch(s, 'WORLD', [roIgnoreCase]) then
Writeln('Match.')
else
Writeln('No match.');
end.
TRegExOption は正規表現に使えるオプションです。
列挙値 | 意味 |
---|---|
roNone | 何もオプションが設定されないことを指定します。 |
roIgnoreCase | 大文字小文字を区別しないマッチングを指定します。 |
roMultiLine | 複数行モード。 ^ と $ の意味が、文字列全体の先頭と末尾だけでなく、各行の先頭と末尾でも一致するように変更されます。 |
roExplicitCapture | 明示的に名前または番号が付けられた (?...) 形式のグループだけが有効なキャプチャになるように指定します。 これによって、名前なしのかっこ表現 (?:...) は、構文的に間違いがなくても、非キャプチャ グループとして動作します。 |
roCompiled | 正規表現をアセンブリにコンパイルするように指定します。 これによって、実行速度は速くなりますが、起動時間が長くなります。 CompileToAssembly メソッドを呼び出している場合は、この値を Options プロパティに割り当てないでください。 |
roSingleLine | 単一行モードを指定します。 ドット(.)の意味が、(\n 以外のすべての文字ではなく)すべての文字にマッチするように変更されます。 |
roIgnorePatternSpace | エスケープされていないホワイト スペースをパターンから除去し、# でマークされたコメントを有効にします。 ただし、IgnorePatternWhitespace 値は、文字クラス内のホワイト スペースには影響しません(文字クラス内のホワイト スペースは除去されません)。 |
See also:
検索 (その2)
文字列の出現位置を知りたいのであれば、
program Search;
{$APPTYPE CONSOLE}
begin
var s := 'Hello, world.';
var Idx := Pos('world', s);
Writeln(Idx);
end.
TRegEx.Match().Index
で取得できます。
program Search;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := 'Hello, world.';
var Idx := TRegEx.Match(s, 'world').Index;
Writeln(Idx);
end.
ヒットした文字列の長さは TRegEx.Match().Length
で取れます。
See also:
置換
Delphi で文字列置換と言うと StringReplace()
ではないでしょうか?
program Search;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
begin
var s := 'Hello, world.';
s := StringReplace(s, 'world', 'delphi', [rfReplaceAll, rfIgnoreCase]);
Writeln(s);
end.
正規表現で書くとこうなります。
program Replace;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := 'Hello, world.';
s := TRegEx.Replace(s, 'world', 'delphi', [roIgnoreCase]);
Writeln(s);
end.
全置換したくない場合には TRegEx のインスタンスを生成した上で、Count を指定できるオーバーロードメソッドを使う必要があり、ちょっとだけ面倒になります。
次のコードは文字列中の l
の最初の一つだけを A
に置換します。
program Replace;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := 'Hello, world.';
var RegEx := TRegEx.Create('l', [roIgnoreCase]);
s := RegEx.Replace(s, 'A', 1);
Writeln(s);
end.
あるいはこうなります。
program Replace;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := 'Hello, world.';
s := TRegEx.Create('l', [roIgnoreCase]).Replace(s, 'A', 1);
Writeln(s);
end.
TRegEx はクラスではなく、高度なレコード型です。
See also:
置換 (その2)
検索にヒットした文字列を元に置換文字列を作るには、Evaluator を指定できるオーバーロードメソッドを使います。
次のコードは、カンマかピリオドがあれば、その後ろに改行コードを追加します。
program Replace;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
type
TDmyCls = class
class function ReplaceMethod(const Match: TMatch): string;
end;
{ TDmyCls }
class function TDmyCls.ReplaceMethod(const Match: TMatch): string;
begin
result := Match.Value + sLineBreak;
end;
begin
var s := 'Hello, world.';
s := TRegEx.Replace(s, '[,\.]', TDmyCls.ReplaceMethod);
Writeln(s);
end.
Evaluator は TMatchEvaluator 型 (メソッドポインタ) なので、VCL / Firemonkey アプリケーションからはともかく、コンソールアプリケーションだと書きにくいですね。
エスケープ処理
検索/置換を正規検索とプレーンな検索の両方で行ないたい場合、TRegEx.IsMatch()
/ Pos()
, TRegEx.Replace()
/ StringReplace()
と使い分けるのは面倒です。とはいえ、検索したい文字列に、正規表現では特殊な意味を持つ文字が含まれていると、使うメソッドを変えるか検索したい文字列を変更するしかありません。
例えば、次のような検索はエラーになります。
program Search;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := '*** Hello ***';
if TRegEx.IsMatch(s, '***') then // '*' は特殊な意味を持つ (=正規表現検索)
Writeln('Match.')
else
Writeln('No match.');
end.
正規表現検索をプレーン検索に使いたい場合には TRegEx.Escape() を使います。
program Search;
{$APPTYPE CONSOLE}
uses
System.Regularexpressions;
begin
var s := '*** Hello ***';
if TRegEx.IsMatch(s, TRegEx.Escape('***')) then // エスケープ処理を通す (≒プレーン検索)
Writeln('Match.')
else
Writeln('No match.');
end.
これで使うメソッドを統一する事ができます。
See also:
エスケープ処理 (その2)
置換文字列では次の $
から始まるメタ文字を使う事ができます。
名前 | 説明 |
---|---|
$number | グループ番号 number (10 進数) と一致した部分文字列に置換します。グループ番号が存在しない場合は何もしません。 |
${name} | グループ名 name と一致した部分文字列に置換します。グループ名が存在しない場合は何もしません。 |
$$ | "$" に置換します。 |
$& | 一致したパターン全体と同じパターンに置換します。 |
$` | 一致した場所より前にある入力文字列のすべてに置換します。 |
$’ | 一致した場所より後にある入力文字列のすべてに置換します。 |
$+ | キャプチャした最後のグループに置換します。 |
$_ | 入力文字列全体に置換します。 |
つまり、置換したい文字列にメタ文字が含まれる場合には $$
のようにエスケープする必要があります。次のコードは o
を $_$+
に置換しようとしています。
program Replace;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Regularexpressions;
begin
var s := 'Hello, world.';
var rStr := '$_$+';
s := TRegEx.Replace(s, 'o', rStr, [roIgnoreCase]);
Writeln(s);
end.
しかしながら結果は次のようになります。
HellHello, world.o, wHellHello, world.o, world.orld.
次のようにして置換文字列をエスケープすると、
var rStr := '$_$+';
rStr := StringReplace(rStr, '$', '$$', [rfReplaceAll]); // 追加
s := TRegEx.Replace(s, 'o', rStr, [roIgnoreCase]);
希望通りの動作になります。
Hell$_$+, w$_$+rld.
TRegEx.Escape()
は $ から始まるメタ文字以外もエスケープしてしまうので、置換文字列のエスケープには使えません。
var rStr := '$_$+';
rStr := TRegEx.Escape(rStr); // NG
s := TRegEx.Replace(s, 'o', rStr, [roIgnoreCase]);
+
が消えてしまいます。
Hell$_$o, w$_$orld.
おわりに
正規表現を使った所で、大したコード量の違いにはなりませんので、積極的に使ってみてはいかがでしょうか?
See also: