Go 言語って標準ライブラリにテンプレートまであるのか。便利。
ということで使ってみましょう。
使い方は簡単。template.Must(template.ParseFiles("templateFilePath1", "templateFilePath2", ...) みたいにしてテンプレートをパースする。パースしたら ExecuteTemplate メソッドで出力先とテンプレート名とテンプレートに渡す値を指定して完成。
package main import ( "html/template" "net/http" ) type pageData struct { Title string Message string } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{Title: "Template sample title", Message: "Template sample message"}) }) http.ListenAndServe("localhost:8080", mux) }
テンプレートは以下のような感じになります。
まずは templates/index.html
{{ define "index" }}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Hello</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<p>{{ .Message }}</p>
</body>
</html>
{{ end }}
templates/test.html はこんな感じ。
{{ define "test" }}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body style="background-color: yellow">
<h1>{{ .Title }}</h1>
<p>{{ .Message }}</p>
</body>
</html>
{{ end }}
基本的には {{ }} でくくった中に何か書く。先頭は define でレイアウト名(ExecuteTemplate で指定する名前)を指定して最後に end でおしまい。
{{ .プロパティ名 }} とかで ExecuteTemplate で渡されたデータにアクセスできる感じっぽい。
動かしてみるとちゃんと動いた。

今回のプログラムは URL のパラメータでテンプレート名指定するからこんな感じでも動く。

{{ range .xxx }} でループも行ける。テンプレートをいじって
{{ define "index" }}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Hello</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<p>{{ .Message }}</p>
<hr/>
<ul>
{{ range .Records }}
<li>{{ .Name }}</li>
{{ end }}
</ul>
</body>
</html>
{{ end }}
main.go もそれにあわせて変更。
package main import ( "html/template" "net/http" ) type record struct { Name string } type pageData struct { Title string Message string Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: "Template sample message", Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
いい感じに動く。

さらに if やカスタムの関数とかも定義出来るみたい。今回は偶数番目のデータと奇数番目のデータで色を変えたかったので、mod という関数を追加したうえでパースして実行してみました。
package main import ( "html/template" "net/http" ) type record struct { Name string } type pageData struct { Title string Message string Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.New("").Funcs(template.FuncMap{ "mod": func(x int, y int) int { return x % y }, }).ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: "Template sample message", Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
テンプレートはこんな感じになりました。
{{ define "index" }}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Hello</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<p>{{ .Message }}</p>
<hr/>
<ul>
{{ range $index, $record := .Records }}
{{ $isEvenRow := eq (mod $index 2) 0 }}
<li style="color: {{ if $isEvenRow}} red {{ else }} black {{ end }}">{{ $index }}{{ $record.Name }}</li>
{{ end }}
</ul>
</body>
</html>
{{ end }}
if や range のインデックスの取得方法と関数呼び出しと盛りだくさん。実行するとこんな感じです。

あとはサニタイズとかもあるみたいですね。普通に Go 側とこういう風に書くと…
package main import ( "html/template" "net/http" ) type record struct { Name string } type pageData struct { Title string Message string Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.New("").Funcs(template.FuncMap{ "mod": func(x int, y int) int { return x % y }, }).ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: "<script type='text/javascript`>alert('Template sample message')</script>", // !? Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
素晴らしい。

あえてエスケープしたくないときは template.HTML を使う。まぁレアケースだけどマークダウンエディターみたいなものを作りたいときとかは必要。
こんな感じで
package main import ( "html/template" "net/http" ) type record struct { Name string } type pageData struct { Title string Message template.HTML Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.New("").Funcs(template.FuncMap{ "mod": func(x int, y int) int { return x % y }, }).ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: template.HTML("<script type='text/javascript'>alert('Template sample message')</script>"), Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
実行するとこうなる。ばっちり。
