まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:template

Node-RED 環境に標準で提供されている標準ノードの1つに template ノードがあります(下図赤枠):
2024091001


この template ノードは次のノードへ送る msg.payload (など)の値をテンプレートをベースに、一部を変数化して生成するノードです。HTML の一部を変数化して扱いたいとか、最近だと生成 AI のプロンプトを作成する際のテンプレートとしても便利に使えるので、今後活用の機会があるかもしれません。

ところで、この template ノードの標準記法として設定されているのが mastache (「マスタッシュ」)記法というものです:
2024091002


この mastache 記法でどんなことができるのか調べてみました。以下で3つの方法を紹介しますが、とりあえず以下のようなシンプルなフローを作っておきます。inject -> function -> template -> debug の各ノードを一直線につないだだけのシンプルなフローです:
2024091003


このうち inject ノードは単に処理をスタートさせるためのスイッチとしてだけ使うのであまり意味はありません(このノードの左にある部分をクリックするとフローが実行されます)。また最後の debug ノードはデフォルトで msg.payload の内容を画面右側のデバッグウィンドウに出力するのですが、これもデフォルトのままで大丈夫。つまりこの2つは特に設定変更しません。

そして2つ目の function ノードをダブルクリックして編集状態にして、以下の内容を記載します:
msg = {
  payload: {
    value0: "0",
    value1: 1,
    value2: "<b>2.0</b>"
  },
  value1: 1,
  value2: 2.2,
  array1: [ 2, 3, 5, 7, 11, 13, 17, 19 ],
  array2: [
    { name: "apple" },
    { name: "orange" },
    { name: "grape" }
  ]
}
return msg;
2024091004


この3つのノード(inject, function, debug)はこの後特に変更しません。 以下で mastache 記法にちうて3つの方法を紹介しますが、全て上述の内容の msg を受け取る仮定として、 template ノードのみ変更して動作を確認していきます。


【テンプレート内に1つの値を指定する場合】
まずはシンプルな例を紹介します。template ノードを開き、以下の内容を記入してください:
payload.value0 = {{payload.value0}}
payload.value1 = {{payload.value1}}
payload.value2 = {{payload.value2}}

value1 = {{value1}}
value2 = {{value2}}
2024091101


そしてこの状態でフローをデプロイし、inject ノードをクリックしてフローを実行します。すると画面右のデバッグペインには以下のように表示されます:
payload.value0 = 0
payload.value1 = 1
payload.value2 = &lt;b&gt;2.0&lt;&#x2F;b&gt;

value1 = 1
value2 = 2.2

2024091102


msg オブジェクトの一部が変数としてテンプレートの中を埋めて出力されていることがわかると思います。普通の変数であれば {{ と }} でメッセージ内の値を指定することで表示できそうですね。

ちょっと気になる点があるとすれば "payload.value2" の値でしょうか。そのまま HTML で使えるように一部文字がエスケープされた状態になっています。このエスケープが不要である場合は {{ と }} ではなく、 {{{ と }}} で囲うようにするだけです。試しに template の内容を以下のように書き換えてみます:
payload.value0 = {{payload.value0}}
payload.value1 = {{payload.value1}}
payload.value2 = {{{payload.value2}}}

value1 = {{value1}}
value2 = {{value2}}
2024091103


そしてフローを再デプロイし、inject ノードで処理を実行すると、今度は文字列がエスケープされずにそのまま表示されます:
2024091104


【テンプレート内に配列値を指定する場合】
テンプレート内に1つ1つの値を個別に直接指定する方法は上述のような方法でした。では「配列」を扱う場合、テンプレート内にどのように記述するべきでしょうか? 今回の場合だと msg.array1 が配列になっているので、これをどのように列挙するか? というケースを考えます。

