モーリーのメモ

アプリ開発等(プログラミング、CG作成)、興味を持ったことを実践してまとめるブログです。

モーリーのメモ

replace()の機能のまとめ!『正規表現、function()、arguments、RegExp()』:JavaScript

 『replace()』は、文字列の置換を行う関数です。
 基本的な使い方は、下記の通りです。

var text = "abcabc";
var result = text.replace(/a/g, "1"); // "abcabc"内の"a"検索して"1"に置換
// 結果:resultは"1bc1bc"になる

 第1引数の『/a/g』は、検索する文字列を正規表現で表したものです。
 正規表現では、検索する文字列を『/(スラッシュ)』で囲みます。
 末尾の『g』は、最後まで探すという指令です。
 『g』を付けないと1個目しか置換しません。
 なお、『"a"』のように通常の文字列でも検索出来ますが、その場合も1個目しか変換されません。

var text = "abcabc";
var result = text.replace(/a/, "1");  // resultは"1bcabc"になる(1個目だけ置換)
var result = text.replace("a", "1");  // resultは"1bcabc"になる(1個目だけ置換)

 正規表現を使う利点は、”a”のように決まった文字の検索だけではなく、『''に囲まれている部分』等の文字のパターンでも検索出来ることです。
 
 また、replace()の第2引数にはコールバック関数が設定可能で、検索した文字ごとに違う処理を行うことが出来ます。
 
 replace()の様々な使い方をまとめました。

使用環境

 私が使用している環境です。

 記事内のコードは、google ドキュメントスクリプトエディタで動作確認しました。

正規表現でパターン検索

メタ文字

 正規表現では、メタ文字と呼ばれる記号を使って、文字のパターンを表現します。
 例えば『''に囲まれている文字列』は、正規表現では『'.*?'』と書きます。
 『.(ピリオド)』と『 *?(アスタリスク、クエスチョン)』はメタ文字で、下記のような意味を持っています。

メタ文字意味
.(ピリオド)任意の1文字
*?(アスタリスク、クエスチョン)直前の文字が0文字以上続くもので、
当てはまる中で最短のもの(最短一致)

var text = "'abc','de',f";
var result = text.replace(/'.*?'/g, "1");
// 結果:resultは"1,1,f"になる

 パターンで検索するので、上記のように『''』の中の文字の種類や長さが違っても置換出来ます。

最短一致と最長一致

 ちなみに『*?』でなく『*』だけにすると最長一致になります。

var text = "'abc','de',f";
var result = text.replace(/'.*'/g, "1"); 
// 結果:resultは"1,f"になる

 ↑では『'abc','de'』が一番外の『''』で囲まれているとみなされています。

メタ文字一覧

 上記以外にも様々なメタ文字があります。下記のサイトで一覧を見ることが出来ます。
www.megasoft.co.jp

メタ文字自身を検索するには『\(バックスラッシュ)』を付ける

 『(』と『)』はメタ文字なので、カッコに囲まれた文字を検索したい場合は、『\(.*?\)』と書きます。
 『\』は、今回の『(』と『)』はメタ文字じゃないよ、という意味です。
 『\』は、キーボードの『option + ¥』で入力出来ます。

正規表現チェッカー

 狙い通りに正規表現を作成するのはなかなか難しいです。
 下記のサイトでは、リアルタイムで入力結果が反映されるので、結果を確認しながら正規表現が作成することが出来ます。

    www.regexr.com f:id:mmorley:20161108153739p:plain:w400

マッチした文字列を置換結果に使用する方法

 決まった文字に置換するのではなく、検索にマッチした文字をそれぞれ加工したい場合などに使用する方法です。
 
 例えば、下記のように"abc"と"de"をhtmlのbタグで囲みたい場合です。
 "abc,de,f" → "<b>abc</b>,<b>de</b>,f"
 ↓のように決まった文字に置換するのでは、全部同じ文字になってしまいます。

var text = "abc,de,f";
var result = text.replace(/abc|de/g, "<b>abc</b>");  
// 結果:resultは"<b>abc</b>, <b>abc</b>,f"になる

 こういったケースに対応するためには、下記のような方法を用います。

『$&』でマッチした文字列を使用

 マッチした文字列を置換に使用する場合は『$&』を使用します。
 下記の例では、マッチした文字列をhtmlのbタグで囲む処理をしています。

var text = "abc,de,f";
var result = text.replace(/abc|de/g, "<b>$&</b>"); 
// 結果:<b>abc</b>,<b>de</b>,f

『$1、$2、・・・』でマッチした文字列を部分ごとに使用

 下記のように正規表現で部分ごとに使用する箇所を"()"で囲みます。複数可能です。

 /(,)(de)/g

 1個目の"(abc)"にマッチした文字列は『$1』、
 2個目の"(de)"にマッチした文字列は『$2』で使用します。
 下記の例では、"abc"をhtmlのbタグ、"de"をhtmlのiタグで囲みます。

var text = "abc,de,f";
var result = text.replace(/(abc),(de)/g, "<b>$1</b>,<i>$2</i>");
// 結果:resultは"<b>abc</b>,<i>de</i>,f"になる

コールバック関数の使い方『function()とarguments』

 コールバック関数を使うと『$&』等を使うよりも複雑な処理が出来ます。
 下記のように、『replace()』の第2引数にfunction(){}を渡します。

