場合によっては、別の文字列が後続または先行するパターンに一致するものだけを見つけたい場合があります。
そのためには、「先読み」と「後読み」と呼ばれる特別な構文があり、まとめて「ルックアラウンド」と呼ばれます。
まずは、1 turkey costs 30€ のような文字列から価格を見つけましょう。つまり、数字の後に € 記号が続きます。
先読み
構文は X(?=Y) で、「X を探しますが、Y が後続する場合にのみ一致します」という意味です。X と Y の代わりに任意のパターンを使用できます。
€ が後続する整数の場合、正規表現は \d+(?=€) になります。
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=€)/) ); // 30, the number 1 is ignored, as it's not followed by €
注意: 先読みは単なるテストであり、括弧 (?=...) の内容は結果 30 に含まれません。
X(?=Y) を探す場合、正規表現エンジンは X を見つけ、その直後に Y があるかどうかを確認します。そうでない場合、潜在的な一致はスキップされ、検索は続行されます。
より複雑なテストも可能です。たとえば、X(?=Y)(?=Z) は次のことを意味します。
Xを見つける。YがXの直後にあるかどうかを確認する(そうでない場合はスキップする)。ZもXの直後にあるかどうかを確認する(そうでない場合はスキップする)。- 両方のテストに合格した場合、
Xは一致し、そうでない場合は検索を続行します。
言い換えれば、そのようなパターンは、Y と Z が同時に後続する X を探していることを意味します。
これは、パターン Y と Z が相互に排他的でない場合にのみ可能です。
たとえば、\d+(?=\s)(?=.*30) は、スペース (?=\s) が後続し、その後にどこかで 30 がある (?=.*30)\d+ を探します。
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1
私たちの文字列では、それはまさに数字 1 に一致します。
負の先読み
同じ文字列から価格ではなく数量が欲しいとしましょう。それは数字 \d+ で、€ が後続しません。
そのためには、負の先読みを適用できます。
構文は X(?!Y) で、「X を検索しますが、Y が後続しない場合にのみ一致します」という意味です。
let str = "2 turkeys cost 60€";
alert( str.match(/\d+\b(?!€)/g) ); // 2 (the price is not matched)
後読み
注意: 後読みは、Safari、Internet Explorer などの V8 以外のブラウザではサポートされていません。
先読みでは、「後続するもの」の条件を追加できます。
後読みも似ていますが、後ろを見ます。つまり、前に何かがある場合にのみパターンに一致させることができます。
構文は次のとおりです。
- 正の後読み:
(?<=Y)Xは、Xに一致しますが、前にYがある場合にのみ一致します。 - 負の後読み:
(?<!Y)Xは、Xに一致しますが、前にYがない場合にのみ一致します。
たとえば、価格を米ドルに変更してみましょう。ドル記号は通常数字の前にあるので、$30 を探すには、(?<=\$)\d+ を使用します。つまり、$ が先行する金額です。
let str = "1 turkey costs $30";
// the dollar sign is escaped \$
alert( str.match(/(?<=\$)\d+/) ); // 30 (skipped the sole number)
そして、数量、つまり $ が先行しない数字が必要な場合は、負の後読み (?<!\$)\d+ を使用できます。
let str = "2 turkeys cost $60";
alert( str.match(/(?<!\$)\b\d+/g) ); // 2 (the price is not matched)
キャプチャグループ
一般に、ルックアラウンド括弧内の内容は結果の一部になりません。
たとえば、パターン \d+(?=€) では、€ 記号は一致の一部としてキャプチャされません。これは当然のことです。数字 \d+ を探しているのに対し、(?=€) は € が後続する必要があるかどうかをテストするだけです。
しかし、状況によっては、ルックアラウンド式も、またはその一部もキャプチャしたい場合があります。それは可能です。その部分を別の括弧で囲むだけです。
以下の例では、通貨記号 (€|kr) は金額とともにキャプチャされます。
let str = "1 turkey costs 30€";
let regexp = /\d+(?=(€|kr))/; // extra parentheses around €|kr
alert( str.match(regexp) ); // 30, €
後読みの場合も同じです。
let str = "1 turkey costs $30";
let regexp = /(?<=(\$|£))\d+/;
alert( str.match(regexp) ); // 30, $
まとめ
先読みと後読み(一般に「ルックアラウンド」と呼ばれます)は、前後のコンテキストに応じて何かを一致させたい場合に役立ちます。
単純な正規表現の場合は、同様のことを手動で行うことができます。つまり、コンテキストに関係なくすべてに一致させ、ループ内でコンテキストによってフィルタリングします。
str.match (フラグ g なし) と str.matchAll (常に) は、index プロパティを持つ配列として一致を返すため、テキスト内の正確な位置がわかり、コンテキストを確認できます。
しかし、一般的にルックアラウンドの方が便利です。
ルックアラウンドの種類
| パターン | 種類 | 一致 |
|---|---|---|
X(?=Y) |
正の先読み | Y が後続する場合の X |
X(?!Y) |
負の先読み | 負の先読み |
|
(?<=Y)X | 正の後読み |
|
(?<!Y)X | 負の後読み |
前のレッスン次のレッスン