文字列に潜む特殊文字を構文として解釈されないように置き換えるRegExp.escape
RegExpオブジェクトを生成するときに正規表現のテキストをそのままの文字として扱いたい時があります。これまでは手動でエスケープする必要がありましたが、RegExp.escape()を使うことで構文として解釈されないような文字列に変換し、文字通りの並びとして検索できます。RegExp.escapeを使ってエスケープ漏れとは縁のない生活を送りましょう。
RegExp.escape()
RegExp
オブジェクトを作る時は、new RegExp(str)
のように正規表現のテキストstr
を渡します。
正規表現のテキストをそのままの文字として扱いたい場合、new RegExp(str)
のままではうまく動かないことがあります。
例えば、正規表現のテキストの中に特殊文字が含まれていると、それらが構文として解釈されてしまい、意図した通りに文字列がマッチしないです。
const str = 'abc*';
const regexp = new RegExp(str);
console.log(regexp.test('abc')); // true
console.log(regexp.test('abc*')); // true
console.log(regexp.test('ab')); // true
上記は特殊文字*
を含む文字列abc*
を正規表現のテキストとして渡しています。
*
は正規表現の構文であり、直前の文字が0回以上繰り返されることを意味します。そのため、上記の例ではabc*
だけではなく、abc
やab
にもマッチしてしまいます。
abc*
という文字列のままマッチさせたい時は、RegExp.escape()
を使うことで構文として解釈されないように特定の文字列に変換し、文字通りの並びとして検索することができます。
const str = 'abc*';
// \\x61bc\\*
const escapedStr = RegExp.escape(str);
const regexp = new RegExp(escapedStr);
console.log(regexp.test('abc')); // false
console.log(regexp.test('abc*')); // true
console.log(regexp.test('ab')); // false
abc*
という文字列がRegExp.escape()
によって\\x61bc\\*
に変換され、ab
やabc
にはマッチしなくなりました。
RegExp.escape()
は手動では達成できない複数のパターンを考慮して実装されています。
単純に正規表現の構文の前に\
を追加するだけでは対処できないケースをカバーしてくれるので、古いブラウザのバージョンをサポートしていない限りはRegExp.escape()
を使うようにしましょう。
変更の規則
先頭の文字が0~9,a~z,A~Zである場合
先ほどの例で、RegExp.escape('abc*')
は\\x61bc\\*
に変換されました。\\*
は想定通りですが、a
が\\x61
に変換されるのは、少し意外だった人もいるのではないでしょうか。
RegExp.escape()
は、テキストの先頭の文字が0~9,a~z,A~Z
である場合、特殊なエスケープをします。
これらの文字は、\x
に続く16進数コード(例: a
はASCIIコードが61なので\x61
)としてエスケープされます。
これは、別の文字列の後ろに連結して使用する場合に、前の文字列を引き継いだ構文として解釈されないようにするためです。
const str = '0';
const regexp = new RegExp('(.)\\1' + str);
console.log(regexp.test('aa0')); // false
const escapedStr = RegExp.escape(str);
const escapedRegexp = new RegExp('(.)\\1' + escapedStr);
console.log(escapedRegexp.test('aa0')); // true
上の例では、(.)\\1
の後ろに0
を連結しています。0
をエスケープしない場合、new RegExp('(.)\\10')
となり「後方参照 10」のように解釈されてしまいます。
RegExp.escape()
を使うことで、0
は\x30
に変換され、new RegExp('(.)\\1\\x30')
となり「後方参照 1」と「0」で分けて解釈してくれるので、意図した通りにaa0
がマッチします。
特殊文字
^
・$
・\
・.
・*
・+
・?
・(
・ )
・[
, ]
・{
・}
・|
・/
は、\
を前に付けてエスケープされます。
const str = '^$\.*+?()[]{}|/';
// \\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\/
const escapedStr = RegExp.escape(str);
これは、正規表現の構文として解釈されないようにするためです。
区切り文字,
・-
・=
・<
・>
・#
・&
・!
・%
・:
・;
・@
・~
・'
・`・"
は\x
に続く16進数コードとしてエスケープされます。
const str = ',-=<>#&!%:;@~`" ';
// \\x2c\\x2d\\x3d\\x3c\\x3e\\x23\\x26\\x21\\x25\\x3a\\x3b\\x40\\x7e\\x60\\x22\\x20
const escapedStr = RegExp.escape(str);
\n
のような制御文字は、\
を前につけてエスケープされます。
const str = '\f\n\r\t\v';
// \\f\\n\\r\\t\\v
const escapedStr = RegExp.escape(str);
その他の文字
タブ、全角スペースなどの空白文字は、\u
に続くUTF-16コード単位へエスケープされます。
const str = ' ';
// \\u3000
const escapedStr = RegExp.escape(str);
ab�
(文字化け対策のため、全角バックスラッシュで書くとab\uD800
)や�ab
(\uDFFFab
)のような孤立サロゲート(文字列に対してisWellFormed()
でチェックできます。)も\u
に続くUTF-16コード単位へエスケープされます。
const str = 'ab� �ab';
// \\x61b\�\\x20\�ab
const escapedStr = RegExp.escape(str);