掘金 后端 ( ) • 2024-04-21 13:53

Go - Template 最全的模板说明

一、前言

Go 语言提供了两个包进行模板渲染

  • html/template:

    html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。

  • text/template:

    将内容都已text文本格式返回 ,正常普通的模板渲染可以采用这个库

如:当你的模板中出现 < ,如果你采用的是html/template则会转义为&lt; ,如果你使用的是 text/template则正常输出<


二、基本语法

2.1 {{.}}

模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象

  • 当我们传入一个普通结构体对象时,我们可以根据.来访问结构体的对应字段。例如:
func main() {
    // 通用单层结构体
    param := &struct {
        Name string
    }{
        Name: "张三",
    }
    tmp, _ := template.New("aa").Parse("普通结构体 name: {{.Name}}")
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}
  • 当我们传入的是嵌套结构体,我们依然可以使用.访问嵌套的结构体字段,如:
func main() {
    // 嵌套结构体
    school := &struct {
        Student struct {
            Name string
        }
    }{
        Student: struct{ Name string }{
            Name: "王五",
        },
    }
    tmp, _ := template.New("aa").Parse("嵌套结构体 name: {{.Student.Name}}")
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, school)
    fmt.Println(buffer.String())
}
  • 当我们传入的是普通的map也可以通过.访问key的值,如:
func main() {
    // 普通map
    param := map[string]string{
        "Name": "李四",
    }
    tmp, _ := template.New("aa").Parse("普通的map name: {{.Name}}")
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}

2.2 模板注释

{{/* a comment */}}

示例如下:

func main() {
    tmp, _ := template.New("aa").Parse("模板注释 {{/* a comment */}}")
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer,nil)
    fmt.Println(buffer.String())
}

注意:注释执行时会忽略不会输出。可以多行。注释不能嵌套 并且 必须紧贴分界符始止


2.3 pipeline

pipeline是指产生数据的操作。比如{{.}}、{{.Name}}等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。

注意 : 并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。


2.4 变量

们还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下:

func main() {
    param := map[string]string{
        "name": "张三",
    }
    tmp, _ := template.New("aa").Parse("模板注释 {{ $b:=.name}} {{$b}}")
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}
​

2.5 移除空格

有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。

例如:{{- .Name -}}

func main() {
    param := map[string]string{
        "Name": "李四",
    }
    tmp, _ := template.New("aa").Parse("开始 \n {{- .Name -}} 结束")
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}

注意: -要紧挨{{和}},同时与模板值之间需要使用空格分隔。



三、条件判断/遍历

3.1 条件判断

Go模板语法中的条件判断有以下几种:

{{if pipeline}} T1 {{end}}
​
{{if pipeline}} T1 {{else}} T0 {{end}}
​
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

示例如下:

func main() {
    param := map[string]interface{}{
        "ok": true,
    }
    tmp, _ := template.New("aa").Parse(`{{if .ok}} if ok {{else}} else ok {{end}}`)
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}

3.2 遍历

Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。

