狛ログ

2022年2月24日木曜日

【JavaScript】メールテンプレート内のプレースホルダーを独自に式展開(置換)する方法。

オフィス狛 技術部のyuckieee(ゆっきー)です。

■はじめに

今回は、私が関わったプロジェクトで「ユーザがメールテンプレートを自由に編集できるようにしたい!」という要望を受けて、その実現方法を検討・実装した際のお話です。
要件実現にあたって使用したのがJavaScriptの「replace」という関数なのですが、この関数が非常に使える子で...ふるえました(,,゚Д゚)。 ←初心者感丸出しなのと語彙力(笑)

今後も活用する場面がありそうなので、自分の理解向上と備忘録としてコチラにまとめます。

■今回の要件整理

最初に話したとおり、今回の要望はメールテンプレートをユーザが自由に編集・管理したいというものです。また、この要件には「メールの宛先によって一部の内容(名前とか、契約内容とか)はシステムで可変となるようにしたい」という要望も含まれています。
なので、ざっくりこんな感じの要件です。

[要件箇条書き]
     ①メールテンプレートは外部に保存可能とし、ユーザが自由に編集できるようにして欲しい
       ②ユーザが可変指定した箇所は、システムで自動的に置き換えて欲しい

      可変にしたいと言うのは、メールテンプレート内にプレースホルダーを埋め込みたいというイメージです。 そのため、イメージとしては、以下のようにユーザがメールテンプレートを定義したらプレースホルダー(${})部分は、該当する情報に置き換えられて欲しいという感じです。
      (まんま、JavaScriptのテンプレートリテラル内で式展開させる感じと同じです。)

      ■メールテンプレート
      ${last_name} {first_name} 様
      この度は、${company_name}の${plan_name}にご登録いただき、ありがとうございました。
       ↓
      ■メール送信内容
      オフ狛 ゆっきー 様
      この度は、株式会社オフ狛のスペシャルプランにご登録いただき、ありがとうございました。
      

      ■検討結果と実装内容

      テンプレートリテラレルを使えば解決じゃん?と思うのですが、外部から読み込んだ文字列にプレースホルダーが書いてあったとしても式展開されず、単なる文字列になっちゃうんですよね....(´・ω・)
      まぁ、そうですよね、、、ということで、結果、独自にプレースホルダー(${})を置換する関数を作ることにしました。それがコチラ↓

      /**
       * テンプレート置換処理
       * @param  {string} template // 外部から読み込んだメールテンプレート文字列
       * @param  {object} jsonObj  // メールテンプレート内のプレースホルダー置換対象情報
       */
       const templateRepalcer = (template, jsonObj) 
                               => template.replace(/\${([^}]+)}/g, (_, prop) => jsonObj[prop]);
      

      以上です!

      ■実装関数の使用例

      つづいて、実装した関数を使用して簡単な使い方例を以下に紹介します。

      // メールテンプレートのテンプレートリテラレルを顧客情報に置き換える関数を定義
       const templateRepalcer = (template, jsonObj) 
                               => template.replace(/\${([^}]+)}/g, (_, prop) => jsonObj[prop]);
      
      // メールテンプレート(!!実際は外部(DBなど)から読み込む!!)
      const mailTempalte = "${last_name} ${first_name} 様\nこの度は、株式会社オフ狛の${plan_name}にご登録いただきありがとうございました。"
      
      // 顧客情報(!!実際は外部(DBなど)から読み込む!!)
      const userInfo = {
          "user_id": "ID00001",
          "last_name": "オフ狛",
          "first_name": "ゆっきー",
          "plan_name": "スペシャルプラン",
          "mail_address": "yuckieee@officekoma.co.jp"
      }
      
      // メールテンプレートのプレースホルダーを顧客情報に入れ替え
      const mailContents = templateRepalcer(mailTempalte, userInfo);
      
      // 出来上がった変数を本文に編集すれば出来上がり!(この例ではコンソール出力のみ)
      console.log(mailContents);
      

      上記実行の結果、コンソールに出力される内容は以下のとおりです。

      オフ狛 ゆっきー 様
      この度は、株式会社オフ狛のスペシャルプランにご登録いただきありがとうございました。
      

      以降、実装関数について詳細を解説していこうと思います。

      ■関数の詳細説明

      この関数を説明する前にreplace関数についての知識が必要ですが、こちらの詳細は公式(?)ページを参照された方が分かりやすいですし、ここでの説明を割愛します。

      [replace関数の説明サイト]
      MDN Web Docs:String.prototype.replace()
      JavaScript Primer:文字列の置換/削除

      作成した関数は2つあり、replace関数を呼ぶ大元のtemplateReplacerという関数と、replace関数の第二引数で使用する無名関数です。 templateReplacer自体は、テンプレートと差し替えデータを引数に、replace関数を呼び出しているだけですので説明は省略します。
      以下より、replace関数の第2引数で指定している無名関数に焦点をあてて説明したいと思います。

      まず、replace関数全体を眺めてると、以下のとおり、第2引数は、第1引数の結果を引き継いで処理を行う作りとなっていることが分かります。

      template.replace(/\${([^}]+)}/g, (_, prop) => jsonObj[prop]);
      [置換の母体となる文字列].replace(第1引数:[置換対象を正規表現で指定], 第2引数:[第一引数の結果を元に置換処理]);

      無名関数を説明する上で、第1引数も重要となってくるため、第1引数と第2引数の両方を順に説明します。

      第1引数: 置換対象の文字列又は、正規表現を指定

      replace関数の第1引数には、置換対象にしたい文字列を固定で指定するか、正規表現を使って指定する事ができます。
      今回は、汎用性を高めるために正規表現を使用しています。
      なお、/\${([^}]+)}/の箇所は、書き方は色々あると思うので、正規表現をググってみてください。
      .?*とかでも行けると思います。
      ()表記については、第2引数内で説明します。正規表現による抽出とは直接関わりません。

      また、/gを指定することで置換の母体となる文字列全体での抽出が可能になります。
      この指定を行わない場合は、対象が複数あった場合でも、最初に正規表現にひっかかった1件だけが置換される結果となるので注意が必要です。

      第2引数: 置換する文字列又は、置換処理を指定

      第2引数には、置換後の文字列を固定で指定するか、処理自体を指定することが出来ます。今回紹介した関数では、第2引数に無名関数を定義して、置換処理を行う形をとっています。
      なお、第1引数での結果をもとに、以下の引数を無名関数に引き渡すことが可能です。
      ※ブラウザによっては、5つ引数があるようですが、ここでは説明対象外とします。

      (match, p1, p2..., offset, string )

      それぞれの具体的な値について説明します。
      ※実装例で上げたメールテンプレート中の${last_name}を例に上げて説明します
      引数 説明
      match 第1引数の正規表現にマッチした文字列が設定されます。
      今回の例で言うところの${last_name}が該当箇所です。なお、今回使用しないため、関数では_と表記しています。
      p1,p2... 第1引数でキャプチャした文字列が引数として渡されます。
      キャプチャは、第1引数の正規表現内でキャプチャしたい箇所を()で囲むことで指定が可能です。
      複数指定した場合は、指定した分引数が増えていきます。今回は1対1で置換したかったので1つだけ指定しています。
      今回の例ではlast_nameという箇所が抜き出されることになります。
      offset 第1引数の正規表現でヒットした文字列の最初の位置を返します。${last_name}であれば、開始位置の0がセットされる感じです。今回は使用していません。
      string 検索対象全体がセットされます。今回でいうと"${last_name} ${first_name} 様\nこの度は、株式会社狛の"${plan_name}にご登録いただきありがとうございました。"です。今回は使用していません。

      上記の引数を元に置換対象を指定していますが、やっていることはJSONのキーを指定して、キーに紐づく値を取得しているだけです。

      (_, prop) => jsonObj[prop]

      今回の例では、以下のようなJSONが与えられていますので、第一引数でキャプチャしたキー(last_name)を元に、JSONのキーに紐づく値(オフ狛)を取得し、それを置換対象としています。

      {
          "user_id": "ID00001",
          "last_name": "オフ狛",
          "first_name": "ゆっきー",
          "plan_name": "狛スタンダードプラン",
          "mail_address": "yuckieee@officekoma.co.jp"
      }
      

      ■まとめ

      私は、今までreplace関数を単純に置換してくれるだけの関数ででしょ?って思っていました。
      ですが、こんなに汎用性の高い関数であるということを知って、目からウロコ...本当にreplace関数さん申し訳ありません...!!!ってなりました^^;
      特に引数として関数を指定するというスタイルは、自分の作る関数にも活かして行けると面白いなと感じます。

0 件のコメント:

コメントを投稿