官方定义:html
Package template implements data-driven templates for generating textual output.数组
template 包是数据驱动的文本输出模板,其实就是在写好的模板中填充数据。安全
什么是模板?bash
下面是一个简单的模板示例:并发
// 模板定义
tepl := "My name is {{ . }}"
// 解析模板
tmpl, err := template.New("test").Parse(tepl)
// 数据驱动模板
data := "jack"
err = tmpl.Execute(os.Stdout, data)
复制代码
{{ 和 }} 中间的句号 .
表明传入模板的数据,根据传入的数据不一样渲染不一样的内容。less
.
能够表明 go 语言中的任何类型,如结构体、哈希等。函数
至于 {{ 和 }} 包裹的内容统称为 action,分为两种类型:ui
action 求值的结果会直接复制到模板中,控制结构和咱们写 Go 程序差很少,也是条件语句、循环语句、变量、函数调用等等...lua
将模板成功解析(Parse)后,能够安全地在并发环境中使用,若是输出到同一个 io.Writer
数据可能会重叠(由于不能保证并发执行的前后顺序)。url
模板中的 action 并很少,咱们一个一个看。
{{/* comment */}}
复制代码
// 裁剪 content 先后的空格
{{- content -}}
// 裁剪 content 前面的空格
{{- content }}
// 裁剪 content 后面的空格
{{ content -}}
复制代码
{{ pipeline }}
复制代码
pipeline 表明的数据会产生与调用 fmt.Print
函数相似的输出,例如整数类型的 3 会转换成字符串 "3" 输出。
{{ if pipeline }} T1 {{ end }}
{{ if pipeline }} T1 {{ else }} T0 {{ end }}
{{ if pipeline }} T1 {{ else if pipeline }} T0 {{ end }}
// 上面的语法实际上是下面的简写
{{ if pipeline }} T1 {{ else }}{{ if pipeline }} T0 { {end }}{{ end }}
{{ if pipeline }} T1 {{ else if pipeline }} T2 {{ else }} T0 {{ end }}
复制代码
若是 pipeline 的值为空,不会输出 T1,除此以外 T1 都会被输出。
空值有 false、0、任意 nil 指针、接口值、数组、切片、字典和空字符串 ""
(长度为 0 的字符串)。
{{ range pipeline }} T1 {{ end }}
// 这个 else 比较有意思,若是 pipeline 的长度为 0 则输出 else 中的内容
{{ range pipeline }} T1 {{ else }} T0 {{ end }}
// 获取容器的下标
{{ range $index, $value := pipeline }} T1 {{ end }}
复制代码
pipeline 的值必须是数组、切片、字典和通道中的一种,便可迭代类型的值,根据值的长度输出多个 T1。
{{ define "name" }} T {{ end }}
复制代码
定义命名为 name 的模板。
{{ template "name" }}
{{ template "name" pipeline }}
复制代码
引用命名为 name 的模板。
{{ block "name" pipeline }} T1 {{ end }}
复制代码
block 的语义是若是有命名为 name 的模板,就引用过来执行,若是没有命名为 name 的模板,就是执行本身定义的内容。
也就是多作了一步模板是否存在的判断,根据这个结果渲染不一样的内容。
{{ with pipeline }} T1 {{ end }}
// 若是 pipeline 是空值则输出 T0
{{ with pipeline }} T1 {{ else }} T0 {{ end }}
{{ with arg }}
. // 此时 . 就是 arg
{{ end }}
复制代码
with 建立一个新的上下文环境,在此环境中的 .
与外面的 .
无关。
参数的值有多种表现形式,能够求值任何类型,包括函数、指针(指针会自动间接取值到原始的值):
nil
表明 go 语言中的 nil
.Field
,结果是 Field 的值,支持链式调用 .Field1.Field2
.Key
结果是 Key 对应的值.Field1.Key1.Method1.Field2.Key2.Method2
$x.Method1.Field
print (.Func1 arg1) (.Func2 arg2)
或 (.StructValuedMethod "arg").Field
这里最难懂的可能就是函数被调用的方式,若是访问结构体方法集中的函数和字段中的函数,此时的行为有什么不一样?
写个 demo 测一下:
type T struct {
Add func(int) int } func (t *T) Sub(i int) int {
log.Println("get argument i:", i)
return i - 1
}
func arguments() {
ts := &T{
Add: func(i int) int {
return i + 1
},
}
tpl := ` // 只能使用 call 调用 call field func Add: {{ call .ts.Add .y }} // 直接传入 .y 调用 call method func Sub: {{ .ts.Sub .y }} `
t, _ := template.New("test").Parse(tpl)
t.Execute(os.Stdout, map[string]interface{}{
"y": 3,
"ts": ts,
})
}
output:
call field func Add: 4 call method func Sub: 2 复制代码
能够得出结论:若是函数是结构体中的函数字段,该函数不会自动调用,只能使用内置函数 call
调用。
若是函数是结构体方法集中的方法,会自动调用该方法,而且会将返回值赋值给 .
,若是函数返回新的结构体、map,能够继续链式调用。
action 中的 pipeline 能够初始化变量存储结果,语法也很简单:
$variable = pipeline
复制代码
此时,这个 action 声明了一个变量而没有产生任何输出。
range 循环能够声明两个变量:
range $index, $element := pipeline
复制代码
在 if、with 和 range 中,变量的做用域拓展到 {{ end }} 所在的位置。
若是不是控制结构,声明的变量的做用域会扩展到整个模板。
例如在模板开始时声明变量:
{{ $pages := .pagination.Pages }}
{{ $current := .pagination.Current }}
复制代码
在渲染开始的时候,$
变量会被替换成 .
开头的值,例如 $pages
会被替换成 .pagenation.Pages
。因此在模板间的相互引用不会传递变量,变量只在某个特定的做用域中产生做用。
模板渲染时会在两个地方查找函数:
自定义函数使用 func (t *Template) Funcs(funcMap FuncMap) *Template
注册。
全局函数列表:
返回参数之间 and 布尔操做的结果,其实就是 JavaScript 中的逻辑操做符 &&
,返回第一个能转换成 false 的值,在 Go 中就是零值,若是都为 true 返回最后一个值。
tpl := "{{ and .x .y .z }}"
t, _ := template.New("test").Parse(tpl)
t.Execute(os.Stdout, map[string]interface{}{
"x": 1,
"y": 0,
"z": 3,
})
output:
0
复制代码
逻辑操做符 ||
,返回第一个能转换成 true 的值,在 Go 中就是非零值,若是都为 false 返回最后一个值。
tpl := "{{ or .x .y .z }}"
t, _ := template.New("test").Parse(tpl)
t.Execute(os.Stdout, map[string]interface{}{
"x": 1,
"y": 0,
"z": 3,
})
output:
1
复制代码
返回调用第一个函数参数的结果,函数必须有一个或两个回值(第二个返回值必须是 error,若是值不为 nil 会中止模板渲染)
tpl := "call: {{ call .x .y .z }} \n"
t, _ := template.New("test").Parse(tpl)
t.Execute(os.Stdout, map[string]interface{}{
"x": func(x, y int) int { return x+y},
"y": 2,
"z": 3,
})
output:
5
复制代码
返回转义后的 HTML 字符串,这个函数不能在 html/template
中使用。
返回转义后的 JavaScript 字符串。
在第一个参数是 array、slice、map 时使用,返回对应下标的值。
index x 1 2 3
等于 x[1][2][3]
。
返回复合类型的长度。
返回布尔类型参数的相反值。
等于 fmt.Sprint
。
等于 fmt.Sprintf
。
等于 fmt.Sprintln
。
对字符串进行 url Query 转义,不能在 html/template
包中使用。
// URLQueryEscaper returns the escaped value of the textual representation of
// its arguments in a form suitable for embedding in a URL query.
func URLQueryEscaper(args ...interface{}) string {
return url.QueryEscape(evalArgs(args))
}
复制代码
从源码能够看到这个函数直接调用 url.QueryEscape
对字符串进行转义,并无什么神秘的。
eq
: ==ge
: >=gt
: >le
: <=lt
: <ne
: !=分析两个源码:
// eq evaluates the comparison a == b || a == c || ...
func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
v1 := indirectInterface(arg1)
k1, err := basicKind(v1)
if err != nil {
return false, err
}
if len(arg2) == 0 {
return false, errNoComparison
}
for _, arg := range arg2 {
v2 := indirectInterface(arg)
k2, err := basicKind(v2)
if err != nil {
return false, err
}
truth := false
if k1 != k2 {
// Special case: Can compare integer values regardless of type's sign.
switch {
case k1 == intKind && k2 == uintKind:
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
case k1 == uintKind && k2 == intKind:
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
default:
return false, errBadComparison
}
} else {
switch k1 {
case boolKind:
truth = v1.Bool() == v2.Bool()
case complexKind:
truth = v1.Complex() == v2.Complex()
case floatKind:
truth = v1.Float() == v2.Float()
case intKind:
truth = v1.Int() == v2.Int()
case stringKind:
truth = v1.String() == v2.String()
case uintKind:
truth = v1.Uint() == v2.Uint()
default:
panic("invalid kind")
}
}
if truth {
return true, nil
}
}
return false, nil
}
// ne evaluates the comparison a != b.
func ne(arg1, arg2 reflect.Value) (bool, error) {
// != is the inverse of ==.
equal, err := eq(arg1, arg2)
return !equal, err
}
复制代码
eq 先判断接口类型是否相等,而后判断值是否相等,没什么特殊的地方。
ne 更是简单的调用 eq,而后取反。
ge、gt、le、lt 与 eq 相似,先判断类型,而后判断大小。
下面是一个更复杂的例子:
// 加载模板
template.ParseFiles("templates/")
// 加载多个模板到一个命名空间(同一个命名空间的模块能够互相引用)
template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")
// must 加载失败时 panic
tmpl := template.Must(template.ParseFiles("layout.html"))
// 执行加载后的模板文件,默认执行第一个
tmpl.Execute(w, "test")
// 若是 tmpl 中有不少个模板,能够指定要执行的模板名
tmpl.ExecuteTemplate(w, "layout", "Hello world")
复制代码
ExecuteTemplate
指定的名字就是模板文件中 define "name"
的 name。
Parse
系列函数初始化的 Template
类型实例。
Execute
系列函数则将数据传递给模板渲染最终的字符串。
模板本质上就是 Parse
函数加载多个文件到一个 Tempalte
类型实例中,解析文件中的 define 关键字注册命名模板,命名模板之间可使用 template
互相引用,Execute
传入对应的数据渲染。