模板template

模板基础

html/template包实现了数据驱动的模板,用于生成可防止代码注入的安全的HTML内容。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用html/template这个包。

  1. 模板文件通常定义为.tmpl.tpl为后缀(也可以使用其他的后缀),必须使用UTF8编码。
  2. 模板文件中使用{{`和`}}包裹和标识需要传入的数据。
  3. 传给模板这样的数据就可以通过点号(.)来访问,如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。
  4. {{`和`}}包裹的内容外,其他内容均不做修改原样输出。

模板使用三部曲

  1. 定义模板

  2. 解析模板

    1
    func ParseFiles(filenames ...string) (*Template, error)
  3. 渲染模板

    1
    func (t *Template) Execute(wr io.Writer, data interface{}) error

一个小栗子

main.go

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
package main

import (
"fmt"
"html/template"
"net/http"
)

type User struct {
Name string
Gender string
Age int
}

func sayHello(w http.ResponseWriter, r *http.Request) {
// 1.解析指定文件生成模板对象
tmpl, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}

// 2.利用给定数据渲染模板,并将结果写入w

u1 := User{
Name: "梅西",
Gender: "男",
Age: 36,
}

mp := make(map[string]interface{})
mp["name"] = "c罗"
mp["gender"] = "男"
mp["age"] = 21

// 创建一个slice
arr := []string{
"足球", "篮球", "羽毛球",
}

// 传出数据并且渲染数据(用map封装起来)
err = tmpl.Execute(w, map[string]interface{}{
"u1": u1,
"mp": mp,
"arr": arr,
})

if err != nil {
fmt.Println("传递参数出错,err:", err)
return
}

}

func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("HTTP server failed, err:", err)
return
}
}

hello.tmpl文件

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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello</title>
</head>
<body>
<p>hello</p>
<p>{{ .u1.Name }}</p>
<p>{{ .u1.Gender }}</p>
<p>{{ .u1.Age }}</p>
<hr>

<p>hello</p>
<p> {{ .mp.name }} </p>
<p> {{ .mp.gender }} </p>
<p> {{ .mp.age }} </p>
<hr>

{{ range $idx,$hobby := .arr }}
<p>{{$idx}} - {{$hobby}}</p>
{{ end }}
</body>
</html>

image-20221128153347586

基础模板语法

  • 注释:

    1
    {{/* a comment */}}
  • pipeline:管道|

  • 变量:$obj := {{.}}

  • 移除空格:{{- .Name -}}

  • 条件判断:

    1
    2
    3
    4
    {{if ...}}
    {{else if ...}}
    {{else}}
    {{end}}
  • range:

    1
    2
    {{rang ...}}
    {{end}}
  • 比较函数:

    1
    2
    3
    4
    5
    6
    7
    8
    eq      如果arg1 == arg2则返回真
    ne 如果arg1 != arg2则返回真
    lt 如果arg1 < arg2则返回真
    le 如果arg1 <= arg2则返回真
    gt 如果arg1 > arg2则返回真
    ge 如果arg1 >= arg2则返回真
    // 例子
    {{eq arg1 arg2}}

模板嵌套

自定义函数

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
package main

import (
"fmt"
"html/template"
"io/ioutil"
"net/http"
)

type User struct {
Name string
Gender string
Age int
}

func f(w http.ResponseWriter, r *http.Request) {
fByte, err := ioutil.ReadFile("./f.tmpl")
if err != nil {
fmt.Println("read html failed, err:", err)
return
}

// 自定义一个模板函数
fun1 := func(arg string) (string, error) {
return arg + "是球王", nil
}
// 采用链式操作在Parse之前嗲用Funcs添加自定义函数
tmpl, err := template.New("f").
Funcs(template.FuncMap{"fun1": fun1}).
Parse(string(fByte))
if err != nil {
fmt.Println("create template failed, err:", err)
return
}

user := User{
Name: "梅西",
Gender: "男",
Age: 35,
}

// 使用user渲染模板,并将结果写入w
tmpl.Execute(w, user)
}

func main() {
http.HandleFunc("/", f)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("failed err:", err)
return
}
}
1
{{fun1 .Name}}

image-20221128153142088

嵌套模板

我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。

f.tmpl文件内容如下:

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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>tmpl test</title>
</head>
<body>

<h1>测试嵌套template语法</h1>
<hr>
{{template "ul.tmpl"}}
<hr>
{{template "ol.tmpl"}}
</body>
</html>

{{ define "ol.tmpl"}}
<ol>
<li>吃饭</li>
<li>睡觉</li>
<li>打豆豆</li>
</ol>
{{end}}

ul.tmpl文件内容如下:

1
2
3
4
5
<ul>
<li>注释</li>
<li>日志</li>
<li>测试</li>
</ul>

main.go文件:

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
package main

import (
"fmt"
"html/template"
"net/http"
)

type User struct {
Name string
Gender string
Age int
}

func fun1(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("./f.tmpl", "./ul.tmpl")

if err != nil {
fmt.Println("create failed, err :", err)
return
}
user := User{
Name: "梅西",
Gender: "男",
Age: 36,
}
tmpl.Execute(w, user)
}

func main() {
http.HandleFunc("/tmpl", fun1)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("Http server failed, err:", err);
return
}
}

image-20221128154740137

模板继承

block: block是定义模板{{define "name"}} T1 {{end}}和执行{{template "name" pipeline}}缩写,典型的用法是定义一组根模板,然后通过在其中重新定义块模板进行自定义。

  1. 定义根模板base.tmpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <title>Go Templates</title>
    </head>
    <body>
    <h1>模板继承</h1>
    <div class="container-fluid">
    {{block "content" . }}{{end}}
    </div>
    </body>
    </html>
  2. 定义一个home.tmpl继承base.tmpl

    1
    2
    3
    4
    5
    {{template "base.tmpl"}}

    {{define "content"}}
    <div>Hello world!</div>
    {{end}}
  3. 解析模板和渲染模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func home(w http.ResponseWriter, r *http.Request) {
    tmpl, err := template.ParseGlob("templates/*.tmpl")
    if err != nil {
    fmt.Println("create failed err:", err)
    return
    }
    err = tmpl.ExecuteTemplate(w, "home.tmpl", nil)
    if err != nil {
    fmt.Println("render template failed, err:", err)
    return
    }
    }