YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

html/templateでテンプレートを継承する

はじめに

こんにちは、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>&lt;p&gt;escaped&lt;/p&gt;</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>