JavaScriptの正規表現で、マッチする文字列を検索するexecメソッドでハマったのでメモ。
正規表現に一致する文字全てを抽出したい場合、RegExpにgフラグ(グローバルサーチ)オプションを付けて実行します。
var regex1 = RegExp('foo*','g');
あとはexecメソッドを実行するだけ、と思いきや…。
ハマりポイントその1
execメソッド一回の実行につき1つしか文字列が取得できません。
戻り値で配列が返ってくるんだから、てっきり一致する文字列が全て列挙されてるのかと思ってました。
参考
RegExp.prototype.exec()MDN web docs
MDNのサンプルを参考にwhile文で回して取得します。
マッチする文字が無くなるとnullが返ってきます。
let regex = RegExp('foo*','g');
let str = 'table football, foosball';
let result_array;
while ((result_array = regex.exec(str)) !== null) {
console.log('マッチ:' + result_array[0]);
}
ハマりポイントその2
うっかり空文字とマッチすると無限ループ。
先ほどのサンプルを参考に実装していたのですが、うっかり空文字とマッチしてしまうパターンを書くとindexが進まなくなり無限ループになってしまいました。
let regex = RegExp('.*?(?=ドーム)', 'g');
let str = '東京ドーム, 福岡ドーム, 大阪ドーム, ナゴヤドーム, 西武ドーム, 札幌ドーム';
let result_array;
while ((result_array = regex.exec(str)) !== null) {
console.log('ドーム所在地:' + result_array[0]);
}
上記コードは「ドーム」の前の文字を取得したくて書いたのですが(無限ループ以前に .*?(?=ドーム) だと意図した結果にはなりませんが、それは置いといて)、
- 一度目のexec実行では意図通り '東京' の文字列が取得できます。このときに'東京'の文字分だけregexのindexが進み 2 になります。
- 二度目以降は量指定子が * (0回以上の繰り返し)になっているため、そのまま index 2 の位置で条件一致。ただの空文字('')が取得できてしまいます。
- 文字列長が0のためindexが進まず、三度目以降もずっとindexが2のまま無限ループ。
.*?(?=ドーム) だったのを .+?(?=ドーム) 「1回以上の繰り返し」にすれば回避できるのですが、正規表現パターンを間違えるたびに無限ループになるのは大変なので、空文字チェックも加えて無限ループになるのを避けられるように修正しました。
let regex = RegExp('.*?(?=ドーム)', 'g');
let str = '東京ドーム, 福岡ドーム, 大阪ドーム, ナゴヤドーム, 西武ドーム, 札幌ドーム';
let result_array;
while ((result_array = regex.exec(str)) !== null && result_array[0] != '') {
console.log('ドーム所在地:' + result_array[0]);
}
while文の繰り返し条件にresult_array[0] != '' で空文字チェックを加えました。
これなら永久パターンを書いてしまっても大丈夫。
そして最後に。
正しく所在地が取得できるパターンを書くと
[^(, ドーム)].+?(?=ドーム)
でした。
正規表現の確認が出来るツールを作ったので是非こちらもチェックしてみてください。