はじめに
Goのテンプレートのpackageをちゃんと使ってみたいという気持ちになったので調べました。
参考
text/template
パッケージのドキュメント
テンプレートの文法についてはこっちに書いてあります。
html/template
パッケージのドキュメント
packageのインタフェースはtext/template
と同じですが、セキュリティ上の問題を引き起こさないためには出力がHTMLの場合にはこちらを使うべきと書いてあります。
Effective Go - The Go Programming Language # A web server
formに文字列を入力すると、GoogleのAPIを使ってQRコードの画像を生成して表示するサーバのサンプルコードがあります。html/template
も登場します。
[Writing Web Applications - The Go Programming Language]
(https://golang.org/doc/articles/wiki/)
簡易的なWikiを作ってWebアプリの作り方を学べるチュートリアルです。重複を排除するようにリファクタしつつ進んでいくので教材としていい感じです。これも同じく、html/template
も登場します。
テンプレートの文法
文字列の中に{{Action}}
の形式でデータや制御構造を埋め込める。
Action
データの評価または制御構造を表す。
- コメント
-
{{/* a comment */}}
の形式で出力されないコメントを書ける
-
- pipelineの値そのもの
{{pipeline}}
-
if
{{if pipeline}} T1 {{end}}
- pipelineの値がempty(
false
,0
,nil
のポインタかinterface,len(x) == 0
の配列、スライス、map、文字列の場合)でなければT1、emptyなら何も出力されない
- pipelineの値がempty(
-
if-else
{{if pipeline}} T1 {{else}} T0 {{end}}
- pipelineの値がemptyでなければT1、emptyならT0
-
if-else-if
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
-
else
に別のif
を入れこむことができる - これと同じ結果になる
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
-
-
with
{{with pipeline}} T1 {{end}}
- ifと同じだが、pipelineの値がemptyでない場合、dot(テンプレートに渡したデータ中の現在の参照位置を示す)がpipelineの値になる
-
with-else
{{with pipeline}} T1 {{else}} T0 {{end}}
- pipelineの値がemptyでなければ
with
と同じ、emptyならdotは変わらない
- pipelineの値がemptyでなければ
-
range
{{range pipeline}} T1 {{end}}
- pipelineの値が配列、スライス、map、channelのいずれかである必要がある
- pipelineの値がempty(
len(x) == 0
)なら何も出力されない - emptyでない場合、要素の数だけループする
- ループ中、dotは現在の要素を示す
-
range-else
{{range pipeline}} T1 {{else}} T0 {{end}}
-
range
と同じだが、pipelineの値がemptyの場合T0が実行される
-
Argument
単なる値を表す。ポインタでも自動的に値を見にいってくれる
- 表記:値
- Goの定数:untyped constantになる
-
nil
:untyped nil -
.
:そのときのdotの値 -
$alphanum
の形式の変数名$
も有効 - 構造体のフィールド名:フィールドの値
-
.Field
の形式 -
.Field1.Field2
ネストしていても参照できる -
$x.Field1.Field2
で変数のフィールドにもアクセスできる
-
- mapのキー名:キーの値
-
.Key
の形式 -
$x.key
変数に対しても同じことができる
-
- 引数を持たないメソッド名:メソッドの呼び出し結果
- この形式で使えるメソッドは返り値を2つまでしか持てない
- 2つの場合は、2つ目はerrorでないといけない
- nilではないerrorが返った場合は、テンプレートの実行は止まりExecute()からそのerrorが返却される
- 引数を持たない関数名:関数の呼び出し結果
- 返り値についての振る舞いはメソッドの場合と同じ
Pipeline
1つ以上のcommand(単なる値あるいは関数かメソッドの呼び出し)を|
で繋いだもの
関数
定義済みの関数
and
引数のうち最初に見つかったemptyの引数を返す。emptyのものが1つもなかった場合は最後の引数を返す。
or
引数のうち最初に見つかったemptyではない引数を返す。すべてemptyだった場合は最後の引数を返す。
not
引数がemptyならtrue
、そうでなければfalse
を返す。
call
最初の引数(関数じゃないといけない)に残りの引数を渡して実行した結果を返す。
package main
import (
"math"
"os"
"text/template"
)
type Point struct {
X float64
Y float64
}
func Distance(a, b Point) float64 {
return math.Sqrt((a.X-b.X)*(a.X-b.X) + (a.Y-b.Y)*(a.Y-b.Y))
}
const tmplStr = `
p1: {{printf "%#v" .p1}}
p2: {{printf "%#v" .p2}}
{{/* {{.distance .p1 .p2}} はダメ */}}
distance: {{call .distance .p1 .p2}}
`
var tmpl = template.Must(template.New("call").Parse(tmplStr))
func main() {
data := map[string]interface{}{
"p1": Point{X: 3.0, Y: 0.0},
"p2": Point{X: 0.0, Y: 4.0},
"distance": Distance,
}
if err := tmpl.Execute(os.Stdout, data); err != nil {
panic(err)
}
}
p1: main.Point{X:3, Y:0}
p2: main.Point{X:0, Y:4}
distance: 5
print
, printf
, println
それぞれfmt.Sprint
, fmt.Sprintf
, fmt.Sprintln
の働きをする。
html
, js
, urlquery
引数をそれぞれHTML,JavaScript,クエリストリングとしてエスケープする。
package main
import (
"os"
"text/template"
)
const tmplStr = `
and: {{and 0 0}} {{and 1 0}} {{and 0 1}} {{and 1 1}}
or: {{or 0 0}} {{or 1 0}} {{or 0 1}} {{or 1 1}}
not: {{not "ok"}} {{0 | not}}
html: {{html "<a href=\"https://golang.org/\">Go</a>"}}
js: {{js "<script>alert('XSS');</script>"}}
urlquery: {{urlquery "http://example.com/path?key=val"}}
slice: {{slice "golang" 1 4}}
eq: {{eq "🍣" "🍣"}} {{eq "🍣" "🍺"}} {{eq "🍺" "🍣"}}
ne: {{ne "🍣" "🍣"}} {{ne "🍣" "🍺"}} {{ne "🍺" "🍣"}}
lt: {{lt "🍣" "🍣"}} {{lt "🍣" "🍺"}} {{lt "🍺" "🍣"}}
le: {{le "🍣" "🍣"}} {{le "🍣" "🍺"}} {{le "🍺" "🍣"}}
gt: {{gt "🍣" "🍣"}} {{gt "🍣" "🍺"}} {{gt "🍺" "🍣"}}
ge: {{ge "🍣" "🍣"}} {{ge "🍣" "🍺"}} {{ge "🍺" "🍣"}}
`
var tmpl = template.Must(template.New("functions").Parse(tmplStr))
func main() {
if err := tmpl.Execute(os.Stdout, nil); err != nil {
panic(err)
}
}
and: 0 0 0 1
or: 0 1 1 1
not: false true
html: <a href="https://golang.org/">Go</a>
js: \x3Cscript\x3Ealert(\'XSS\');\x3C/script\x3E
urlquery: http%3A%2F%2Fexample.com%2Fpath%3Fkey%3Dval
slice: ola
eq: true false false
ne: false true true
lt: false true false
le: true true false
gt: false false true
ge: true false true
自分で定義した関数を使う
(*Template) Funcs()
でtemplate.FuncMap
(map[string]interface{}
)を渡すと自分で定義した関数を使える。Funcs()
はParse()
より前に呼ぶ必要がある。
package main
import (
"math"
"os"
"text/template"
)
const tmplStr = `
multiply: {{multiply 2 3}}
average: {{average 2 3 5}}
round: {{round 3.14}}
average|round: {{average 2 3 5 | round}}
reverse: {{reverse "reverse"}}
sushi|beer: {{"" | sushi | beer | sushi | beer}}
`
var (
funcMap = map[string]interface{}{
"multiply": func(a, b int) int {
return a * b
},
"average": func(nums ...int) float64 {
sum := 0
for _, n := range nums {
sum += n
}
return float64(sum) / float64(len(nums))
},
"round": func(num float64) int {
return int(math.Round(num))
},
"reverse": func(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
},
"sushi": func(s string) string {
return s + "🍣"
},
"beer": func(s string) string {
return s + "🍺"
},
}
tmpl = template.Must(template.New("funcmap").Funcs(funcMap).Parse(tmplStr))
)
func main() {
if err := tmpl.Execute(os.Stdout, nil); err != nil {
panic(err)
}
}
multiply: 6
average: 3.3333333333333335
round: 3
average|round: 3
reverse: esrever
sushi|beer: 🍣🍺🍣🍺