その答がこれです。template の内容を以下のように変更します:
{{#array1}}
{{.}}
{{/array1}}

2024091105


この template でデプロイして inject ノードをクリックして処理が実行されると、デバッグ画面に下図のような結果(msg.array1 の配列の内容が1行ずつ表示)が出力されます:
2024091106


このようにテンプレート内で {{#配列名}} と {{/配列名}} で括り、その中で {{.}} と指定すると配列内の各要素が表示されました。


【テンプレート内に JSON 配列を指定する場合】
最後にテンプレート内で「 JSON オブジェクトの配列」を扱う場合を考えます。 今回の場合だと msg.array2 が JSON 配列になっているので、これをどのように列挙するか? というケースを考えます。

その答がこれです。template の内容を以下のように変更します:
{{#array2}}
{{name}}
{{/array2}}

2024091107



この template でデプロイして inject ノードをクリックして処理が実行されると、デバッグ画面に下図のような結果(msg.array2 の JSON 配列の name 要素が1行ずつ表示)が出力されます:

2024091108

このようにテンプレート内で {{#配列名}} と {{/配列名}} で括り、その中で {{要素名}} と指定すると JSON 配列内の特定の要素を表示できました。


【まとめ】
Node-RED のテンプレートとしての template ノードはあまり使う機会がなかったのですが、うまく使うとプロンプトエンジニアリングのプロンプト作り(サンプル作り)のテンプレートに使えそうだな、という印象を受けています。この方法でサンプルを列挙するようなプロンプトを生成して、その結果を拙作の watsonxai ノードhttps://flows.nodered.org/node/node-red-contrib-dotnsf-watsonxai)に渡して問い合わせする、といった使い方ができるのではないかと考えています:
2024091109


mastache 記法はここで紹介した内容以外にもいろいろな記法があります。詳しくは以下のページを参照ください。とてもわかりやすく、自分の理解にも使わせていただいたページです。


【参照】
mustache記法について簡単にまとめてみた

前回の続きです:
CentOS に Go をインストールする


Go の実行環境が導入できたので、テンプレートエンジンを使ってみます。まずは HTML テンプレートを作成しておきます。base.html というファイル名で以下の内容を作成/保存します:
{{define "base"}}<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>{{.Title}}</title>
</head>
<body>
<div class="content">
<h1>{{.Title}}</h1>
<hr/>
<div>
{{.Body|safehtml}}
</div>
</div>
</body>
</html>{{end}}

↑ほぼ HTML ですが、赤字で書いた部分が変数になります。変数名としては Title と Body の2つがあり、その中身はこの後の Go アプリの中で定義して渡します。また全体を "base" という名前で定義しています(青字部分)。


次にこのテンプレートを使って画面を表示する Go のウェブアプリを作成します(http2.go というファイル名で、base.html と同じディレクトリに作成します):
package main

import(
  "net/http"
  "html/template"
)

func viewHandler( w http.ResponseWriter, req *http.Request ){
  funcMap := template.FuncMap{
    "safehtml": func(text string) template.HTML { return template.HTML(text) },
  }
  templates := template.Must(template.New("").Funcs(funcMap).ParseFiles("base.html"))
  dat := struct {
    Title string
    Body string
  }{
    Title: req.FormValue( "title" ),
    Body: req.FormValue( "body" ),
  }
  err := templates.ExecuteTemplate(w, "base", dat)
  if err != nil {
    http.Error( w, err.Error(), http.StatusInternalServerError )
  }
}

func main(){
  http.HandleFunc( "/", viewHandler )
  http.ListenAndServe( ":8080", nil )
}

基本形は前回の Go ウェブアプリとほぼ同じですが、まず html/template を使う宣言をして、テンプレートに上記で作成した(同じディレクトリ上にある) base.html を使う宣言をしています。

またテンプレート内の変数 Title, Body を、それぞれ title, body という URL パラメータを受け取って定義するようにしています。

この状態で http2.go を実行します:
# go run http2.go


そしてウェブブラウザでこのサーバーの 8080 番ポートのルートパスに、パラメータ title と body を指定してアクセスすると、指定した内容がウェブページの一部になって表示されるはずです:
2016042602


これで Go でもテンプレートエンジンっぽいこともできました。

このページのトップヘ