1.快速入门
Go语言内置了 text/template 和 html/template两个模板库,专门用于处理网页html模板。
html/template 是在 text/template 模板库的基础上增加了对html输出的安全处理,主要目的是为了防止被攻击。
下面通过一个例子介绍template的用法。
模版引擎使用流程 :
编写模版代码
导入包
加载模版代码
根据模版参数渲染模版
1.快速入门例子
1.1.编写模版代码
将下面模版代码保存至views/demo.tpl文件中, 文件后缀名随意。
{{define "demo" }}
昵称: {{.Name}},
{{- if .IsWin}}
恭喜,大吉大利,今晚吃鸡!
{{- else }}
遗憾,鸡被吃光了!
{{- end }}
{{- end }}
define "模板名" 用于定义子模板,后面渲染模板会用到这个名字。
提示: 大家可以先跳过模版语法的细节,先了解大概怎么用,后面的章节会详细介绍模版语法。
1.2.导入包
import "text/template"
提示: 本文主要以text/template为例,如果要使用html/template直接替换包名就行,他们接口一样。
1.3.加载模版代码
t := template .Must(template .ParseGlob("./views/*.tpl" ))
1.4.根据模版参数渲染模版
定义模版参数:
type GameStatus struct {
Name string
IsWin bool
}
提示: 因为只能传入一个模版参数,如果想传入多个模版参数,可以使用map或者struct类型。
初始化模板参数, 这里初始化一个参数数组,下面用于循环渲染模板。
var userStatus = []GameStatus{
{"大春" , true },
{"NiuBee" , false },
{"球球" , true },
}
下面是根据userStatus 数组循环渲染模板
for _, u := range userStatus {
err := t.ExecuteTemplate(os.Stdout, "demo" , u)
if err != nil {
log .Println("executing template:" , err)
}
}
输出结果:
昵称: 大春,
恭喜,大吉大利,今晚吃鸡!
昵称: NiuBee,
遗憾,鸡被吃光了!
昵称: 球球,
恭喜,大吉大利,今晚吃鸡!
说明:根据不同的模版参数,渲染模版输出不同的页面内容,就是模版引擎的主要工作,目的是简化html模版输出工作。
2.模版基础语法
模板表达式都包括在 {{ 和 }} 之间。
格式: {{ 模板表达式 }}
注释格式: {{/* 注释语法 */}}
1.删除空格处理
有时候我们输出表达式结果的时候,在表达式结果左右两边都会携带一些空格。
下面是删除表达式结果左右两边空格的语法:
在左边增加减号 和空格 ,表示删除左边空格: {{- 模板表达式 }}
在右边增加空格 和减号 ,表示删除右边空格: {{ 模板表达式 -}}
删除表达式左右两边空格的写法: {{- 模板表达式 -}}
例子:
"{{23 -}} < {{- 45}}"
输出:
"23<45"
23和45之间的空格都被删除了
2.模版参数
在模版中主要通过 点( . ) 引用模版参数。
因为在渲染模版的时候只能传入一个参数,所以点( . ) 正好可以代表模版参数的引用。
模版参数支持数字、布尔值、字符串、map、struct、数组类型。
下面分别介绍传入不同类型参数,模版代码如何引用参数。
2.1. int/bool/string类型参数
模版参数为int/bool/string类型,可以直接用点( . ) 引用即可。
{{.}}
例子:
//渲染demo模版,传入字符串参数 "欢迎访问tizi365.com"
t.ExecuteTemplate(os.Stdout, "demo" , "欢迎访问tizi365.com" )
{{.}} 模版代码,输出:
欢迎访问tizi365 .com
2.2. map/struct类型参数
如果要传入多个模版参数,一般都使用map或者struct类型。
引用语法格式:
{{.字段名}}
嵌套的struct或者map类型引用语法格式:
{{.字段名1.字段名2}}
使用 点( . ) 连接多个字段名,就可以访问多层嵌套的struct/map类型数据.
例子:
type User struct {
Name string
}
type Order struct {
Id int
Title string
Customer User
}
food := Order{
Id: 10 ,
Title: "柠檬" ,
Customer: User{
Name: "李大春"
}
}
t.ExecuteTemplate(os.Stdout, "demo" , food)
模版代码:
商品名: {{.Title}}
用户名: {{.Customer.Name}}
输出结果:
商品名: 柠檬
用户名: 李大春
提示:如果模板参数传入数组,则需要循环语句处理输出,后面的章节会介绍range循环语句用法。
3.模版变量
在模版中也可以自定义变量, 类似golang使用:=符号定义变量,语法如下:
$变量名 : = 数据
变量名需要以$美元符号开头。
例子:
定义变量
$title := "标题"
为变量赋值, 第二次为变量赋值,不需要冒号:
$title = "新标题"
引用变量
{{$title }}
3.模板流程控制语句
模版流程控制域名主要指if/range/with三种语句。
1. if语句
语法格式1:表达式为真,则执输出T1
{{if 表达式}} T1 {{end }}
语法格式2:表达式为真,则执输出T1, 否则输出T0
{{if 表达式}} T1 {{else }} T0 {{end }}
语法格式3:表达式1 为真,则执输出T1, 否则如果表达式2 为真,则输出T0
{{if 表达式1 }} T1 {{else if 表达式2 }} T0 {{end }}
2. range循环语句
语法格式1:
{{range 数组引用}}
{{.}} - 在range循环中,(点 .) 引用的是数组元素,而不是模版参数。
{{end }}
例子:
假设传入的模版参数如下, 这里我们直接传入一个数组:
titles := []string{"标题1" , "标题2" , "标题3" }
模版代码:
{{range .}}
{{.}}
{{end }}
输出:
标题1
标题2
标题3
提示:在range循环中,改变了 (点 . ) 的作用,(点 . )引用的是当前的数组元素。
语法格式2:
类似go语法循环语句中的range, 这里定义了两个局部变量$index和$element, 第一个变量代表索引(对于map类型数据就是代表map的key), 第二个变量代表元素值(对于map类型数据,代表key对应的值)
{{range $index, $element := 数组或者map 的引用}}
索引: {{$index}}
元素值: {{$element}}
{{end} }
例子1:
map类型模版参数:
dataMap := map [string ]string {}
dataMap["key1" ] = "value1"
dataMap["key2" ] = "value2"
模版代码
{{range $key, $value : = .}}
key: {{$key}}
value: {{$value}}
{{end }}
输出:
key : key1
value : value1
key : key2
value : value2
例子2:
假设传入的模版参数如下, 这里传入一个struct 类型:
type Data struct {
Titles []string
}
data := Data{Titles:[]string {"标题1" , "标题2" , "标题3" }}
模版代码
{{range $index, $value : = .Titles}}
{{$index}} - {{$value}}
{{end }}
输出:
0 - 标题1
1 - 标题2
2 - 标题3
3. with语句
with语句主要用于struct类型数据的访问,就是一种快速访问struct数据的方式。
语法:
{{with struct 类型对象}}
{ {.字段}}
{{end}}
在with语句中,点( . ) 代表的是对with引用的struct对象,而不是模版参数。
例子:
type User struct {
Id int
UserName string
}
type Data struct {
User User
}
user := User{Id:1001 , UserName:"李大成" }
data := Data{}
data.User = user
模版代码:
{{with .User}}
Id: {{.Id}}
Username: {{.UserName}}
{{end}}
输出:
Id : 1001
Username : 李大成
注意: range和with语句都改变了点(.)引用的数据,那么如果想要在range和with语句中引用模版参数,请先将(点(.)赋值给一个自定义变量, 然后在range和with中通过自定义变量,引用模版参数。
4.模板函数
模板引擎为我们提供了函数机制,方面我们在处理模板时执行一些特定的功能,例如格式化输出内容、字母大小写转换等等。
1.函数调用语法
语法格式:
functionName [Argument...]
Argument参数是可选的,如果有多个参数,参数直接用空格分隔。
例子:
{{html "<h1 > www.tizi365.com</h1 > "}}
输出:
<h1 > www.tizi365.com</h1 >
html函数的作用是对输入参数进行html转义处理,html标签都转义了。
多个函数参数的例子:
{{printf "%d - %s" 100 "www.tizi365.com" }}
printf函数主要用于格式化输出字符串,是fmt.Sprintf函数的别名,用法跟fmt.Sprintf函数一样,区别就是模板函数的参数用空格隔开, 这里为printf输入了3个参数。
下面是输出:
100 - www .tizi365 .com
2.内置模板函数
模板引擎预先定义了一些函数,我们可以直接在模板中进行函数调用,下面介绍常用的内置函数。
2.1. 关系运算和逻辑运算函数
在模板引擎中关系运算(==、<、<=、>、>=、!=)和逻辑运算(and、or、not)都封装成函数形式,也就是说我们需要通过函数调用的方式进行关系运算和逻辑运算。
下面是关系运算函数:
说明:下表中的arg1和arg2代表两个参数
函数名
函数调用格式
对应关系运算
说明
eq
eq arg1 arg2
arg1 == arg2
arg1等于arg2则返回true
ne
ne arg1 arg2
arg1 != arg2
arg1不等于arg2则返回true
lt
lt arg1 arg2
arg1 < arg2
arg1小于arg2则返回true
le
le arg1 arg2
arg1 <= arg2
arg1小于等于arg2则返回true
gt
gt arg1 arg2
arg1 > arg2
arg1大于arg2则返回true
ge
ge arg1 arg2
arg1 >= arg2
arg1大于等于arg2则返回true
下面是逻辑运算函数 :
函数名
函数调用格式
对应逻辑运算
说明
and
and 表达式1 表达式2
表达式1 && 表达式2
表达式1和表达式2都为真的时候返回true
or
or 表达式1 表达式2
表达式1 || 表达式2
表达式1和表达式2其中一个为真的时候返回true
not
not 表达式
!表达式
表达式为false则返回true, 反之返回false
提示: 关系运算和逻辑运算函数通常跟if语句一起使用
例子:
{{$x := 100 }}
//等价于$x == 100
{{if eq $x 100 }}
...代码...
{{end} }
//等价于$x < 100
{{if lt $x 500 }}
...代码...
{{end} }
//等价于$x >= 100
{{if ge $x 500 }}
...代码...
{{end} }
//等价于$x > 50 && $x < 200
//这里调用了and 函数和gt 、lt 三个函数, gt 和lt 函数的结果作为and 的参数,gt 和lt 函数调用分别用括号包括起来
{{if and (gt $x 50 ) (lt $x 200 )}}
...代码...
{{end} }
{{$y := 200 }}
//等价于$x > 100 || $y > 100
{{if or (gt $x 100 ) (gt $y 100 )}}
...代码...
{{end} }
2.2. html函数
对html内容进行转义处理,前面的例子已经介绍。
2.3. len函数
用于计算数组大小。
例子:
数组大小: {{len .}}
假如传入的模板参数是一个数组:
a := []int {1 ,2 ,3 ,4 }
输出:
数组大小: 4
2.4. printf函数
主要用于格式化字符串,是go fmt.Sprintf函数的别名,前面的例子已经介绍。
2.5. urlquery函数
主要用于url编码。
例子:
/search?keyword={{urlquery "搜索关键词" }}
输出:
/search?keyword=%E6%90%9C%E7%B4%A2%E5%85%B3%E9%94%AE%E8%AF%8D
3.pipeline
pipeline 翻译过来可以称为管道或者流水线, pipeline运算的作用是将多个函数调用或者值串起来,从左往右执行,左边执行的结果会传递给右边,形成一个任务流水。
pipeline运算符:| (竖线)
语法格式:
command1 | command2 | command3 ...
command可以是一个值,也可以是一个函数。
例子1:
{{"<h1 > www.tizi365.com</h1 > " | html}}
这里意思就是将第一个字符串值传递给html函数。
输出:
<h1 > www.tizi365.com</h1 >
例子2:
{{"关键词" | html | urlquery}}
这个例子就是先将 "关键词" 传递给html函数转义下html标签,然后在将html执行结果传递给urlquery函数进行url编码。
输出:
%E5%85%B3%E9%94%AE%E8%AF%8D
提示: 如果函数有多个参数,pipeline运算会将值传递给函数的最后一个参数, 例如: {{100 | printf "value=%d"}}, 这里将100传递给printf函数的最后一个参数。
4.自定义模板函数
内置的模板函数使用有限,我们可以自己定义模板函数。
下面代码展示如何自定义模板函数:
funcMap := template .FuncMap{
"toupper" : strings.ToUpper,
}
const templateText = `
自定义函数调用例子:{{"abcdef" | toupper }}
`
tmpl, err := template .New("test" ).Funcs(funcMap).Parse(templateText)
if err != nil {
log .Fatalf("解析模板失败: %s" , err)
}
err = tmpl.Execute(os.Stdout, "" )
if err != nil {
log .Fatalf("渲染模板失败: %s" , err)
}
输出:
自定义函数调用例子:ABCDEF
5.子模板
在实际项目中,我们不可能只有一个模板,一般来说都有很多个模板,而且这些模板也会共享一些公共的模板,这些公共的模板我们都可以定义成子模板,在需要的时候调用子模板,就可以将子模板的内容嵌入当前模板中。
提示:在项目中使用子模板,可以让项目模板具有模块化的能力,提高模块复用能力和可维护性。
1.定义子模板
定义子模板语法:
{{define "子模板名字" }}
模板内容
{{end}}
例子:
{{define "T1" }}
模板内容T1
{{end}}
{{define "T2" }}
模板内容T2
{{end}}
2.调用子模板
调用子模板语法:
{{template "子模板名字" 参数}}
template函数的第一个参数是模板名字,第二个参数是模板参数, 在子模板内部也是通过点( . ) ,引用模板参数。
提示: template的第二个模板参数是可选的。
例子:
const templateText = `{{define "T1" }}ONE{{end}}
{{define "T2" }}TWO{{end}}
{{define "T3" }}{{template "T1" }} {{template "T2" }}{{end}}
{{template "T3" }}`
tmpl, err := template .New("test" ).Parse(templateText)
if err != nil {
log .Fatalf("解析模板失败: %s" , err)
}
err = tmpl.Execute(os.Stdout, "" )
if err != nil {
log .Fatalf("渲染模板失败: %s" , err)
}
输出:
ONE TWO
3.模板管理
上面的例子,我们将模板代码定义在一个变量或者常量中,这个只是用于演示,实际项目中模板代码通常非常多,建议大家按如下方式组织模板代码:
一个模块的模板代码,保存在一个模板文件中,模板文件名为tpl。
所有的模板代码都定义在子模板中,方便根据模板名字进行渲染。
例子 :
模板目录views, 下面分别按功能模块创建不同的模板文件
创建公共模板文件: views/common.tpl 主要用于保存一些公共的模板定义
{{define "common1" }}
这里是共享模块1
{{end}}
{{define "common2" }}
这里是共享模块2
{{end}}
创建mod1模块的模板文件: views/mod1.tpl
{{define "mod1" }}
这里是模块1
{{- template "common1" }}
{{end}}
创建mod2模块的模板文件: views/mod2.tpl
{{define "mod2" }}
这里是模块2
{{- template "common2" }}
{{end}}
渲染模板代码:
t := template .Must(template .ParseGlob("views/*.tpl" ))
t.ExecuteTemplate(os.Stdout, "mod1" , nil)
t.ExecuteTemplate(os.Stdout, "mod2" , nil)
输出:
这里是模块1
这里是共享模块1
这里是模块2
这里是共享模块2