{{range pipeline}} T1 {{end}}
​
{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为0,则会执行T0。 

示例一:

func main() {
    param := map[string]interface{}{
        "list": []string{"张三", "李四", "王五"},
    }
    tmp, _ := template.New("aa").Parse(`{{ range .list}} {{.}} {{end}}`)
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}

示例二:

func main() {
    param := map[string]interface{}{
        "list": []string{"张三", "李四", "王五"},
    }
    tmp, _ := template.New("aa").Parse(`{{ range $i,$item:=.list}} {{$i}} {{$item}} {{end}}`)
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}

3.3 with

{{with pipeline}} T1 {{end}}
如果pipeline为empty不产生输出,否则将并执行T1。
​
{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为empty,执行T0,否则并执行T1。 

示例:

func main() {
    param := map[string]interface{}{
        "list": []string{"张三", "李四", "王五"},
    }
    tmp, _ := template.New("aa").Parse(`{{ with .list}} not empty {{else}} empty {{end}}`)
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}

四、预定于函数

执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。

预定义的全局函数如下:

and
    函数返回它的第一个empty参数或者最后一个参数;
    就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or
    返回第一个非empty参数或者最后一个参数;
    亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not
    返回它的单个参数的布尔值的否定
len
    返回它的参数的整数类型长度
index
    执行结果为第一个参数以剩下的参数为索引/键指向的值;
    如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print
    即fmt.Sprint
printf
    即fmt.Sprintf
println
    即fmt.Sprintln
html
    返回其参数文本表示的HTML逸码等价表示。
urlquery
    返回其参数文本表示的可嵌入URL查询的逸码等价表示。
js
    返回其参数文本表示的JavaScript逸码等价表示。
call
    执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
    其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误; 

示例:

​
func main() {
    param := map[string]interface{}{
        "x":    true,
        "y":    true,
        "z":    false,
        "list": []string{"a", "b"},
        "add": func(a, b int) int {
            return a + b
        },
    }
    tmp, _ := template.New("aa").Parse(`
    x && y = {{and .x .y}}
​
    x || y = {{or .x .z}}
​
    !x = {{not .x}}
​
    len(list) = {{len .list}}
​
    printf("a %v b","true") = {{printf "a %v b" .x}}
​
    add(1,2) = {{call .add 1 2}}
`)
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}


五、比较函数

布尔函数会将任何类型的零值视为假,其余视为真。

下面是定义为函数的二元比较运算的集合:

    eq      如果arg1 == arg2则返回真
    ne      如果arg1 != arg2则返回真
    lt      如果arg1 < arg2则返回真
    le      如果arg1 <= arg2则返回真
    gt      如果arg1 > arg2则返回真
    ge      如果arg1 >= arg2则返回真 

为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:

 {{eq arg1 arg2 arg3}} 

比较函数只适用于基本类型。但是整数和浮点数两者不能互相比较。

示例:

func main() {
    param := map[string]interface{}{
        "x":    1,
        "y":    2,
        "z":    0,
        "c":    0,
    }
    tmp, _ := template.New("aa").Parse(`
    z=c {{eq .z .c}}
​
    x!=y {{ne .x .y}}
​
    x<y {{lt .x .y}}
​
    x<=y {{le .x .y}}
`)
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}


六、自定义函数

Go的模板支持自定义函数。

func main() {
    funMap := template.FuncMap{
        "add": func(a, b int) int {
            return a + b
        },
        "rec": func(a, b int) int {
            return a - b
        },
    }
    param := map[string]interface{}{
        "x": 1,
        "y": 2,
    }
    tmp, _ := template.New("aa").Funcs(funMap).Parse(`
    x+y = {{add .x .y}}
​
    x-y = {{rec .x .y}}
`)
    buffer := new(bytes.Buffer)
    _ = tmp.Execute(buffer, param)
    fmt.Println(buffer.String())
}


七、嵌套template

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

例子:t.tmpl文件内容如下:tmpl test

测试嵌套template语法 {{template "ul.tmpl"}}

{{template "ol.tmpl"}}
​
{{ define "ol.tmpl"}}
吃饭
睡觉
打豆豆
{{end}}

示例:

func tmplDemo(w http.ResponseWriter, r *http.Request) {
    tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
    if err != nil {
        fmt.Println("create template failed, err:", err)
        return
    }
    user := UserInfo{
        Name:   "小王子",
        Gender: "男",
        Age:    18,
    }
    tmpl.Execute(w, user)
}

注意:在解析模板时,被嵌套的模板一定要在后面解析,例如上面的示例中t.tmpl模板中嵌套了ul.tmpl,所以ul.tmpl要在t.tmpl后进行解析。


八、修改默认的标识符

Go标准库的模板引擎使用的花括号{{和}}作为标识,而许多前端框架(如Vue和AngularJS)也使用{{和}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:

template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")

九、链接