Go text/template で文字列作成

# Go text/template で文字列作成

みなさん、こんにちは！
Go書いてますか？

今回は、`text/template` を使って ひな形 から 文字列 を生成する方法をご紹介します。

## text/template

公式が作成している 標準package なので すぐに使えます。

* template - The Go Programming Language
 * https://golang.org/pkg/text/template/

似たようなpackageで `html/template` というのがありますが、
これはコードインジェクションに対して安全なHTML出力を生成するものなので
そういった目的がないときは、 `text/template` を使用しましょう。

* template - The Go Programming Language
 * https://golang.org/pkg/html/template/

ちなみにどちらも使い方は、同じです。

## 使ってみる

文字列のひな形 と データ を入れ込むことによって、目的の文字列を作成します。

```go:ソースコード
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	// ひな形
	tmpl := "Hello {{.}}!\n"

	// template.New(<テンプレート名>).Parse(<文字列>)
	t, err := template.New("sample").Parse(tmpl)
	if err != nil {
		log.Fatal(err)
	}
	// Execute(io.Writer(出力先), <データ>)
	if err = t.Execute(os.Stdout, "World"); err != nil {
		log.Fatal(err)
	}
}
```

```text:出力結果
Hello World!
```

上記ソースのコメントのように、それぞれ指定してデータを入れるだけでできます。

## 構造体を使用するとき

先ほどは 1つの値のみ を データ として入れていましたが
雛形の中に 複数の違う値 を入れたいときがあると思います。

1つの方法として、構造体(struct) を使用する方法を紹介します。

```go:ソースコード
package main

import (
	"log"
	"os"
	"text/template"
)

// 構造体(すべて公開)
type Language struct {
	Name string
	URL  string
}

func main() {
	// 構造体に値を設定
	data := Language{
		Name: `Go`,
		URL:  `https://golang.org/`,
	}

	// ひな形
	tmpl := "今回紹介する言語は、{{.Name}}です！\n詳しくは、{{.URL}} を見てください。\n"

	// New(<テンプレート名>).Parse(<文字列>)
	t, err := template.New("sample").Parse(tmpl)
	if err != nil {
		log.Fatal(err)
	}
	// Execute(io.Writer(出力先), <データ>)
	if err = t.Execute(os.Stdout, data); err != nil {
		log.Fatal(err)
	}
}
```

```go:ひな形(tmpl)
今回紹介する言語は、{{.Name}}です！
詳しくは、{{.URL}} を見てください。
```
 * `{{.メンバ名}}`
  * 構造体のメンバを指定すると値が挿入されます

```text:出力結果
今回紹介する言語は、Goです！
詳しくは、https://golang.org/ を見てください。
```

## 雛形ファイルとの連携

雛形ファイル と連携する場合は、テキストファイル を読みこむ必要がありますが
`ParseFiles()` 使用すると簡単に読み込むことができます。

```go:ソースコード
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	// mapの使用例
	data := map[string]string{
		"Name":     "hiro",
		"Language": "Go",
	}

	// New(<ファイル名>).ParseFiles(<ひな形ファイル>)
	t, err := template.New("message.tmpl").ParseFiles("template/message.tmpl")
	if err != nil {
		log.Fatal(err)
	}

	// Execute(io.Writer(出力先), <データ>)
	if err = t.Execute(os.Stdout, data); err != nil {
		log.Fatal(err)
	}
}
```

入力データをmapで扱う場合も同じように指定すれば出力されます

```go:template\message.tmpl
My name is {{.Name}}.
Programming in {{.Language}} language.
```
 * `{{.キー名}}`
  * map[string]stringのキー名を指定すると値が挿入されます

```text:出力結果
My name is hiro.
Programming in Go language.
```

また1行で処理することもできます。

```go:ソースコード
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	// mapも使用できます
	data := map[string]string{
		`Name`:     `hiro`,
		`Language`: `Go`,
	}

	// Must(テンプレート) ･･･ テンプレートにエラーがあったらpanicする
	t := template.Must(template.ParseFiles(`template/message.tmpl`))

	// Execute(io.Writer(出力先), データ)
	if err := t.Execute(os.Stdout, data); err != nil {
		log.Fatal(err)
	}
}
```

## 雛形ファイル内での制御構文

雛形ファイル内で `if` を使ったり、`range` で スライス・配列 を回すことができます。

```go:ソースコード
package main

import (
	"log"
	"os"
	"text/template"
)

type Member struct {
	Name    string
	Comment string
}

func main() {
	// 構造体のデータ
	list := []Member{
		{
			Name:    "Ken",
			Comment: "hello! :)",
		},
		{
			Name:    "Nancy",
			Comment: "",
		},
	}

	// 1行で終わらせるとき
	// Must(テンプレート) ･･･ テンプレートにエラーがあったらpanicする
	t := template.Must(template.ParseFiles(`template/message.tmpl`))

	// Execute(io.Writer(出力先), データ)
	if err := t.Execute(os.Stdout, list); err != nil {
		log.Fatal(err)
	}
}
```

```go:template/message.tmpl
{{ range $i, $member := . -}}
名前    : {{ $member.Name }}
{{ if ne $member.Comment "" -}}
コメント: {{ $member.Comment }}
{{ else -}}
コメントはありません
{{ end }}
{{- end }}
```
 * `range`
  * goの`range`関数とほぼ同じ使い方(range 配列番号, 要素 := 配列)
  * 右辺の`.`は、下記のソースで入れた`list`がそのまま入ってきます
 * `if`
  * 条件式を書く
 * `ne` (__n__ot __e__qual : !=)
  * `ne A B`という風に指定する
  * 同じとする場合は、`eq A B` (__eq__ual : ==)
 * `end`
  * 制御構文の末尾を囲うように配置する
 * `{{-`
  * 左側にある改行・スペースを削除
 * `-}}`
  * 右側にある改行・スペースを削除

```text:出力結果
名前    : Ken
コメント: hello! :)
名前    : Nancy
コメントはありません

```

まだその他にもたくさん使えるものがあります。

## 自作関数を使用する

自作関数も追加できるので、いろいろ幅が広がりそうです。

```go:ソースコード
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	// 追加で使いたい関数を定義する
	funcMap := template.FuncMap{
		"getHogeString": getHogeString,                       // 追加する関数(※1)
		"sum":           func(x, y int) int { return x + y }, // 無名関数でもOK
	}

	// New(ファイル名).ParseFiles(雛形ファイル)
	t, err := template.New("message.tmpl").Funcs(funcMap).ParseFiles("template/message.tmpl")
	if err != nil {
		log.Fatal(err)
	}

	// Execute(io.Writer(出力先), データ)
	if err := t.Execute(os.Stdout, nil); err != nil {
		log.Fatal(err)
	}
}

// ※1: 追加する関数
func getHogeString() string {
	return "Hoge!"
}

```

```go:template/message.tmpl
Say {{ getHogeString }}

1 + 2 = {{ sum 1 2 }}
```

```text:出力結果
Say Hoge!

1 + 2 = 3
```

## まとめ

雛形を使った文字列作成をご紹介しました。

`Execute`の出力先が`io.Writer`なので、直接ファイルに書くこともできるので便利です。
是非使ってみてください。