关于 Go 模板的笔记

字段操作

Go 语言的模板通过{{}}来插入变量,{{.}}表示当前对象,类似于 C++ 中的 this 或者 Python 中的 self。{{ .FieldName }}用来获取当前对象中的字段FieldName,需要注意的是字段名必须是exported的(即变量的首字母必须是大写)。

如果字段名不是exported的,那么就会有如下错误:

1
template: header.html:5:14: executing "header.html" at <.title>: title is an unexported field of struct type struct { Env map[string]string; title string }
  • 可以用下面这种形式输出字符串变量string
1
{{ `string` }}

输出嵌套内容

with操作可以更改当前{{ . }}所指向的对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func main() {
    t := template.New("test_with")
    t.Parse(`{{ with .Env }}{{ range $key, $val := . }}
    {{ $key }}: {{ $val }}
    {{ end }}{{ end }}`)
    t.Execute(os.Stdout, struct {
        Env map[string]string
    }{
        Env: map[string]string{
            "Home":  "jc",
            "Lover": "xff",
        },
    })
}

pipelines

  • 在 Go 语言模板中,任何{{}}里面的内容都是pipelines数据
  • pipelines数据之间可以通过|联系起来,例如: {{ "<output>" | html }}

自定义函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package main

import (
    "fmt"
    "html/template"
    "os"
    "strings"
)

type Friend struct {
    Fname string
}

type Person struct {
    UserName string
    Emails   []string
    Friends  []*Friend
}

func EmailDealWith(args ...interface{}) string {
    ok := false
    var s string
    if len(args) == 1 {
        s, ok = args[0].(string)
    }
    if !ok {
        s = fmt.Sprint(args...)
    }
    // find the @ symbol
    substrs := strings.Split(s, "@")
    if len(substrs) != 2 {
        return s
    }
    // replace the @ by " at "
    return (substrs[0] + " at " + substrs[1])
}

func Upper(args ...interface{}) string {
    ok := false
    var s string
    if len(args) == 1 {
        s, ok = args[0].(string)
    }
    if !ok {
        s = fmt.Sprint(args...)
    }

    return strings.ToUpper(s)
}

func main() {
    fmt.Println(EmailDealWith("a", "b", "@", "c"))

    f1 := Friend{Fname: "minux.ma"}
    f2 := Friend{Fname: "xushiwei"}
    t := template.New("fieldname example")
    t = t.Funcs(template.FuncMap{
        "emailDeal": EmailDealWith,
        "upper":     Upper,
    })
    t, _ = t.Parse(`hello {{.UserName | upper}}!
                {{range .Emails}}
                    an emails {{.|emailDeal}}
                {{end}}
                {{with .Friends}}
                {{range .}}
                    my friend name is {{.Fname}}
                {{end}}
                {{end}}
                `)
    p := Person{UserName: "Astaxie",
        Emails:  []string{"astaxie@beego.me", "astaxie@gmail.com"},
        Friends: []*Friend{&f1, &f2}}
    t.Execute(os.Stdout, p)
}
  • Go 模板包中还提供了许多自定义函数,相关文档

Must

Must是一个 helper 函数,用来检查模板编译是否正确,如果不正确就会panic。

1
2
3
4
5
func main() {
	tErr := template.New("terr")
	template.Must(tErr.Parse(`some error {{text}`))
}
// panic: template: terr:1: function "text" not defined

模板嵌套

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// header.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ .Title }}</title>
</head>
<body>
    {{ template "body" . }}
    {{ with .Env }}
        {{ range $key, $val := . }}
        <li>{{ $key }} -- {{ $val }}</li>
        {{ end }}
    {{ end }}
</body>
</html>

// body.html
{{ define "body" }}
<ul>
    {{ range $key, $val := .Env }}
    <li>{{ $key }} -- {{ $val }}</li>
    {{ end }}
</ul>
{{ end }}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// main.go
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	t, err := template.ParseFiles("header.html", "body.html")
	if err != nil {
		log.Fatalln(err)
	}

	env := map[string]string{
		"Home":  "/Users/michaletsui",
		"Shell": "/bin/bash",
	}
	err = t.Execute(os.Stdout, struct {
		Env   map[string]string
		Title string
	}{
		Env:   env,
		Title: "Go 模板",
	})

	if err != nil {
		log.Fatalln(err)
	}
}
  • {{ define "blockname" }}...{{ end }}可以定义一个 block
  • {{ template "blockname" }} 将会调用一个 block
  • t.Execute传递的对象并不会直接传递给 block,需要{{ block "body" . }}这样显示传递
  • 同一个集合类的模板是互相知晓的,如果同一模板被多个集合使用,则它需要在多个集合中分别解析
  • template.ParseFiles将第一个文件作为主模板
  • t.ExecuteTemplate可以指定要执行的主模板
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func main() {
    t, err := template.ParseFiles("body.html", "header.html")
    if err != nil {
        log.Fatalln(err)
    }

    env := map[string]string{
        "Home":  "/Users/michaletsui",
        "Shell": "/bin/bash",
    }

    data := struct {
        Env   map[string]string
        Title string
    }{
        Env:   env,
        Title: "Go 模板",
    }

    // 因为 body.html 作为主模板除了定义的block外没有其它内容,所以`t.Execute`没有不会输出任何内容
    // 故这里通过 `t.ExecuteTemplate` 来指定主模板
    // 也可以通过下面的方式
    // t = t.Lookup("header.html")
    // err = t.Execute(os.Stdout, data)
    err = t.ExecuteTemplate(os.Stdout, "header.html", data)

    if err != nil {
        log.Fatalln(err)
    }
}

参考链接