はじめに
こんにちは、Go界の千葉実です。GoのテンプレートエンジンはJinja2ライクに継承ができますが、若干ハマりポイントがあったので忘れないうちにメモっておきます。
メモ
- html/templateはtext/templateの拡張で、HTMLエスケープを自動で行なってくれるところが違う
- 逆にHTMLエスケープをしたくない時はtemplate.HTML型で渡してやる
- 同じテンプレートを継承する複数のテンプレートを生成するときには、都度継承元テンプレートのParse()を行わないとだめ
- New()をすると新しいテンプレートの定義になってしまうので、継承の途中で挟んではダメ
- フィールド名にきちんと値を反映させるためにはExecuteTemplateを使ったほうがわかりやすい
サンプルコードと結果
template.Parse()を使う場合
- sample.go
package main import ( "fmt" "html/template" "os" ) const baseTemplateHTML = `{{define "base"}}<html> <head> {{template "head" .}} </head> <body> {{template "body" .}} </body> </html>{{end}}` const childTemplateHTML = ` {{define "head"}}<title>hoge</title>{{end}} {{define "body"}}<p>{{.}}</p>{{end}} ` const childTemplateHTML2 = ` {{define "head"}}{{.Title}}{{end}} {{define "body"}}{{.Body}}{{end}} ` var baseTemplate = template.Must(template.New("base").Parse(baseTemplateHTML)) var childTemplate = template.Must(baseTemplate.Parse(childTemplateHTML)) var baseTemplate2 = template.Must(template.New("base2").Parse(baseTemplateHTML)) var childTemplate2 = template.Must(baseTemplate2.Parse(childTemplateHTML2)) func main() { // テキストだけなので普通に差し込まれる if err := childTemplate.Execute(os.Stdout, `this is a test template`); err != nil { fmt.Printf("%v\n", err) } fmt.Printf("\n\n") // <p>はエスケープされる if err := childTemplate.ExecuteTemplate(os.Stdout, "base", `<p>escaped</p>`); err != nil { fmt.Printf("%v\n", err) } fmt.Printf("\n\n") // Title, Bodyはtemplate.HTML型なのでエスケープされない data := struct { Title template.HTML Body template.HTML }{ Title: template.HTML("<title>no escape test</title>"), Body: template.HTML("<p>disabling auto-escape</p>"), } if err := childTemplate2.ExecuteTemplate(os.Stdout, "base", data); err != nil { fmt.Printf("%v\n", err) } }
- 結果
<html> <head> <title>hoge</title> </head> <body> <p>this is a test template</p> </body> </html> <html> <head> <title>hoge</title> </head> <body> <p><p>escaped</p></p> </body> </html> <html> <head> <title>no escape test</title> </head> <body> <p>disabling auto-escape</p> </body> </html>
template.ParseFiles()を使う場合
テンプレート用のファイルを複数用意しておく。
- base.html
{{define "base"}}<html> <head> {{template "head" .}} </head> <body> {{template "body" .}} </body> </html>{{end}}
- child.html
{{define "head"}}{{.Title}}{{end}} {{define "body"}}{{.Body}}{{end}}
- main.go
package main import ( "fmt" "html/template" "os" ) func main() { // 継承元から引数に渡していく t, err := template.ParseFiles("base.html", "child.html") if err != nil { fmt.Printf("%v\n", err) } data := struct{ Title template.HTML Body template.HTML }{ Title: template.HTML("<title>no escape test</title>"), Body: template.HTML("<p>disabling auto-escape</p>"), } t.ExecuteTemplate(os.Stdout, "base", data) }
- 結果
<html> <head> <title>no escape test</title> </head> <body> <p>disabling auto-escape</p> </body> </html>