JavaScript 正規表現 ルックアラウンド
JavaScript正規表現のルックアラウンドとは
JavaScript の正規表現において「ルックアラウンド」と呼ばれているパターンマッチングには大きく分けて二種類あります。 ひとつは「ルックアヘッド」で、もうひとつは「ルックビハインド」です。
さらに、「ルックアヘッド」と「ルックビハインド」のそれぞれに、「ポジティブ」(マッチの場合) と「ネガティブ」(マッチしない場合) があります。
「ルックアラウンド」を使うと、パターン前後に特定の文字がある場合にマッチ、あるいはマッチしないという条件を記述することができます。
(?=a) JavaScript正規表現 ポジティブルックアヘッド
(?=a)はポジティブルックアヘッドであり、「次に a があればマッチする」という意味になります。
次の例では数値の部分が全てマッチしています。
const s = 'iPhone 12 15% OFF'
for (let m of s.matchAll(/\d+/g)) {
console.log(m[0])
}
// 12
// 15
ポジティブルックアヘッド (?=) を用いることで、パーセントの数値である箇所だけがマッチしています。
const s = 'iPhone 12 15% OFF'
for (let m of s.matchAll(/\d+(?=%)/g)) {
console.log(m[0])
}
// 15
単純に % をパターンに含めた場合は、 %も含めてマッチします。
const s = 'iPhone 12 15% OFF'
for (let m of s.matchAll(/\d+%/g)) {
console.log(m[0])
}
// 15%
(?!a) JavaScript正規表現 ネガティブルックアヘッド
(?!a)はネガティブルックアヘッドであり、「次にaがあればマッチしない」という意味になります。
ネガティブルックアヘッドを用いる具体例として、航空機の便名を考えてみましょう。
便名は「英数字の2桁からなる航空会社コード」と「数字4桁以内からなるフライト番号」で構成されます。 例えば NH105 や 1U97 などは正しい便名です。
これを考慮すると、便名にマッチするパターンは、英数字2桁と数字4桁以内のパターンとして [A-Z0-9]{2}\d{1,4} と書けます。
ところがもうひとつ条件があり、航空会社コードとしては数字のみ2桁はありません。つまり、はじめの2桁がともに数字である 12345 は正しい便名ではありません。
以上を配慮すると、便名にマッチするパターンは次のように書けます。
const r = /^(?!\d{2})[A-Z0-9]{2}\d{1,4}$/
console.log(r.test('NH105')) // true
console.log(r.test('1U97')) // true
console.log(r.test('12345')) // false
ネガティブルックアヘッドを利用することで、数字2桁となる航空会社コードをマッチする条件から除外しています。
(?<=a) JavaScript正規表現ポジティブルックビハインド
(?<=a)はポジティブルックビハインドであり、「前に a があればマッチする」という意味になります。
次の例では数値の部分が全てマッチしています。
const s = 'iPhone 12 for $699'
for (let m of s.matchAll(/\d+/g)) {
console.log(m[0])
}
// 12
// 699
次の例ではポジティブルックビハインド (?<=) を用いることで、アメリカドル表記の数値の箇所だけがマッチしています。
const s = 'iPhone 12 for $699'
for (let m of s.matchAll(/(?<=\$)\d+/g)) {
console.log(m[0])
}
// 699
(?<!a) JavaScript正規表現ネガティブルックビハインド
(?<!a)はネガティブルックビハインドであり、「前に aがあればマッチしない」という意味になります。
次の例では数値の部分が全てマッチしています。
const s = 'iPhone 12 for $699'
for (let m of s.matchAll(/\b\d+\b/g)) {
console.log(m[0])
}
// 12
// 699
次の例ではネガティブルックビハインド (?<!)を用いることで、アメリカドル表記の数値の箇所はマッチしていません。
const s = 'iPhone 12 for $699'
for (let m of s.matchAll(/\b(?<!\$)\d+\b/g)) {
console.log(m[0])
}
// 12