模板语法
{{.}}
模板语法都包含在 {{
和 }}
中间,其中{{ . }}
中的点表示当前对象。
当传入一个结构体对象时,可以根据 .
来访问结构体的对应字段。
当传入的变量是map时,也可以在模板文件中通过 .
根据key来取值。
main.go
package main
import (
"fmt"
"net/http"
"text/template"
)
type User struct {
// 如果字段的首字母大写,则该字段是导出的(public),可以在结构体外部访问
// 如果字段的首字母小写,则该字段是非导出的(private),只能在同一个包内访问
Name string
Gender string
Age int
}
func sayHello(w http.ResponseWriter, r *http.Request) {
// 解析指定文件生成模板对象
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
http.Error(w, "Unable to parse template", http.StatusInternalServerError)
fmt.Printf("parse template failed, err:%v\n", err)
return
}
u1 := User{
Name: "小王子",
Gender: "男",
Age: 18,
}
m1 := map[string]interface{}{
"name": "小王子",
"gender": "男",
"age": 18,
}
t.Execute(w, map[string]interface{}{
"u1": u1,
"m1": m1,
})
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
return
}
}
模板文件 hello.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>u1 </p>
<p>Hello {{ .u1.Name }}</p>
<p>性别: {{ .u1.Gender }}</p>
<p>年龄: {{ .u1.Age }}</p>
<p>m1</p>
<p>Hello {{ .m1.name }}</p>
<p>性别: {{ .m1.gender }}</p>
<p>年龄: {{ .m1.age }}</p>
</body>
</html>
效果
注释
{{/* a comment */}}
注释,执行时会忽略。可以多行。
注释不能嵌套,并且必须紧贴分界符始止。
pipeline
pipeline是指产生数据的操作。比如 {{.}}
、{{.Name}}
等。
Go的模板语法中支持使用管道符号|链接多个命令,用法和Unix下的管道类似:|
前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
注意:
并不是只有使用了|
才是pipeline。
Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。
变量
还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下:
$obj := {{ . }}
$obj
是变量的名字,在后续的代码中就可以使用该变量了。
移除空格
有时候在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和想的不一样,这个时候可以:
- 使用
{{-
语法去除模板内容左侧的所有空白符号, - 使用
-}}
去除模板内容右侧的所有空白符号。
例如:
{{- .Name -}}
注意:
-
要紧挨{{
和}}
,同时与模板值之间需要使用空格分隔。
条件判断
Go模板语法中的条件判断有以下几种:
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
{{ if lt .m1.age 22}}
好好上学
{{else}}
好好工作
{{end}}
range
Go的模板语法中使用 range
关键字进行遍历,有以下两种写法,其中 pipeline
的值必须是数组、切片、字典或者通道。
{{range pipeline}} T1 {{end}}
// 如果pipeline的值其长度为0,不会有任何输出
{{range pipeline}} T1 {{else}} T0 {{end}}
// 如果pipeline的值其长度为0,则会执行T0。
{{ range $idx,$hobby := .hobby }}
<p>{{$idx}} - {{$hobby}}</p>
{{else}}
没啥爱好
{{end}}
with
with 可以引导一块局部的作用域
{{with pipeline}} T1 {{end}}
// 如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。
{{with pipeline}} T1 {{else}} T0 {{end}}
// 如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。
<p>m1</p>
{{with .m1}}
<p>{{ .name }}</p>
<p>{{ .age }}</p>
<p>{{ .gender }}</p>
{{end}}
预定义函数
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用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]的值;每个被索引的主体必须是数组、切片或者字典。
即fmt.Sprintprintf
即fmt.Sprintfprintln
即fmt.Sprintlnhtml
返回与其参数的文本表示形式等效的转义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,模板执行会中断并返回给调用模板执行者该错误;
比较函数
布尔函数会将任何类型的零值视为假,其余视为真。
下面是定义为函数的二元比较运算的集合:
- 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 }}
比较函数只适用于基本类型(或重定义的基本类型,如"type Celsius float32")。但是,整数和浮点数不能互相比较。
自定义函数
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func f1(w http.ResponseWriter, r *http.Request) {
// 定义一个函数kua
// 要么只有一个返回值,要么有两个返回值,第二个返回值必须是error类型
k := func(name string) (string, error) {
return name + "年轻又帅气!", nil
}
// 定义模板 f.tmpl
t := template.New("f.tmpl") // 创建一个模板对象, 名字一定要与模板的名字对应上
// 告诉模板引擎,现在多了一个自定义的函数kua
t.Funcs(template.FuncMap{
"kua": k,
})
// 解析模板
_, err := t.ParseFiles("./f.tmpl")
if err != nil {
fmt.Printf("parse temple failed, err:%v\n", err)
return
}
name := "小王子"
// 渲染模板
t.Execute(w, name)
}
func main() {
http.HandleFunc("/", f1)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
return
}
}
f.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>自定义模板函数</title>
</head>
<body>
{{ kua . }}
</body>
</html>
嵌套template
可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func f1(w http.ResponseWriter, r *http.Request) {
// 定义一个函数kua
// 要么只有一个返回值,要么有两个返回值,第二个返回值必须是error类型
k := func(name string) (string, error) {
return name + "年轻又帅气!", nil
}
// 定义模板 f.tmpl
t := template.New("f.tmpl") // 创建一个模板对象, 名字一定要与模板的名字对应上
// 告诉模板引擎,现在多了一个自定义的函数kua
t.Funcs(template.FuncMap{
"kua": k,
})
// 解析模板
_, err := t.ParseFiles("./f.tmpl")
if err != nil {
fmt.Printf("parse temple failed, err:%v\n", err)
return
}
name := "小王子"
// 渲染模板
t.Execute(w, name)
}
func demo1(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
// 在解析模板时,被嵌套的模板一定要在后面解析
// t.tmpl模板中嵌套了ul.tmpl,所以ul.tmpl要在t.tmpl后进行解析
t, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
if err != nil {
fmt.Printf("parse temple failed, err:%v\n", err)
}
name := "小王子"
// 渲染模板
t.Execute(w, name)
}
func main() {
http.HandleFunc("/", f1)
// 注册一个demo1路由处理函数
http.HandleFunc("/tmplDemo", demo1)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
return
}
}
t.tmpl
<!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>
{{/* 嵌套另外一个define定义的模板 */}}
{{template "ol.tmpl"}}
<div>你好,{{ . }}</div>
</body>
</html>
{{/* 通过define定义一个模板 */}}
{{ define "ol.tmpl"}}
<ol>
<li>吃饭</li>
<li>睡觉</li>
<li>打豆豆</li>
</ol>
{{end}}
ul.tmpl
<ul>
<li>注释</li>
<li>日志</li>
<li>测试</li>
</ul>
效果
block
{{block "name" pipeline}} T1 {{end}}
block
是定义模板{{define "name"}} T1 {{end}}
和执行 {{template "name" pipeline}}
缩写,典型的用法是定义一组根模板,然后通过在其中重新定义块模板进行自定义。
main.go
package main
import (
"fmt"
"net/http"
"text/template"
)
func index(w http.ResponseWriter, r *http.Request) {
// 定义模板(模板继承的方式)
// 解析模板
t, err := template.ParseFiles("./templates/base.tmpl", "./templates/index.tmpl")
if err != nil {
fmt.Printf("parse template failed, err:%v\n", err)
return
}
// 渲染模板
name := "小王子"
t.ExecuteTemplate(w, "index.tmpl", name)
}
func home(w http.ResponseWriter, r *http.Request) {
// 定义模板(模板继承的方式)
// 解析模板
t, err := template.ParseFiles("./templates/base.tmpl", "./templates/home.tmpl")
if err != nil {
fmt.Printf("parse template failed, err:%v\n", err)
return
}
// 渲染模板
name := "七米"
t.ExecuteTemplate(w, "home.tmpl", name)
}
func main() {
http.HandleFunc("/index", index)
http.HandleFunc("/home", home)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
return
}
}
templates
base.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>模板继承</title>
<style>
* {
margin: 0;
}
.nav {
height: 50px;
width: 100%;
position: fixed;
top: 0;
background-color: burlywood;
}
.main {
margin-top: 50px;
}
.menu {
width: 20%;
height: 100%;
position: fixed;
left: 0;
background-color: cornflowerblue;
}
.center {
text-align: center;
}
</style>
</head>
<body>
<div class="nav"></div>
<div class="main">
<div class="menu"></div>
<div class="content center" >
{{block "content" .}}{{end}}
</div>
</div>
</body>
</html>
home.tmpl
{{/*继承根模板*/}}
{{template "base.tmpl" .}}
{{/*重新定义块模板*/}}
{{define "content"}}
<h1>这是home页面</h1>
<p>Hello {{.}}</p>
{{end}}
index.tmpl
{{/*继承根模板*/}}
{{template "base.tmpl" .}}
{{/*重新定义块模板*/}}
{{define "content"}}
<h1>这是index页面</h1>
<p>Hello {{.}}</p>
{{end}}
效果
修改默认的标识符
Go标准库的模板引擎使用的花括号 {{
和 }}
作为标识,而许多前端框架(如 Vue
和 Angular JS
)也使用 {{
和 }}
作为标识符,所以当同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:
template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")
main.go
package main
import (
"fmt"
"net/http"
"text/template"
)
func index(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
t, err := template.New("index.tmpl").
Delims("{[", "]}").
ParseFiles("./index.tmpl")
if err != nil {
fmt.Printf("parse template fail, err:%v\n", err)
return
}
// 渲染模板
name := "小王子"
err = t.Execute(w, name)
if err != nil {
fmt.Printf("execute template fail, err:%v\n", err)
}
}
func main() {
http.HandleFunc("/index", index)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
return
}
}
index.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>修改模板引擎的标识符</title>
</head>
<body>
<div>Hello {[ . ]}</div>
</body>
</html>
效果
text/template 与 html/tempalte 的区别
html/template
针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。
定义模板文件 xss.tmpl
<!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>xss</title>
</head>
<body>
用户的评论是:{{.}}
</body>
</html>
使用 html/template
渲染模板
这个时候传入一段JS代码并使用 html/template
去渲染该文件,会在页面上显示出转义后的JS内容。 <script>alert(123);</script>
这就是 html/template
做的事。
- 效果
自定义函数 safe
不想转义的话,可以自行编写一个safe函数,手动返回一个 template.HTML
类型的内容。
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func xss(w http.ResponseWriter, r *http.Request) {
// 定义模板
// 解析模板
// 解析模板之前定义一个自定义的函数safe
t, err := template.New("xss.tmpl").Funcs(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
}).ParseFiles("./xss.tmpl")
if err != nil {
fmt.Printf("parse template fail, err:%v\n", err)
return
}
// 渲染模板
str1 := "<script>alert(123);</script>"
str2 := "<a href='http://liwenzhou.com'>liwenzhou的博客</a>"
err = t.Execute(w, map[string]interface{}{
"str1": str1,
"str2": str2,
})
}
func main() {
http.HandleFunc("/xss", xss)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
return
}
}
xss.tmpl
<!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>xss</title>
</head>
<body>
用户的评论是:{{.str1}}
用户的评论是:{{.str2 | safe}}
</body>
</html>