text/templatehtml/template 包可以将变量的值代入文本或 HTML 模板,用于输出更精细的格式,将输出格式和代码彻底分离。

模板可以是字符串或文件,它包含若干个用 {{}} 包起来的部分(称为 action)。大部分字符串会按照字面值打印,action 则会触发一些行为。

模板语言作为一套记号,可以用来打印值,选择结构体字段,调用函数、方法,表达控制流(if-else, range),初始化其它模板。

每个 action 包含一个模板语言的语句。

一个 action 中,. 表示当前值,. 的初始值是当前模板的参数
{{range .Items}}{{end}} 构成一个循环。
| 表示将上一个操作的结果作为下一个操作的参数,类似 shell 管道。

使用模板的步骤:

  1. 解析模板
    • 只需执行一次。
  2. 对输入的数据执行模板
const templ = `{{.TotalCount}} issues:
{{range .Items}}-------------------------
Number: {{.Number}}
User:   {{.User.Login}}
Title:  {{.Title | printf "%.64s"}}
Age:    {{.CreatedAt | daysAgo}} days
{{end}}`

// printf 即 fmt.Sprintf;daysAgo 是自定义函数
func daysAgo(t time.Time) int {
    return int(time.Since(t).Hours() / 24)
}
// chaining methods
report, err := template.New("report").
    Funcs(template.FuncMap{"daysAgo": daysAgo}).
    Parse(templ)
if err != nil {
    log.Fatal(err)
}
// // template.Must helper makes error handling more convenient
// var report = template.Must(template.New("issuelist").
//     Funcs(template.FuncMap{"daysAgo": daysAgo}).
//     Parse(templ))
if err := report.Execute(os.Stdout, inputs); err != nil {
    log.Fatal(err)
}
// 13 issues:
// ----------------------------------------
// Number: 5680
// User: eaigner
// Title: encoding/json: set key converter on en/decoder
// Age: 750 days
// ----------------------------------------
// Number: 6050
// User: gopherbot
// Title: encoding/json: provide tokenizer
// Age: 695 days
// ----------------------------------------
// ...

“index x 1 2 3” is, in Go syntax, x[1][2][3]. Each indexed item must be a map, slice, or array.

kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}'

html/templatetext/template 使用相同的接口,增加了对 HTML,JavaScript,CSS,URL 中的字符串的自动 escape,可以防止注入攻击。

<!-- html/template: 对 <,>,&, 等 HTML 保留字符 escape 后,浏览器引擎读到的是字面量,不具有特殊含义,页面上则正常显示对应字符。-->
x/net/html: void element <link> has child nodes
html/template: escape xmldesc as &lt;?xml
<a href='https://github.com/golang/go/issues/10535'>x/net/html: void element &lt;link&gt; has child nodes</a>
<a href='https://github.com/golang/go/issues/3133'>html/template: escape xmldesc as &amp;lt;?xml</a>
<!-- text/template:不对保留字符进行 escape,浏览器将 <> 作为标签来识别,页面上也就不能正常显示 -->
x/net/html: void element has child nodes
html/template: escape xmldesc as <?xml
<a href='https://github.com/golang/go/issues/10535'>x/net/html: void element <link> has child nodes</a>
<a href='https://github.com/golang/go/issues/3133'>html/template: escape xmldesc as &lt;?xml</a>

对于可信的 HTML 而言,可以用 template.HTML 来阻止自动 escape 的行为。对于 JavaScript,CSS 和 URL也存在类似的方法。

const templ = `<p>A: {{.A}}</p><p>B: {{.B}}</p>`
t := template.Must(template.New("escape").Parse(templ))
var data struct {
    A string        // untrusted plain text
    B template.HTML // trusted HTML
}
data.A = "<b>Hello!</b>"    // 渲染成 <b>Hello!</b>
data.B = "<b>Hello!</bb>"    // 渲染成加粗的 Hello!
if err := t.Execute(os.Stdout, data); err != nil {
    log.Fatal(err)
}

References