var text = "'abc','de',f";
var result = text.replace(/'.*?'/g,           
  function(){
    return arguments[0].toUpperCase(); // 見つけた文字を大文字に変換して返す
  });
// 結果:resultは"'ABC','DE',f"になる

 変数の宣言をすること無く、いきなりarguments[0]が出てきますが、argumentsには検索結果が自動的に入っています。
 検索条件にマッチした文字列が見つかるごとにfunction(){}が呼び出されます。
 arguments[0]の中身もその都度、見つかった文字列に変わっています。

arguments(検索結果)で条件分岐

 argumentsを利用して、『''』には囲まれず、『<>』に囲まれた文字を置換します。
 正規表現では『''』または『<>』で囲まれた文字列を検索します。
 検索結果であるargumentsの内容によって、条件に合うか判別しています。

var text = "'<abc>',<de>,f";
var result = text.replace(/'(.*?)'|<(.*?)>/g, // 『|』はorを示すメタ文字       
  function(){
    // '<abc>'が見つかった時のargumentsの中身
    // arguments[0]:"'<abc>'"、正規表現で見つかった文字列
    // arguments[1]:"<abc>"、正規表現の1個目の()に該当する文字列
    // arguments[2]:undefined、正規表現の2個目の()に該当する文字列はないのでundefined
    // arguments[3]:0、見つかった文字列の先頭の位置、0始まり
    // arguments[4]:"'<abc>',<de>,f"、検索の対象の文字列

    // <de>が見つかった時のargumentsの中身
    // arguments[0]:"<de>"、正規表現で見つかった文字列
    // arguments[1]:undefined、正規表現の1個目の()に該当しないのでundefined
    // arguments[2]:de、正規表現の2個目の()該当する文字列
    // arguments[3]:8、見つかった文字列の先頭の位置、0始まり
    // arguments[4]:"'<abc>',<de>,f"、検索の対象の文字列
    
    if(!arguments[1] && arguments[2]) { // ''に囲まれず、<>に囲まれた文字列
      return "[" + arguments[2] + "]";
    } else {
      return arguments[0]
    }
  });
// 結果:resultは"'<abc>',[de],f"になる

 argumentsの中身は下記のようになります。
 argumentsの配列のサイズは、正規表現に含まれる『()』の数で変わります。
f:id:mmorley:20160526201106p:plain:w300

arguments[0] 見つかった文字列
arguments[1] 正規表現の1個目の()内に該当する文字列
arguments[n] 正規表現のn個目の()内に該当する文字列
arguments[arguments.length-2] 見つかった文字列の先頭の位置、0始まり
arguments[arguments.length-1] 検索の対象の文字列
正規表現の『()』の囲み方の違いについて

 上記の例で、(<.*?>)で検索するとarguments[2]はですが、
 <(.*?)>で検索するとarguments[2]はdeとなり、<>の中身を取得できます。

動的に正規表現を作成

 『new RegExp()』を使うと通常の文字列から正規表現を作成できます。
 『new RegExp("<りんご>", 'g')』は『/<りんご>/g』です。

var result = "<apple>,<grape>,<peach>";
var arrReplaceData = [
  ["apple", "りんご"],
  ["grape", "ぶどう"],
  ["peach", "もも"]
];
for(var i = 0; i < arrReplaceData.length; i++){
  var replaceData = arrReplaceData[i];
  result = result.replace(
    new RegExp("<" + replaceData[0] + ">", 'g'), 
    function() {
      return replaceData[1];
    });
}
// 結果:resultは"りんご,ぶどう,もも"になる
バックスラッシュに注意

 『\b』は単語の区切りを意味するメタ文字です。
 grapeとgrapefruitを区別したい時、『/grape\b/g』と書きます。
 これを動的に作成する場合は下記のように『\』を2重にする必要があります。

var result = "apple,grapefruit,peach";
var arrReplaceData = [
  ["apple\\b", "りんご"],
  ["grape\\b", "ぶどう"],
  ["peach\\b", "もも"]
];
for(var i = 0; i < arrReplaceData.length; i++){
  var replaceData = arrReplaceData[i];
  result = result.replace(
    new RegExp(replaceData[0], 'g'), 
    function() {
      return replaceData[1];
    });
}
// 結果:resultは"りんご,grapefruit,もも"になる

 ちなみに通常の文字として『"\t"』を検索する場合は

var result = "abc\\tde";
var replaceData = ["\\\\t","タブ"];
result = result.replace(
  new RegExp(replaceData[0], 'g'), 
  function() {
    return replaceData[1];
  });
// 結果:resultは"abcタブde"になる

 バックスラッシュだらけです。

あとがき

 複雑な正規表現は、ぱっと見では意味がわかりにくいです。
 思い通りに検索できる正規表現を作るのには慣れが必要です。
 いろいろな文字列を探すための正規表現の例をネットで見つけることも出来ますが、その複雑さに嫌になることも多々あります。
 個人的には、複雑な正規表現を組むより、argumentsを利用してプログラム的に処理したほうがわかりやすい場合もあります。
 処理速度には、全て正規表現で処理するより遅くなるのかもしれませんが。
 
 Cocos Creatorでも正規表現が使えました。(確認はブラウザ実行だけです。)