JavaScript 文字列の一括複数文字置換


# 文字列を同時複数置換したい場合がある。

特殊用途かと思うけど、たまに文字列を置き換えるときにaをbに変えて、bをaに変えたい場合があります。

aを一旦cに変えて、bをaに変えて、cをbに変える。みたいな、対処したりする方法もあるのですが、
いろいろ汎用的に使う場合苦しくなってきます。

例えば前回の記事のときにこれが必要となりました

[JavaScript 書式を指定して日時出力 和暦対応 引用囲み対応 - Qiita](https://qiita.com/standard-software/items/f56a05d35f96022a08a9)

YYYY/MM/DD HH: mm:ss とかで日付を出力する、よくある仕組みです。

こういう場合、書式ルールで、Mayとか、Marchとか、Mondayとか、出力すると、その変換をしたあとに、Mが月の値になって変換されてしまっては困るわけです。

Mを何月かの値として変換してから、月名とか曜日を変換すれば、とは思いますが、自分のコードでは汎用的にルールを作れるようにしているので、変換順番の指定が難しいという問題がありました。

実際、他の方の記事では置き換えに苦労しているようです。

[javascriptで日付フォーマッタ - Qiita](https://qiita.com/egnr-in-6matroom/items/37e65bb642d2e158804c)

# 文字列の複数一括置換を実装する。

そこで、aをbに変更して、bをaに変更することのできる関数
replaceAllArray を実装しました。
次のような動作をします。

~~~index.html
<!DOCTYPE html>
<html lang="ja"><head>
  <meta charset="utf-8">

  <script src="https://cdn.rawgit.com/standard-software/stsLib.js/master/Source/stsLib.js/stslib_core.js"></script>

<script>

  var stsLib = require('stsLib');

  var result;
  result = stsLib.string.replaceAllArray('ab', [
    ['a', 'b'],
    ['b', 'a']
  ]);
  console.log(result);  //ba

  result = stsLib.string.replaceAllArray('abcdef', [
    ['abc', 'def'],
    ['def', '123']
  ]);
  console.log(result);  //def123

</script>
</head><body>
</body></html>

~~~

このコードは次の通りです。
ライブラリからの切り出して、そのままコピペでは動かなくてすまないです。
使用しているライブラリの他の関数もすべてコピペしたら動きますが、自分には手間すぎるので、利用されたい方におまかせします。

~~~js:stslib_core.js
      //----------------------------------------
      //・replaceAllArray
      //----------------------------------------
      //  ・配列内容に従って一斉に置き換え
      //  ・ab の文字を aをb に bをa に同時置き換えできる
      //  ・一致して置き換えを行うため
      //    AAA >> B | AA >> C のように検索文字を長いもの順に
      //    しておかないと誤動作する
      //----------------------------------------
      _.replaceAllArray = function(str, replaceArray) {
        c.assert(t.isString(str));
        c.assert(t.isArray(replaceArray));

        var keys = [];
        for (var i = 0, l = replaceArray.length; i < l; i += 1) {
          c.assert(replaceArray[i].length === 2);
          c.assert(t.isStrings(replaceArray[i][0], replaceArray[i][1]));
          keys.push(replaceArray[i][0]);
        }
        var start = 0;
        var result = '';
        var searchResult = null;
        while (true) {
          searchResult = s.indexOfAnyFirst(str, keys, start);
          if (searchResult.index === -1) {
            //検索文字が見つからなかった場合
            //ループから抜ける
            result += s.substrIndex(str, start);
            break;
          }

          if (start < searchResult.index) {
            result += s.substrIndex(str, start, searchResult.index - 1);
            start = searchResult.index;
          }
          result += replaceArray[searchResult.indexAny][1];
          start += keys[searchResult.indexAny].length;
        }
        return result;
      };
~~~
ざっとプログラムの内容を書いておくと

* 引数の型をチェックする
* 変換前の値を配列にする
* 配列でindexOfを求められる indexOfAnyFirst (stsLib.string.indexOfAnyFirst) を呼ぶ
* 文字が見つからなければ、元文字の開始位置から以降を取得して戻す
* 文字が見つかれば、元の文字の開始位置から見つかった場所まで文字を取得して
* さらに置き換え文字を取得して結果に追加する。
* 繰り返す。

ということになります。

こちらもライブラリからの切り出しですが、
indexOfAnyFirstの実装は次の通りです。

~~~js:stsLib_core.js
      //----------------------------------------
      //◇indexOfAny
      //----------------------------------------

      _.indexOfAnyFirst = function(str, searchArray, startIndex) {
        c.assert(t.isString(str));
        c.assert(t.isArray(searchArray));
        c.assert(t.isStrings(searchArray));
        startIndex = t.ifNullOrUndefinedValue(startIndex, 0);
        c.assert(t.isInt(startIndex));
        
        var result = Infinity;
        var findIndex;
        var indexAny = -1;
        for (var i = 0, l = searchArray.length; i < l; i += 1) {
          findIndex = _.indexOfFirst(str, searchArray[i], startIndex);
          if (findIndex !== -1) {
            if (findIndex < result) {
              result = findIndex;
              indexAny = i;
            }
          }
        }
        return {
          index: result === Infinity ? -1 : result,
          indexAny: indexAny
        };
      };
~~~

これを利用すれば特殊な用途でも文字列一括置換が正しく行えます。

必要になった方はご参考にしてください。