1. ホーム
  2. Web プログラミング
  3. 正規表現

ES9の正規表現の新機能 RegExpの説明

2022-01-17 19:55:22

はじめに

正規表現はデータマッチングを行う際によく使うツールで、正規表現の構文は複雑ではありませんが、複数の構文が組み合わされると、どうしようもないように感じられることがあります。

だから、正規表現はプログラマーの悪夢になっている。今日は、ES9で正規表現を弄る方法について見ていきましょう。

番号付きキャプチャグループ

正規表現はグループ化できることが分かっていて、グループは括弧で表現されるので、グループの値を取得する場合は、キャプチャグループと呼ばれます。

通常、キャプチャグループには通し番号でアクセスしますが、これをNumbered capture groupsと呼びます。

例として

const RE_DATE = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31

上の正規表現では、年、月、日にマッチする必要があり、次にexecメソッドを実行すると、マッチの配列が返されます。この配列には、マッチしたグループに関する情報が格納されます。

括弧が3つあるので、3つのグループにマッチし、1、2、3で特定のグループにアクセスすることができるのです。

上のmatchObjの出力を取り出して、その内容を見てみましょう。

[
 '1999-12-31',
 '1999',
 '12',
 '31',
 index: 0,
 input: '1999-12-31',
 groups: undefined
]

matchObjは配列であり、インデックス0にはマッチングされる文字列が格納されていることがわかる。ここで、matchObjは未定義のグループも持っており、このグループが名前付きグループであることがわかります。

名前付きキャプチャグループ

前述したように、番号付きキャプチャグループは、シリアル番号でアクセスしてデータを照合する。しかし、照合されたグループには名前がありません。

では、これらのグループに名前を付ける方法を見てみましょう。

const RE_DATE = /(? <year>[0-9]{4})-(? <month>[0-9]{2})-(? <day>[0-9]{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

matchObjの中身を見ましょう。

[
 '1999-12-31',
 '1999',
 '12',
 '31',
 index: 0,
 input: '1999-12-31',
 groups: [Object: null prototype] { year: '1999', month: '12', day: '31' }
]

ご覧のように、今回はグループに関する情報が多くなっています。

以前マッチングさせたグループ情報にマッチングさせたい場合は、番号付きグループの場合は \k で、名前付きグループの場合は \k でマッチングさせます。

例を見てみましょう。

const RE_TWICE = /^(? <word>[a-z]+)! \k<word>$/;
RE_TWICE.test('abc!abc'); // true
RE_TWICE.test('abc!ab'); // false

const RE_TWICE = /^(? <word>[a-z]+)! \1$/;
RE_TWICE.test('abc!abc'); // true
RE_TWICE.test('abc!ab'); // false

どちらの構文も使用可能です。

名前付きキャプチャグループはreplaceでも使用可能です。

グループ名を使えば、replaceの中で直接グループ名を使って参照することができます。

const RE_DATE = /(? <year>[0-9]{4})-(? <month>[0-9]{2})-(? <day>[0-9]{2})/;
console.log('1999-12-31'.replace(RE_DATE,
  '$<month>/$<day>/$<year>'));
  // 12/31/1999

replaceの第2引数は関数でもよく、関数の引数はグループアウトするものの一部である

const RE_DATE = /(? <year>[0-9]{4})-(? <month>[0-9]{2})-(? <day>[0-9]{2})/;
console.log('1999-12-31'.replace(
  RE_DATE,
  (g0,y,m,d,offset,input, {year, month, day}) => // (A)
    month+'/'+day+'/'+year));
  // 12/31/1999

上記の例では、g0 = 1999-12-31 が一致する部分文字列を示します。y, m, d は、番号付きのグループ 1, 2, 3 にマッチします。

input は入力全体です。{年、月、日}は名前付きグループにマッチします。

RegExpにおけるUnicode属性のエスケープ

ユニコード規格では、すべての文字に属性があり、簡単に言えば、その属性を用いて文字を記述しています。

例えば、General_Categoryは、xという文字のカテゴリを表しています。x: General_Category = 小文字

White_Spaceは、スペース、タブ、改行を示す。\t: White_Space = True

Ageは、その文字がUnicodeに追加された時期などを示します。

また、これらのプロパティには対応する略語がある。小文字 = Ll , 通貨記号 = Sc , など。

例として、スペースにマッチさせたいとします。従来のアプローチでは、次のような方法をとりました。

> /^\s+$/.test('\t \n\r')
true

先頭に正規表現があり、その後にテストメソッドで文字列をマッチさせ、その結果をtrueとする。

先ほどunicode属性について説明しましたが、この属性を使ってマッチングすることも可能です。

> /^\p{White_Space}+$/u.test('\t \n\r')
true

属性は \p の後に、属性値が続きます。

なお、Unicodeの属性エスケープが使われていることを示すために、正規表現にuを付加しています。

ルックアラウンド・アサーション

lookaroundアサーションは見回りアサーションと訳すことができ、マッチするオブジェクトの先行環境と後続環境がどのようなものかを判断するための正規表現における構成要素である。

ルックアラウンド・アサーションには、ルックアヘッドとルックビハインドの2種類があります。

まず、Lookaheadの使い方を見てみましょう。

const RE_AS_BS = /aa(? =bb)/;
const match1 = RE_AS_BS.exec('aabb');
console.log(match1[0]); // 'aa'

const match2 = RE_AS_BS.exec('aab');
console.log(match2); // null

ルックアヘッドは前方表示で、上記では (? =bb) をbbフォワードに合わせます。

正規表現はaabbにマッチしますが、bbは含まれないことに注意してください。

その結果、1つ目はマッチするが、2つ目はマッチしない。

を使っていることに加えて ? = を使用することもできます。 ?! は不等号を表す。

> const RE_AS_NO_BS = /aa(? !bb)/;
> RE_AS_NO_BS.test('aabb')
false
> RE_AS_NO_BS.test('aab')
true
> RE_AS_NO_BS.test('aac')
true

Lookbehindの使い方をもう一回見てみましょう。

LookbehindとLookaheadのクエリは、ちょうど逆の方向に進みます。

後方一致は ? <= を表現するために、その例を見てみましょう。

const RE_DOLLAR_PREFIX = /(? <=\$)foo/g;
'$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
  // '$bar %foo foo'

上の例では、一番上の$にマッチして、fooをbarに置き換えています。

同様に ? <! で非同等な場合を表します。

const RE_NO_DOLLAR_PREFIX = /(? <! \$)foo/g;
'$foo %foo foo'.replace(RE_NO_DOLLAR_PREFIX, 'bar');
  // '$foo %bar bar'

dotAllフラグ

通常の場合、ドット.は文字を表しますが、この文字は行末を表すことはできません。

> /^. $/.test('\n')
false

そしてdotAllは、ドットの後に導入されたsを. の後に導入された s であり、これは行末のマッチングに使用することができます。

> /^. $/s.test('\n')
true

ESでは、行の終端を表す文字がいくつかあります。

  • U+000A LINE FEED (LF) (˶‾᷄ -̫̫ ‾᷅˵)
  • U+000D カリッジリターン(CR) (\r)
  • u+2028 ラインセパレータ
  • u+2029 段落区切り記号

 概要

ES9 Regular Expressions RegExpの新機能に関するこの記事は、これに導入され、より多くのES9 Regular Expressions RegExpの内容に関連するスクリプトハウスの過去の記事を検索してくださいまたは次の関連記事を閲覧し続けることは、より多くのスクリプトハウスをサポートすることを願っています!.