go template使用
以text/template为例, 而html/template的接口与前者一样,不再缀述。
模板文件一般由 .tmpl
或 .tpl
为后缀。
一些名词
dot:用表示
.
,相当于一个变量,保存着传进来的值,可以改变pipeline:从字面上看,有点像管道
|
,但从文档上看,实际上指的是一切取值操作,包括{{ . }}
、{{ $name }}
,而|
与unix中的一样:作为函数的最后一个参数{{ }}:相当于占位符,主要的逻辑都写在里面
1 模板定义
1.1 取值
取值的作用主要是在页面中表示出来,或者使用一个变量保存
类型方式解释当前值{{ . }}
传什么值,就取什么值,假如直接在页面上输出的话,类似fmt.Println结构体{{ .Field }}
Field
指的是字段名,假如结构体嵌套,还可以再使用 .
取值,注意:遵循可见性规则变量{{ $varName }}
以 $
开头,取出变量的值,如何定义且看 1.2
字典{{ .key }}
取字典key对应的值,不需要首字母大写,嵌套时,可以再使用 .
取值无参数方法{{ .Method }}
执行Method这个方法,第一个返回值作为取出的值,注意:遵循可见性规则,而且返回值有要求,详细见xxx无参数函数{{ func }}
执行func(),把返回值当做结果,详见xxx
1.2 变量
有些值,我们可能需要重复使用,最好的方法就是使用一个变量来保存值减少重复求值的过程。
// 用到的数据
name := "abcdef"
假如我们把name传进来,那么假如要求其长度并将其保存起来,可以使用一个内置函数(见1.4): len
在go template中,用 $
表示变量,有点类似shell,使用:
{{ $lenght := len . }}
<h1>长度:{{ $lenght }}</h1>
实际上,还可以这样写:
{{ $lenght := . | len }}
<h1>长度:{{ $lenght }}</h1>
利用 |
把 abcdef
当做最后一个参数传给 len
1.3 动作
go template的动作(action)有点像,django的模板引擎中的tag,不过两者之间还是有较大的不一样。
1.3.1 注释
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止,就像这里表示的一样。
{{/* 我是注释啊 */}}
1.3.2 if判断
有以下3种
{{if pipeline}} T1 {{end}}
如果pipeline的值为empty,不产生输出,否则输出T1执行结果。不改变dot的值。
Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。
如:
<p>{{ if . }}welcome{{ end }}</p>
在这里我传的是一个bool值,为true,因此p便签中的内容为welcome
{{if pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值为empty,输出T0执行结果,否则输出T1执行结果。不改变dot的值。
<p>{{ if . }}welcome{{ else }} Get out!{{ end }}</p>
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
用于简化if-else链条,else action可以直接包含另一个if;等价于:
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
<p>{{ if eq . 1 }}count=1{{ else if eq . 2}} count=2{{ else if eq . 3}} count=3{{ end }}</p>
这里的 eq
是一个内置函数,相当于 ==
。
1.3.3 with
这里的 with
与并不是Python的with。go template的 with
相当于可以暂时修改dot的if。
形式一: {{with pipeline}} T1 {{end}}
如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。
{{ with gt . 18}} result:{{ . }}, 嘿嘿嘿 {{end}}
这里的gt相当于 >
, 因此假如执行成功,那么 .
必然是 true
.
形式二: {{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。不修改外面的dot。
实际上这和上面的一样,就是多了个 {{ else }}
{{ with gt . 18}} result:{{ . }}, 嘿嘿嘿 {{else}} 才{{ . }}岁,未成年 {{end}}
1.3.4 遍历
遍历的值必须是数组、切片、字典或者通道。
- 简单形式:
{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为0,不会有任何输出;
否则dot依次设为数组、切片、字典或者通道的每一个成员元素并执行T1;
如果pipeline的值为字典,且键可排序的基本类型,元素也会按键的顺序排序。
如,要遍历的数据如下:
data := map[string]string{
"张三": "hello",
"李四": "word",
}
在模板文件中定义:
<div>
{{ range . }}
<p>{{ . }}</p>
{{ end }}
</div>
所得到的结果:
<div>
<p>hello</p>
<p>word</p>
</div>
- 加else形式:
{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为0,不改变dot的值并执行T0;否则会修改dot并执行T1。
假如数据是一个空切片 []int{}
:
<div>
{{ range . }}
<p>{{ . }}</p>
{{ else }}
<span>no data</span>
{{ end }}
</div>
结果是 <span>no data</span>
1.3.5 嵌套与继承
define
当解析模板时,可以定义另一个模板,该模板会和当前解析的模板相关联。 模板必须定义在当前模板的最顶层,就像go程序里的全局变量一样。
这种定义模板的语法是将每一个子模板的声明放在”define”和”end” action内部。
如:
{{ define "rd"}}
<div>
{{ range . }}
<p>{{ . }}</p>
{{ else }}
<span>v2 no data</span>
{{ end }}
</div>
{{ end }}
注意:结尾 {{ end }}
和 define
后面的是字符串
template
template
就是对 define
定义的模板或其他模板文件的引用。
template的形式
{{template "name"}}
执行名为name的模板,提供给模板的参数为nil,如模板不存在输出为””
- {{template "name" pipeline}}
执行名为name的模板,提供给模板的参数为pipeline的值。
如,在当前文件中引用:
{{ define "rd"}}
<div>
{{ range . }}
<p>{{ . }}</p>
{{ else }}
<span>v2 no data</span>
{{ end }}
</div>
{{ end }}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>template</title>
</head>
<body>
{{/*引用*/}}
{{ template "rd"}}
</body>
</html>
template引用其他文件注意:
- 千万注意,要在代码中把文件读进来。
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl")
也可以使用其他函数 2. template后面跟的是完整的文件名
在h1.tpl中: {{ template "h2.tpl" }}
{{template "name" pipeline}}
形式
就是把define中或文件中的 .
替换成template传进去的值,假如不指定的话,使用当前文件的 .
{{ define "say"}}
<h1>say {{ . }}</h1>
{{ end }}
{{ template "say" "hi"}}
结果: <h1>say hi</h1>
block
block是定义模板 {{define "name"}} T1 {{end}}
和执行 {{template "name" pipeline}}
缩写,典型的用法是定义一组根模板,然后通过在其中重新定义块模板进行自定义。
如,在 ./templates/base.tpl
中,定义:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Go Templates</title>
</head>
<body>
<div class="container-fluid">
{{block "content" . }}{{end}}
</div>
</body>
</html>
而在其他的模板文件中:
{{template "base.tpl"}}
{{/* 使用 */}}
{{define "content"}}
<!-- 写入自己的代码 -->
<div>Hello world!</div>
{{end}}
同样要注意,在解析文件时把多个模板文件传进来
1.3.6 去空
{{- . -}}
使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。
1.4 函数
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。
1.4.1 一般函数
- and
函数返回它的第一个empty参数或者最后一个参数;
就是说”and x y”等价于”if x then y else x”;所有参数都会执行;
和上面一样:Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。下面再重复
如:
{{ and 1 0 }}
{{/* 返回0 */}}
{{ and 1 1 1}}
{{/* 返回1 */}}
- or
返回第一个非empty参数或者最后一个参数;
亦即”or x y”等价于”if x then x else y”;所有参数都会执行;
如:
{{ or 1 0 }}
{{/* 返回1 */}}
{{ or 0 2 1}}
{{/* 返回2 */}}
- not
返回它的单个参数的布尔值的否定
如:
{{ not 1 }}
{{/* 返回false */}}
{{ not 0 }}
{{/* 返回true */}}
- len
返回它的参数的整数类型长度
如:
{{/* . 为"abcdef" */}}
{{ len . }}
{{/* 返回6 */}}
- index
执行结果为第一个参数以剩下的参数为索引/键指向的值;
如”index x 1 2 3”返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
假如数据为:
data := [][]int{
{1, 2, 3, 4, 5,},
{6, 7, 8, 9, 10,},
}
{{ index . 0 1}}
{{/* 结果为2 */
即fmt.Sprint
S系列函数会把传入的数据生成并返回一个字符串。以下两个相同。
- printf
即fmt.Sprintf
- println
即fmt.Sprintln
- html
返回与其参数的文本表示形式等效的转义HTML。
这个函数在 html/template
中 不可用。
- urlquery
以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
这个函数在 html/template
中 不可用。
- js
返回与其参数的文本表示形式等效的转义JavaScript。
- call
执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
如 {{ call .X.Y 1 2 }}
等价于go语言里的 dot.X.Y(1, 2)
;
其中Y是函数类型的字段或者字典的值,或者其他类似情况;
call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
1.4.2 布尔函数
布尔函数会将任何类型的零值视为假,其余视为真。
函数说明eq如果arg1 == arg2则返回真ne如果arg1 != arg2则返回真lt如果arg1 < arg2则返回真le如果arg1 <= arg2则返回真gt如果arg1 > arg2则返回真ge如果arg1 >= arg2则返回真
注意:
为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:
arg1 == arg2 || arg1 ==arg3 || arg1==arg4 ...
(和go的||不一样,不做惰性运算,所有参数都会执行)
比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。它们实现了go语言规则的值的比较,但具体的类型和大小会忽略掉,因此 任意类型有符号整数值都可以互相比较;任意类型无符号整数值都可以互相比较;等等。但是,整数和浮点数不能互相比较。
1.4.3 自定义函数
使用 Funcs
方法,可以将自定义好的函数放入到模板中。
Funcs
的签名:
func (t *Template) Funcs(funcMap FuncMap) *Template
Funcs方法向模板t的函数字典里加入参数funcMap内的键值对。
如果funcMap某个键值对的值不是函数类型或者返回值不符合要求会panic。
但是,可以对t函数列表的成员进行重写。方法返回t以便进行链式调用。
例子:
例子中的使用的一些方法,见第2部分
// 1. 定义函数,首字母可以小写,注意返回值
func SayHi(char string) (string, error) {
return "Hi" + char, nil
}
func indexFunc(w http.ResponseWriter, r *http.Request) {
// 2. new
t := template.New("hello.tpl")
// 3. 加入t的函数列表,需要替换掉t
t = t.Funcs(template.FuncMap{"sayHi": SayHi})
// 4. Parse 可以是文件也可以是字符串
t, _ = t.ParseFiles("./hello.tpl")
userName := "xxx"
// 5. 渲染
_ = t.Execute(w, userName)
}
上面代码的2-4步,可以使用一段链式调用完成:
t, _ := template.New("hello.tpl").Funcs(template.FuncMap{"sayHi": SayHi}).ParseFiles("./hello.tpl")
注意事项
template.New
的文件名应该和要渲染的文件名一样
自定义函数有1-2个返回值,第一个值当做正式返回值。假如有第二个返回值:用来 panic
,其类型必须是 error
,当对应的值非 nil
时, panic
2 一些常用的方法
模板引擎的使用,一般有如下三步:
- 定义模板文件
- 解析模板文件
- 模板渲染
其中,第2、3步都要用到一些template的方法(这里用的是text/template)
2.1 解析模板文件的方法
// 解析字符串
func (t *Template) Parse(src string) (*Template, error)
// 解析1个或多个文件
func ParseFiles(filenames ...string) (*Template, error)
// 解析用正则匹配到的文件
func ParseGlob(pattern string) (*Template, error)
使用
1. Parse
这里使用 New
函数:
func New(name string) *Template
其作用是创建一个名为name的模板。
t, _ := template.New("test.tpl").Parse("<h1>{{ . }}</h1>")
Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。
如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;
如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板。
2. ParseFiles
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl", "./h3.tpl")
解析匹配参数中的文件里的模板定义并将解析结果与t关联。
如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要存在一个匹配的文件。
3. ParseGlob
t, _ := template.ParseGlob("./*.tpl")
解析当前目录下,所有以 .tpl
结尾的文件,假如有专门的文件夹存放模板文件,可以使用 templates/*.tmpl
(1层目录时)和 templates/**/*.tmpl
(2层目录时)
匹配时,和 ParseFiles
一样。
2.2 模板渲染的方法
func (t *Template) Execute(wr io.Writer, data interface{}) error
// Execute方法将解析好的模板应用到data上,并将输出写入wr。
// 如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。
// 模板可以安全的并发执行。
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
// 类似Execute,但是使用名为name的t关联的模板产生输出。
Execute
渲染的是 ParseFiles
或 ParseGlob
得到的第一个文件,假如要读取多个文件时,就有可能渲染的不是想要的文件,所以需要使用 ExecuteTemplate
指定一个已经解析的文件。
如:
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl")
userName := "xxx"
_ = t.Execute(w, userName)
怎么样都是渲染 h1.tpl
,假如要渲染 h2.tpl
:
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl")
userName := "xxx"
_ = t.ExecuteTemplate(w, "h2.tpl", userName)
注意
ExecuteTemplate
的name可以是 define
的模块名
如:
t, _ := template.New("test").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
_ = t.ExecuteTemplate(out, "T", "word")
当然,用其他解析方法也可以。
3 html/template的不同之处
由于 html/template
的API和 text/template
的API是一样的,解析和渲染没有什么不一样,但是在定义模板时,考虑到网站的安全性,会对一些风险内容进行转义,因此会有 text/template
有点差别。
如:
t, _ := template.New("test").Parse("{{ . }}")
char := "<script>alert('you have been pwned')</script>!"
_ = t.Execute(w, char)
得到的结果是: <script>alert('you have been pwned')</script>!
与预期不符,为此, html/template
有一个函数可以专门处理这些我们认为安全的字符串: template.HTML
。
再使用时,我们可以自定义一个 safe
函数,和其他模板引擎一样,不对一些字符串转义。
func safe(s string) template.HTML {
return template.HTML(s)
}
然后使用:
t, _ := template.New("test").Funcs(template.FuncMap{"safe": safe}).Parse("{{ . | safe }}")
_ = t.Execute(w, char)
补充
如果 {{.}
}是非字符串类型的值,可以用于JavaScript上下文环境里:
struct{A,B string}{ "foo", "bar" }
将该值应用在在转义后的模板里:
<script>var pair = {{.}};</script>
模板输出为:
<script>var pair = {"A": "foo", "B": "bar"};</script>
参考:
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