Go言語(golang) テンプレートの使い方
text/template https://golang.org/pkg/text/template/
html/template https://golang.org/pkg/html/template/
text/template と html/template の違いは以下の通り
To generate HTML output, see package html/template, which has the same interface as this package but automatically secures HTML output against certain attacks. template package - text/template - Go Packages
インターフェースは同じだけど、HTMLの出力が自動的にセキュアになる
目次
- 基本的な使い方
- 値の直前/直後の空白や改行を取り除く
- 変数
- パイプ
- with
- if文
- range
- ビルトイン関数
- 独自の関数の追加
- define, block, template
- テンプレートファイルの読み込み
基本的な使い方
テキストをパースしてExecute関数の第二引数で値を渡す。
渡した値はテキスト内の {{ }}
で .
として参照することができる。
package main import ( "log" "os" "text/template" ) func main() { text := "{{ . }}\n" tpl, err := template.New("").Parse(text) if err != nil { log.Fatal(err) } value := "hello world" if err := tpl.Execute(os.Stdout, value); err != nil { log.Fatal(err) } // 出力 // hello world }
マップを渡す
テキスト中にある .name
と .age
をそれぞれ map の値で置き換える
package main import ( "log" "os" "text/template" ) func main() { text := `Name: {{ .name }} Age: {{ .age }} ` tpl, err := template.New("").Parse(text) if err != nil { log.Fatal(err) } m := map[string]interface{}{ "name": "Tanaka", "age": 31, } if err := tpl.Execute(os.Stdout, m); err != nil { log.Fatal(err) } // 出力 // Name: Tanaka // Age: 31 }
構造体を渡す
マップと違って構造体はフィールド名の先頭の文字を大文字にする必要がある
小文字の場合は以下のようなエラーになる
executing "" at <.name>: name is an unexported field of struct type struct { name string; age int }
package main import ( "log" "os" "text/template" ) func main() { text := `Name: {{ .Name }} Age: {{ .Age }} ` tpl, err := template.New("").Parse(text) if err != nil { log.Fatal(err) } // field名は大文字で始める v := struct { Name string Age int }{ Name: "Tanaka", Age: 32, } if err := tpl.Execute(os.Stdout, v); err != nil { log.Fatal(err) } // 出力 // Name: Tanaka // Age: 31 }
値の直前/直後の空白や改行を取り除く
{{- .B -}}
のように -
で直前、直後の空白や改行を全て消すことができる。
直前だけであれば {{- .B }}
直後だけなら {{ .B -}}
とする。
if文なんかで、インデントをつけて見やすくしたときに {{- .B }}
とすることで余計な空白を除去できる。
package main import ( "log" "os" "text/template" ) func main() { text := "A {{- .B -}} C {{- .D -}} E\n" tpl, err := template.New("").Parse(text) if err != nil { log.Fatal(err) } m := map[string]interface{}{ "B": "B", "D": "D", } if err := tpl.Execute(os.Stdout, m); err != nil { log.Fatal(err) } // ABCDE // // - を使用しなければ空白はそのまま // A B C D E // }
変数
変数名は $
で始まりアルファベット大文字/小文字、数値、アンダーバーが使用可能
$
から始まっていればいいので $1
や $_
でもよい。
text := ` 変数の定義 {{ $v := 100 }} 変数の使い方 {{ $v }} `
パイプ
|
を使って値を受け渡す
` {{ "put" | printf "%s%s" "out" }} ` // 上は以下と同義 ` {{ printf "%s%s" "out" "put" }} `
with
with
は、値が空(型の初期値)ではない場合に、その値を .
として
{{ with }} ... {{ end }}
のブロック内で使うことができる。
値が空の場合は if 文と同じでそのブロックは実行されない。
` {{ with .value }} {{ . }} {{ end }} {{ with "hello" }} {{ . }} {{ end }} `
変数に代入して使うこともできる
` {{ with $v := .value }} {{ $v }} {{ end }} {{ with $v := "hello" }} {{ $v }} {{ end }} `
if 文のように else を使うこともできて、以下の.value
が空だった場合に実行される
` {{ with .value}} {{ . }} {{ else }} value is empty! {{ end }} `
if文
論理演算子と比較演算子は関数として定義されている
論理演算子
and && not ! or ||
比較演算子
eq == ne != lt < le <= gt > ge >=
with
と同じで空の値は偽とみなされるので実行されない。
with
と違ってブロック内で .
に値はセットされない
` {{ $x := 0 }} {{ if $x }} {{ $x }} {{ end }} `
演算子は関数なので、 eq .x 100
のように引数として渡す。
以下の例は if x == 100
と同義
` {{ if eq .x 100 }} {{ .x }} {{ end }} `
and
も eq
と同じで関数なので引数として渡す感じ。
以下は if x == 10 && y == 20
と同義
` {{ if and (eq .x 10) (eq .y 20) }} x: {{ .x }}, y {{ .y }} {{ end }} `
勿論 else if
と else
も使える
` {{ if eq .x 10 }} {{ .x }} is 10 {{ else if eq .x 20 }} {{ .x }} is 20 {{ else }} {{ .x }} {{ end }} `
range
スライスの値は .
に入ってる
` {{ range .slice }} {{- . }} {{ end }} `
インデックス番号や値を変数で使う
` {{ range $i, $v := .slice }} index: {{ $i }}, value: {{ $v }} {{ end }} `
マップ。通常の for range と同じでマップのキーと値が変数に入っている
` {{ range $key, $value := .map }} key: {{ $key }}, value: {{ $value }} {{ end }} `
スライスやマップが空だったときに実行する処理を else
を使ってかける
` {{ range .slice }} {{- . }} {{ else }} .slice is empty! {{ end }} `
ビルトイン関数
ビルトイン関数は以下のように定義されている。
https://golang.org/src/text/template/funcs.go#L32
var builtins = FuncMap{ "and": and, "call": call, "html": HTMLEscaper, "index": index, "slice": slice, "js": JSEscaper, "len": length, "not": not, "or": or, "print": fmt.Sprint, "printf": fmt.Sprintf, "println": fmt.Sprintln, "urlquery": URLQueryEscaper, // Comparisons "eq": eq, // == "ge": ge, // >= "gt": gt, // > "le": le, // <= "lt": lt, // < "ne": ne, // != }
説明については https://golang.org/pkg/text/template/ の Functions に載ってます。
index インデクシング
// A[4] 四番目の値を取り出す ` {{ index .A 4 }} `
slice スライシング
// A[1:] スライスの 1つから最後まで ` {{ slice .A 1 }} ` // A[2:3] 2番目から3番目まで ` {{ slice .A 2 3 }} ` // A[1:3:3] 最後の :N はキャパシティの指定 ` {{ slice . 1 3 3 }} `
独自の関数の追加
以下の例では createElement という関数を追加してテンプレート内で使ってます。
HTMLを返す関数なのでエスケープしたい場合は text/template
ではなく、
html/template
を使うと自動でエスケープしてくれる。
package main import ( "fmt" "log" "os" "text/template" ) func main() { text := ` {{- createElement "div" "hello world" }} ` funcmap := template.FuncMap{ "createElement": func(tagname, text string) string { return fmt.Sprintf("<%s>%s</%s>", tagname, text, tagname) }, } tpl := template.New("") tpl = tpl.Funcs(funcmap) var err error tpl, err = tpl.Parse(text) if err != nil { log.Fatal(err) } if err := tpl.Execute(os.Stdout, nil); err != nil { log.Fatal(err) } // // 出力 // // <div>hello world</div> // // html/template を使うと以下のようにエスケープされる // // <div>hello world</div> // // // html/template を使ってエスケープしないようにするには、たぶんこうする // // func(tagname, text string) template.HTML { // return template.HTML(fmt.Sprintf("<%s>%s</%s>", tagname, text, tagname)) // } // }
当然、構造体やマップに関数を定義して渡すことでもできる
package main import ( "fmt" "log" "os" "text/template" ) type Profile struct { Name string Age int } func (p Profile) ToString() string { return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age) } func main() { text := ` {{- .ToString }} ` tpl, err := template.New("").Parse(text) if err != nil { log.Fatal(err) } p := &Profile{"Tanaka", 31} if err := tpl.Execute(os.Stdout, p); err != nil { log.Fatal(err) } }
define, block, template
{{ define "name" }}
でテンプレート名を定義して、{{ template "name" }}
で呼び出す。
呼び出した側で何らかの処理を行いたい場合は {{ template "name" . }}
の .
に
呼び出したテンプレートが入っているので、パイプ処理できる。
template.ParseFiles
関数でファイルを読み込む場合は先頭のファイルがメタファイルとみなされる。
順不同で読み込んだり template.ParseGlob
関数を使う場合は集約するファイルにもテンプレート名を書いておく必要がある。
その場合は、tpl.ExecuteTemplate(os.Stdout, "name", nil)
のように第二引数で指定する。
{{ define "name" }}
とその呼び出しの {{ template "name" }}
は一つのファイル内に書いても同じ。
ただし、 {{ define "name" }} ... {{ end }}
の中で define
を使うとエラーになる。
template: index.html:: unexpected <define> in command
{{ block "name" . }}
は、 {{ define "name" }}
のその場実行版でネストしてもエラーにはならない
package main import ( "log" "os" "html/template" ) func main() { text := ` {{- define "contents" }} <!-- ネスト --> {{ block "header" . }} <div>header</div> {{ end }} <div>contents</div> {{ end }} {{- define "footer" }} <div>footer</div> {{ end -}} {{- block "index" . -}} <!DOCTYPE html> <html lang="ja"> <head></head> <body> {{ template "contents" . }} {{ template "footer" . }} </body> </html> {{ end -}} ` tpl, err := template.New("").Parse(text) if err != nil { log.Fatal(err) } if err := tpl.Execute(os.Stdout, nil); err != nil { log.Fatal(err) } }
出力
<!DOCTYPE html> <html lang="ja"> <head></head> <body> <!-- ネスト --> <div>header</div> <div>contents</div> <div>footer</div> </body> </html>
テンプレートファイルの読み込み
. ├── contents.html ├── footer.html ├── header.html ├── index.html └── main.go
メタファイルになる index.html から header, contents, footer を呼び出す。
- index.html
{{ block "index" . }} <!DOCTYPE html> <html lang="ja"> <head></head> <body> {{ template "header" }} {{ template "contents" }} {{ template "footer" }} </body> </html> {{ end }}
- header.html
{{ define "header" }} <div>header</div> {{ end }}
- contents.html
{{ define "contents" }} <div>contents</div> {{ end }}
- footer.html
{{ define "footer" }} <div>footer</div> {{ end }}
- main.go
package main import ( "log" "os" "html/template" ) func main() { tpl := template.Must(template.ParseGlob("*.html")) if err := tpl.ExecuteTemplate(os.Stdout, "index", nil); err != nil { log.Fatal(err) } // ParseFiles で、引数の先頭を index.html にしておけば {{ block "index" . }} を書く必要がなくなる // tpl, err := template.ParseFiles("index.html", "header.html", "contents.html", "footer.html") // その場合は、Execute を使う // tpl.Execute(os.Stdout, nil) }
出力
<!DOCTYPE html> <html lang="ja"> <head></head> <body> <div>header</div> <div>contents</div> <div>footer</div> </body> </html>