モーリーのメモ

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

モーリーのメモ

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

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

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

 『replace()』の第1引数の『/a/g』は、検索する文字列を正規表現で表したものです。
  • 正規表現では、検索する文字列を『/(スラッシュ)』で囲みます。
  • 末尾の『g』は、最後まで探すというフラグです。フラグは、下表のようなものがあります。
    フラグ意味
    g1個目だけでなく最後まで検索します。
    i検索の際に大文字と小文字を区別しません。
    m複数行に渡って検索します。
    *『g』フラグを付けないと1個目しか置換しません。
    *また『text.replace("a", "1");』のように通常の文字列で検索すると1個目しか置換されません。

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

      
     正規表現を使う利点は、文字のパターンで検索出来ることです。
     文字のパターンで検索することで、『"'abc','de',f"』→『"1,1,f"』のように文字の種類、長さの異なる'abc'と'de'を一度に置換することが出来ます。
     
     また、『replace()』の第2引数にコールバック関数を指定すると、見つかった文字ごとに違う処理を行うことが出来ます。
     
     上記の例を含めて『replace()』の様々な使い方をまとめました。

    使用環境

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

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

    正規表現でパターン検索

    メタ文字

     正規表現では、メタ文字を使って文字のパターンを表現します。

    • 例:メタ文字を使った正規表現

      var text = "'abc','de',f";
      var result = text.replace(/'.*?'/g, "1"); // 『'.*?'』の部分が正規表現
      // 結果:resultは"1,1,f"になる
      // 文字の種類や長さが異なる'abc'と'de'を一度に置換出来ている
      

      2行目の『'.*?'』は『'(シングルコーテーション)で囲まれている文字列』という文字のパターンを表した正規表現です。パターンで検索するので、文字の種類や長さが異なっても一度に置換出来ます。
      下表は、使用したメタ文字とその意味です。

      メタ文字意味
      .(ピリオド)任意の1文字
      *?(アスタリスク、クエスチョン)直前の文字が0文字以上続くもので、
      当てはまる中で最短のもの(最短一致)
      *最短一致については後述
      直訳すると『'.*?'』は『'(シングルコーテーション)の後に、任意の文字が0文字以上続き、再び'(シングルコーテーション)が来る文字列の中で最短のもの』という意味になります。

    メタ文字一覧

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

    最短一致と最長一致

    • 例:『'abc','de',f』から『'(シングルコーテーション)で囲まれている文字列』を置換
      『*?』:最短一致(当てはまる中で最短のもの)を検索します。
      『*』:最長一致(当てはまる中で最長のもの)を検索します。

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

      『'a』から『'(シングルコーテーション)で囲まれている文字列』を探すと、『'abc'』、『'abc','』、『'abc','de'』の3パターン見つかります。
      この中で『'abc'』が最短一致、『'abc','de'』が最長一致となります。

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

     メタ文字を文字として検索したい場合は、その文字の前に『\(バックスラッシュ)』を付けます。

    • 例:『()』で囲まれた文字を置換

      var text = "abc,(de),f";
      var result = text.replace(/(.*?)/g, "1"); // バックスラッシュを付けない
      // 『(』と『)』をメタ文字として処理
      // 結果:resultは"1a1b1c1,1(1d1e1)1,1f1"になる ←失敗
      
      var result = text.replace(/\(.*?\)/g, "1"); // バックスラッシュを付ける
      // 『(』と『)』を通常の文字として処理
      // 結果:resultは"abc,1,f"になる ←成功
      

      『(』と『)』はそのままだとメタ文字として処理されます。
      それぞれ『\(バックスラッシュ)』を付けて『\(』と『\)』とすることで通常の文字として検索されます。
      『\』は、次の1文字はメタ文字じゃないよ、という意味です。
      『\』は、キーボードの『option + ¥』で入力出来ます。

    正規表現チェッカー

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

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

    見つかった文字列を置換後に使用する方法

     "abc,de,f" → "<b>abc</b>,<b>de</b>,f"
     上記の置換は、"abc"→"<b>abc</b>"、"de"→"<b>de</b>"のように見つかった文字列を置換後に使用しています。
     この置換を行うには下記の方法を用います。

    『$&』で見つかった文字列を置換後に使用する

    • 例:見つかった文字列を<b>〜</b>で囲む置換

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

      『/abc|de/g』により、"abc"または"de"を検索しています。
      『$&』は、"abc"が見つかった時は"abc"に置換され、"de"が見つかった時は"de"に置換されます。

    『$1、$2、・・・』で見つかった文字列の一部に番号を付けて使用する

    • 例:見つかった文字列を<b>〜</b>で囲む置換

      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"になる
      

      『/(abc),(de)/g』により『"abc,de"』を検索しています。
      『(』と『)』はメタ文字です。置換後に使いたい部分を『()』で囲みます。
      『$1』は、1個目のカッコ内の『"abc"』を呼び出します。
      『$2』は、2個目のカッコ内の『"de"』を呼び出します。

    コールバック関数『function()とarguments』を使用する

     コールバック関数を使うとさらに複雑な置換が可能です。
     『replace()』の第2引数に『置換後の文字列』の替わりに、コールバック関数の『function(){}』を渡します。

    • 例:小文字を大文字に置換する

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

      『arguments[0]』がいきなり出てきますが、決まりごとです。
      検索条件に合った文字列が見つかるたびにコールバック関数が実行され、『arguments[0]』に見つかった文字列が代入されます。
      コールバック関数内で文字列を大文字に加工して『return』で返すと、それが置換後の文字列となります。

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

     『arguments[0]』だけでなく『arguments』配列には検索条件に応じて検索結果が代入されます。『arguments』配列のサイズは、下表のように正規表現に含まれる『()』の数で変わります。

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

    • 例:『''』には囲まれていない『<>』を『[]』に置換

      var text = "'<abc>',<de>,f";
      var result = text.replace(/'(.*?)'|<(.*?)>/g, // 『|』はorを示すメタ文字       
        function(){
          // '<abc>'が見つかった時のargumentsの中身
          // arguments[0]:検索条件で見つかった文字列、今回は"'<abc>'"
          // arguments[1]:検索条件の1個目の()に該当する文字列、今回は"<abc>"
          // arguments[2]:検索条件の2個目の()に該当する文字列、今回は該当なしなのでundefined
          // arguments[3]:見つかった文字列の先頭の位置(0始まり)、今回は0
          // arguments[4]:検索の対象の文字列、今回は"'<abc>',<de>,f"
      
          // <de>が見つかった時のargumentsの中身
          // arguments[0]:検索条件で見つかった文字列、今回は"<de>"
          // arguments[1]:検索条件の1個目の()に該当する文字列、今回は該当なしなのでundefined
          // arguments[2]:検索条件の2個目の()該当する文字列、今回はde
          // arguments[3]:見つかった文字列の先頭の位置(0始まり)、今回は8
          // arguments[4]:検索の対象の文字列、今回は"'<abc>',<de>,f"
          
          if(!arguments[1] && arguments[2]) { // ''に囲まれず、<>に囲まれた文字列
            return "[" + arguments[2] + "]";
          } else {
            return arguments[0]
          }
        });
      // 結果:resultは"'<abc>',[de],f"になる
      

      『/'(.*?)'|<(.*?)>/g』により『''で囲まれた文字列』または『<>で囲まれた文字列』を検索します。
      『arguments[0]』には『検索条件で見つかった文字列』が代入されます。
      『arguments[1]』には『検索条件内の1番目の()に該当する文字列』=『''で囲まれた文字列』が代入されます。
      『arguments[2]』には『検索条件内の2番目の()に該当する文字列』=『<>で囲まれた文字列』が代入されます。
      f:id:mmorley:20160526201106p:plain:w300
      これを利用して『 if(!arguments[1] && arguments[2])』によって『''に囲まれず、<>に囲まれた文字列』の場合だけ、『[]』で文字列を囲む置換を行っています。

    正規表現の『()』の囲み方の違いについて

     上記の例で、カッコの位置が『(<.*?>)』の場合は『arguments[2]』は『』となります。
     カッコの位置が『<(.*?)>』の場合は『arguments[2]』は『de』となり、『<>』の中身を取得できます。

    動的に正規表現を作成

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

    • "apple"→"りんご"、"grape"→"ぶどう"、"peach"→ "もも"に置換する

      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は"りんご,ぶどう,もも"になる
      

      『検索の文字列』と『置換後の文字列』のペアの配列を作成し、forループ内で動的に正規表現を作成して各ペアの置換を実行しています。

    『\(バックスラッシュ)に注意

     動的に正規表現を作成する場合、『new RegExp()』に渡す文字列の『\』を2重にする必要があります。

    • 例:『\』を含む文字列から正規表現を動的に作成する
      『\b』は単語の区切りを意味するメタ文字です。
      『grape』を検索する際に『grapefruit』が検索されないようにするには『/grape\b/g』と書きます。
      『new RegExp()』に渡す文字列に『\』が含まれるので『\』を下記のように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でも正規表現が使えました。(確認はブラウザ実行だけです。)