template包实现了数据驱动的用于生成文本输出的模板。
生成html文件的模板在html/template包下。
模板使用插值语法{{.var}}
格式,也可以使用一些流程控制,例如if else
、循环range
,还可以使用一些函数,包括内建函数和自定义函数
package main
import (
"os"
"text/template"
)
func main() {
// 数据
name := "Psych"
// 定义模板
muban := "hello,{{.}}"
// 解析模板
tmpl, err := template.New("test").Parse(muban)
if err != nil {
panic(err)
}
// 执行模板并输出到终端
err = tmpl.Execute(os.Stdout, name)
if err != nil {
panic(err)
}
}
运行结果:
[Running] go run "e:\golang开发学习\go_pro\test.go"
hello,Psych
[Done] exited with code=0 in 1.472 seconds
定义模板文件hello.html
:
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HELLOtitle>
head>
<body>
<p>hello {{ . }}p>
body>
html>
测试:
package main
import (
"net/http"
"text/template"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("./hello.html")
if err != nil {
panic(err)
}
//渲染模板
err = t.Execute(w, "Psych")
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
panic(err)
}
}
浏览器输入127.0.0.1:9090
显示结果:
定义模板文件hello.html
:
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HELLOtitle>
head>
<body>
<p>hello {{ .Name }},Your age {{ .Age }}p>
body>
html>
测试:
package main
import (
"net/http"
"text/template"
)
type User struct {
Name string
Age int
}
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("./hello.html")
if err != nil {
panic(err)
}
//渲染模板
user1 := User{
Name: "Psych",
Age: 18,
}
err = t.Execute(w, user1)
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
panic(err)
}
}
浏览器输入127.0.0.1:9090
显示结果:
定义模板文件hello.html
:
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HELLOtitle>
head>
<body>
<p>hello {{ .user1.Name }},Your age {{ .user1.Age }}p>
<p>hello {{ .user2.Name }},Your age {{ .user2.Age }}p>
body>
html>
测试:
package main
import (
"net/http"
"text/template"
)
type User struct {
Name string
Age int
}
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("./hello.html")
if err != nil {
panic(err)
}
//渲染模板
user1 := User{
Name: "Psych",
Age: 18,
}
user2 := User{
Name: "Klee",
Age: 999,
}
err = t.Execute(w, map[string]interface{}{
"user1": user1,
"user2": user2,
})
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
panic(err)
}
}
浏览器输入127.0.0.1:9090
显示结果:
模板引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等空白都原样保留。
所以,对于要解析的内容,不要随意缩进、随意换行。
例如:
{{23}} < {{45}} -> 23 < 45
{{23}} < {{- 45}} -> 23 <45
{{23 -}} < {{45}} -> 23< 45
{{23 -}} < {{- 45}} -> 23<45
有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样;
这个时候可以:
- 使用
{{- xxx
语法去除模板内容左侧的所有空白符号- 使用
xxx -}}
去除模板内容右侧的所有空白符号。注意:
-
要紧挨{{
和}}
,同时与模板值之间需要使用空格分隔。
注释方式:{{/* a comment */}}
。
注释后的内容不会被引擎进行替换。
注意:注释行在替换的时候也会占用行,所以应该去除前缀后后缀空白,否则会多一行
{{- /* a comment */}}
{{/* a comment */ -}}
{{- /* a comment */ -}}
管道就是一系列命令的链式调用。当然,也可以是一个命令。例如:计算表达式的值{{.}}
、{{.Name}}
或者是一个函数调用或者方法调用。
可以使用管道符号|
连接多个命令,用法和unix下的管道类似:|
前面的命令将运算结果(或者返回值)传递给后一个命令的最后一个位置。
注意:并非只有使用了
|
才是管道。Go template中,管道的概念是传递数据,只要能产生数据的都是管道。
Pipeline的几种示例,他们都输出"Psych"
:
{{`"Psych"`}}
{{printf "%q" "Psych"}}
{{"Psych" | printf "%q"}}
{{printf "%q" (print "Psy" "ch")}}
{{"ch" | printf "%s%s" "Psy" | printf "%q"}}
{{"Psych" | printf "%s" | printf "%q"}}
测试:
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>testtitle>
head>
<body>
{{`"Psych"`}} <br>
{{printf "%q" "Psych"}} <br>
{{"Psych" | printf "%q"}} <br>
{{printf "%q" (print "Psy" "ch")}} <br>
{{"ch" | printf "%s%s" "Psy" | printf "%q"}} <br>
{{"Psych" | printf "%s" | printf "%q"}} <br>
body>
html>
运行结果:
$var := pipeline
$var = pipeline
示例:
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>testtitle>
head>
<body>
{{$Name := "Psych"}}
{{$Name = "Klee"}}
{{$Name}} <br>
{{$len := (len "hello,golang")}}
{{$len}} <br>
body>
html>
运行结果:
Go模板语法中的条件判断有以下几种:
{{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}}
pipeline为false的情况是各种数据对象的0值:数值0;指针或接口是nil;数组、slice、map或string则是len为0。
经常结合一下运算符表达式使用:
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”)。但是,整数和浮点数不能互相比较。
示例:
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>testtitle>
head>
<body>
{{$Age := 18}}
{{if (ge $Age 18)}}
<h3>你是成年人h3>
{{else}}
<h3>你还未成年h3>
{{end}}
body>
html>
运行结果:
Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。
{{range pipeline}} T1 {{end}}
如果pipeline的值为0值,不会有任何输出
{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值为0值,则会执行T0。
range可以迭代slice、数组、map或channel。
迭代时,会设置
.
为当前正在迭代的元素。
示例:
test.html
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>testtitle>
head>
<body>
{{range $x := . -}}
{{println $x}}
{{- end}}
body>
html>
test.go
package main
import (
"net/http"
"text/template"
)
type User struct {
Name string
Age int
}
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("./hello.html")
if err != nil {
panic(err)
}
//渲染模板
s := []string{"四川", "成都", "高新"}
err = t.Execute(w, s)
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
panic(err)
}
}
运行结果:
with用来设置"."
的值,语法如下:
{{with pipeline}} T1 {{end}}
如果pipeline不为0值时,"."设置为pipeline运算的值,否则跳过。
{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为0值,执行T0,否则"."设置为pipeline运算的值并执行T1。
示例:
{{with "四川-成都-高新"}}{{println .}}{{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]的值;每个被索引的主体必须是数组、切片或者字典。
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,模板执行会中断并返回给调用模板执行者该错误;
布尔函数会将任何类型的零值视为假,其余视为真。
define可以直接在待解析内容中定义一个模板,这个模板会加入到common结构组中,并关联到关联名称上
{{template "name"}}
{{template "name" pipeline}}
{{define "name"}}
实例演示:
假设有一个header.html、footer.html和index.html,其中index.html包含header.html和footer.html
header.html:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
{{define "header"}}
<p>header部分...p>
{{end}}
body>
html>
footer.html:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
{{define "footer"}}
<p>footer部分...p>
{{end}}
body>
html>
index.html:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
{{template "header"}}
<h1>首页h1>
{{template "footer"}}
body>
html>
test.go:
package main
import (
"net/http"
"text/template"
)
type User struct {
Name string
Age int
}
func tmpl(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("index.html", "header.html", "footer.html")
if err != nil {
panic(err)
}
//渲染模板
err = t.Execute(w, nil)
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", tmpl)
err := http.ListenAndServe(":9090", nil)
if err != nil {
panic(err)
}
}
运行结果:
html/template
针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。
这个时候传入一段JS代码并使用html/template
去渲染该文件,会在页面上显示出转义后的JS内容。 这就是
html/template
为我们做的事。
但是在某些场景下,我们如果相信用户输入的内容,不想转义的话,可以自行编写一个safe函数,手动返回一个template.HTML
类型的内容。示例如下:
doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div>{{ .str1}}div>
<div>{{ .str2 | safe}}div>
body>
html>
func xss(w http.ResponseWriter, r *http.Request) {
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.Println("parse template failed err =", err)
return
}
str1 := ""
str2 := "百度"
t.Execute(w, map[string]string{
"str1": str1,
"str2": str2,
})
}
这样我们只需要在模板文件不需要转义的内容后面使用我们定义好的safe函数就可以了。
{{ . | safe }}