[JavaScript] String.replace / RegExpのコンストラクタへ渡す引数のエスケープ

String.replaceって結構ややこしいよね…/参考リンク

String.replace( regexp, replacement )

必要に応じて どちらの引数に対してもエスケープが必要となる。

普通の使い方

document.write(
  'ABCDE'.replace(/(b)(c)(d)/i, "$3O$1")
);

結果

ADOBE

第一引数に文字列を入れたときの動作が String.match とぜんぜん違うので注意⇒string-match/#use-string

document.write(
  'ABCDE'.replace(/(b)(c)(d)/i, '$$3O$$1')
);

結果

A$3O$1E

キャプチャを置き換えずにそのままドル記号を含む文字列に置き換えたい時は、ドル記号をドル記号自身でエスケープする。…と思うんだけどググっても答えを探しきれなかったのでテキトーに試したらできた。これでいいんだよね…?

ドル記号は何に置き換えられる?

document.write(
  'L123R'.replace(/(1)(2)(3)/, "|$1,$2,$3,$+,$`,$'|")
);

結果

L|1,2,3,3,L,R|R
$11番目のキャプチャ RegExp.$1 に置き換えられる
$22番目のキャプチャ RegExp.$2 に置き換えられる
$33番目のキャプチャ RegExp.$3 に置き換えられる
$+最後のキャプチャに置き換えられる。上のスクリプトの場合は RegExp.$3 と置換されることになる
$`最後のマッチングの前のテキスト RegExp.leftContext に置き換えられる
$'最後のマッチングの後ろのテキスト RegExp.rightContext に置き換えられる

RegExpのコンストラクタへ渡す引数のエスケープ

document.write(
  '?'.replace(/\?/, 'FOO'),
  '?'.replace(new RegExp( '\\?' ),  'BAR'),
  '\\'.replace(/\\/, 'foo'),
  '\\'.replace(new RegExp( '\\\\' ), 'bar')
);

結果

FOOBARfoobar

RegExpのインスタンスへ入れた引数は2回に展開される(Javascriptのパース時とRegExpのコンストラクタに渡した時)。だから以下のようにエスケープしてRegExpのコンストラクタに渡す。

  • \ ⇒ '\\'
  • ] ⇒ '\]'
  • \' ⇒ '\\\'' or "\\'"
  • ] ⇒ '\\\]'

    var a = '\\';
    var b = '\\\\';
    document.write(
    a.replace( new RegExp( b ), 'TEST')
    );
    

結果

TEST

変数から渡すときも当然同じ。

var a = '\\';
var b = a.replace( new RegExp( '\\\\' ), '\\\\' );
document.write(
  a.replace( new RegExp( b ), 'HOGE')
);

結果

HOGE

頭痛くなりそう

覚えたことの練習がてらphpのstr_replaceっぽい関数を

JavaScriptで書いてみる。phpのstr_replaceの動作を厳密には知らないからテキトーだけども…

// これは preg_quote っぽい関数
function preg_quote( str, delimiter ){
  str = str
    .toString()
    .replace(/\\/g, '\\\\')
    .replace(/\^/g, '\\^')
    .replace(/\$/g, '\\$')
    .replace(/\./g, '\\.')
    .replace(/\*/g, '\\*')
    .replace(/\+/g, '\\+')
    .replace(/\?/g, '\\?')
    .replace(/\(/g, '\\(')
    .replace(/\)/g, '\\)')
    .replace(/\[/g, '\\[')
    .replace(/\]/g, '\\]')
    .replace(/\{/g, '\\{')
    .replace(/\}/g, '\\}')
    .replace(/\|/g, '\\|')
    .replace(/\=/g, '\\=')
    .replace(/\!/g, '\\!')
    .replace(/\</g, '\\<')
    .replace(/\>/g, '\\>')
    .replace(/\:/g, '\\:')
    .replace(/\-/g, '\\-');

  if( typeof delimiter === 'string' || delimiter instanceof String && delimiter.length === 1 ){
    var r = '\\';
    r += delimiter;
    var h = '\\';
    h += delimiter;
    str = str.replace(new RegExp(r, 'g'), h);
  }
  return str;
}

// これが str_replace っぽい関数
function str_replace(search, replace, subject){
  if( search instanceof Array === false ) search = [search];
  if( replace instanceof Array === false ) replace = [replace];
  if( subject instanceof Array === false ) subject = [subject];

  for(var i = 0; i < subject.length; i++){
    for(var j = 0; j < search.length; j++){
      if(replace[j] === undefined){
        replace[j] = '';
      }else{
        replace[j] = replace[j].toString().replace(/\$/g, '$$$$')
      }
      subject[i] = subject[i].toString().replace(
        new RegExp( preg_quote(search[j]), 'g'),
        replace[j]
      );
    }
  }

  if( subject.length === 1 ){
    return subject[0];
  }else{
    return subject;
  }
}

// できたものを動かしてみる
var a = str_replace('(.*?)', '$1', '--(.*?)--');
var b = str_replace([1,2,3],[9,8],[12,333222111]);
document.write(a, "\n", b[0], "\n", b[1]);

結果

--$1--
98
888999

メモ:途中で以下のようなコードを書いてハマった…↓

var x = "";
if( ! x instanceof Array){
  alert('TRUE');
}else{
  alert('FALSE');
}

instanceof 演算子の優先順位が低いためにこれでは常に FALSE がアラートされてしまう。

! (x instanceof Array) または x instanceof Array === false が正しい。

Share
関連記事