本文只介绍template的语法和用法,关于template包的函数、方法、template的结构和原理,见:深入解析Go template模板使用详解。
入门示例
{{.}}
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go Web</title> </head> <body> {{ . }} </body> </html>
以下是test.html同目录下的一个go web程序:
package main import ( "html/template" "net/http" ) func tmpl(w http.ResponseWriter, r *http.Request) { t1, err := template.ParseFiles("test.html") if err != nil { panic(err) } t1.Execute(w, "hello world") } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/tmpl", tmpl) server.ListenAndServe() }
{{.}}
template.ParseFiles("test.html")Execute(w,"hello world"){{.}}.
本文不解释这些template包的函数、方法以及更底层的理论知识,本文只解释template的语法,如果觉得这些无法理解,或者看不懂官方手册,请看深入解析Go template模板使用详解。
关于点"."和作用域
{{.}}{{len .}}{{.Name}}{{$x.Name}}
$_
{{.}}Execute(w,"hello worold")
再例如,有一个Person struct。
type Person struct { Name string Age int } func main(){ p := Person{"longshuai",23} tmpl, _ := template.New("test").Parse("Name: {{.Name}}, Age: {{.Age}}") _ = tmpl.Execute(os.Stdout, p) }
{{.Name}}{{.Age}}{{.Name}}p.Name{{.Age}}{{p.Age}}
但是并非只有一个顶级作用域,range、with、if等内置action都有自己的本地作用域。它们的用法后文解释,这里仅引入它们的作用域来解释"."。
例如下面的例子,如果看不懂也没关系,只要从中理解"."即可。
package main import ( "os" "text/template" ) type Friend struct { Fname string } type Person struct { UserName string Emails []string Friends []*Friend } func main() { f1 := Friend{Fname: "xiaofang"} f2 := Friend{Fname: "wugui"} t := template.New("test") t = template.Must(t.Parse( `hello {{.UserName}}! {{ range .Emails }} an email {{ . }} {{- end }} {{ with .Friends }} {{- range . }} my friend name is {{.Fname}} {{- end }} {{ end }}`)) p := Person{UserName: "longshuai", Emails: []string{"a1@qq.com", "a2@gmail.com"}, Friends: []*Friend{&f1, &f2}} t.Execute(os.Stdout, p) }
输出结果:
hello longshuai! an email a1@qq.com an email a2@gmail.com my friend name is xiaofang my friend name is wugui
这里定义了一个Person结构,它有两个slice结构的字段。在Parse()方法中:
{{.UserName}}{{.Emails}}{{.Friends}}{{range .Emails}}...{{end}}an email {{.}}{{range .}}{{.Fname}}&f1&f2{{.Fname}}
去除空白
template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,对于要解析的内容,不要随意缩进、随意换行。
{{{{- xxxx
}}xxxx -}}
例如:
{{23}} < {{45}} -> 23 < 45 {{23}} < {{- 45}} -> 23 <45 {{23 -}} < {{45}} -> 23< 45 {{23 -}} < {{- 45}} -> 23<45
{{23 -}}}} <{{- 45}}< {{
再看上一节的例子中:
t.Parse( `hello {{.UserName}}! {{ range .Emails }} an email {{ . }} {{- end }} {{ with .Friends }} {{- range . }} my friend name is {{.Fname}} {{- end }} {{ end }}`)
注意,上面没有进行缩进。因为缩进的制表符或空格在替换的时候会保留。
range {{.Emails}}{{-{{- end{{-
注释
{{/* a comment */}}
注释后的内容不会被引擎进行替换。但需要注意,注释行在替换的时候也会占用行,所以应该去除前缀和后缀空白,否则会多一空行。
{{- /* a comment without prefix/suffix space */}} {{/* a comment without prefix/suffix space */ -}} {{- /* a comment without prefix/suffix space */ -}}
注意,应该只去除前缀或后缀空白,不要同时都去除,否则会破坏原有的格式。例如:
t.Parse( `hello {{.UserName}}! {{- /* this line is a comment */}} {{ range .Emails }} an email {{ . }} {{- end }}
管道pipeline
{{.}}{{.Name}}funcname args
||
例如:
{{.}} | printf "%s\n" "abcd"
{{.}}
命令可以有超过1个的返回值,这时第二个返回值必须为err类型。
|
(len "output")
{{println (len "output")}}
"output"
{{`"output"`}} {{printf "%q" "output"}} {{"output" | printf "%q"}} {{printf "%q" (print "out" "put")}} {{"put" | printf "%s%s" "out" | printf "%q"}} {{"output" | printf "%s" | printf "%q"}}
变量
可以在template中定义变量:
// 未定义过的变量 $var := pipeline // 已定义过的变量 $var = pipeline
例如:
{{- $how_long :=(len "output")}} {{- println $how_long}} // 输出6
再例如:
tx := template.Must(template.New("hh").Parse( `{{range $x := . -}} {{$y := 333}} {{- if (gt $x 33)}}{{println $x $y ($z := 444)}}{{- end}} {{- end}} `)) s := []int{11, 22, 33, 44, 55} _ = tx.Execute(os.Stdout, s)
输出结果:
44 333 444 55 333 444
$x$y$x$z($z := 444)
需要注意三点:
$$ = [11 22 33 44 55]$
例如:
func main() { t1 := template.New("test1") tmpl, _ := t1.Parse( ` {{- define "T1"}}ONE {{println .}}{{end}} {{- define "T2"}}{{template "T1" $}}{{end}} {{- template "T2" . -}} `) _ = tmpl.Execute(os.Stdout, "hello world") }
{{template "T2" .}}$$$
$.template("T2",.)${{template "T2"}}$=nil
如果看不懂这些,后文有解释。
条件判断
有以下几种if条件判断语句,其中第三和第四是等价的。
{{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。
range...end迭代
有两种迭代表达式类型:
{{range pipeline}} T1 {{end}} {{range pipeline}} T1 {{else}} T0 {{end}}
range可以迭代slice、数组、map或channel。迭代的时候,会设置"."为当前正在迭代的元素。
对于第一个表达式,当迭代对象的值为0值时,则range直接跳过,就像if一样。对于第二个表达式,则在迭代到0值时执行else语句。
tx := template.Must(template.New("hh").Parse( `{{range $x := . -}} {{println $x}} {{- end}} `)) s := []int{11, 22, 33, 44, 55} _ = tx.Execute(os.Stdout, s)
需注意的是,range的参数部分是pipeline,所以在迭代的过程中是可以进行赋值的。但有两种赋值情况:
{{range $value := .}} {{range $key,$value := .}}
如果range中只赋值给一个变量,则这个变量是当前正在迭代元素的值。如果赋值给两个变量,则第一个变量是索引值(map/slice是数值,map是key),第二个变量是当前正在迭代元素的值。
下面是在html中使用range的一个示例。test.html文件内容如下:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go Web</title> </head> <body> <ul> {{ range . }} <li>{{ . }}</li> {{ else }} <li> Nothing to show </li> {{ end}} </ul> </body> </html>
以下是test.html同目录下的go程序文件:
package main import ( "html/template" "net/http" ) func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", process) server.ListenAndServe() } func process(w http.ResponseWriter, r *http.Request) { t1 := template.Must(template.ParseFiles("test.html")) s := []string{ "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日",} t1.Execute(w, s) }
with...end
with用来设置"."的值。两种格式:
{{with pipeline}} T1 {{end}} {{with pipeline}} T1 {{else}} T0 {{end}}
对于第一种格式,当pipeline不为0值的时候,点"."设置为pipeline运算的值,否则跳过。对于第二种格式,当pipeline为0值时,执行else语句块,否则"."设置为pipeline运算的值,并执行T1。
例如:
{{with "xx"}}{{println .}}{{end}}
xx
内置函数和自定义函数
template定义了一些内置函数,也支持自定义函数。关于如何自定义函数,见深入解析Go template模板使用详解。
以下是内置的函数列表:
and 返回第一个为空的参数或最后一个参数。可以有任意多个参数。 and x y等价于if x then y else x not 布尔取反。只能一个参数。 or 返回第一个不为空的参数或最后一个参数。可以有任意多个参数。 "or x y"等价于"if x then x else y"。 print printf println 分别等价于fmt包中的Sprint、Sprintf、Sprintln len 返回参数的length。 index 对可索引对象进行索引取值。第一个参数是索引对象,后面的参数是索引位。 "index x 1 2 3"代表的是x[1][2][3]。 可索引对象包括map、slice、array。 call 显式调用函数。第一个参数必须是函数类型,且不是template中的函数,而是外部函数。 例如一个struct中的某个字段是func类型的。 "call .X.Y 1 2"表示调用dot.X.Y(1, 2),Y必须是func类型,函数参数是1和2。 函数必须只能有一个或2个返回值,如果有第二个返回值,则必须为error类型。
除此之外,还内置一些用于比较的函数:
eq arg1 arg2: arg1 == arg2时为true ne arg1 arg2: arg1 != arg2时为true lt arg1 arg2: arg1 < arg2时为true le arg1 arg2: arg1 <= arg2时为true gt arg1 arg2: arg1 > arg2时为true ge arg1 arg2: arg1 >= arg2时为true
对于eq函数,支持多个参数:
eq arg1 arg2 arg3 arg4...
它们都和第一个参数arg1进行比较。它等价于:
arg1==arg2 || arg1==arg3 || arg1==arg4
示例:
{{ if (gt $x 33) }}{{println $x}}{{ end }}
嵌套template:define和template
define可以直接在待解析内容中定义一个模板,这个模板会加入到common结构组中,并关联到关联名称上。如果不理解,还是建议阅读深入解析Go template模板使用详解。
定义了模板之后,可以使用template这个action来执行模板。template有两种格式:
{{template "name"}} {{template "name" pipeline}}
第一种是直接执行名为name的template,点设置为nil。第二种是点"."设置为pipeline的值,并执行名为name的template。可以将template看作是函数:
template("name) template("name",pipeline)
例如:
func main() { t1 := template.New("test1") tmpl, _ := t1.Parse( `{{- define "T1"}}ONE {{println .}}{{end}} {{- define "T2"}}TWO {{println .}}{{end}} {{- define "T3"}}{{template "T1"}}{{template "T2" "haha"}}{{end}} {{- template "T3" -}} `) _ = tmpl.Execute(os.Stdout, "hello world") }
输出结果:
ONE <nil> TWO haha
{{template T3}}{{template "T1"}}{{temlate "T2" "haha"}}
注意,模板之间的变量是不会继承的。
下面是html文件中嵌套模板的几个示例。
t1.html文件内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=9"> <title>Go Web Programming</title> </head> <body> <div> This is t1.html before</div> <div>This is the value of the dot in t1.html - [{{ . }}]</div> <hr /> {{ template "t2.html" }} <hr /> <div> This is t1.html after</div> </body> </html>
{{template "t2.html"}}
<div style="background-color: yellow;"> This is t2.html<br/> This is the value of the dot in t2.html - [{{ . }}] </div>
处理这两个文件的handler函数如下:
func process(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("t1.html", "t2.html") t.Execute(w, "Hello World!") }
上面也可以不额外定义t2.html文件,而是直接在t1.html文件中使用define定义一个模板。修改t1.html文件如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=9"> <title>Go Web Programming</title> </head> <body> <div> This is t1.html before</div> <div>This is the value of the dot in t1.html - [{{ . }}]</div> <hr /> {{ template "t2.html" }} <hr /> <div> This is t1.html after</div> </body> </html> {{define "t2.html"}} <div style="background-color: yellow;"> This is t2.html<br/> This is the value of the dot in t2.html - [{{ . }}] </div> {{end}}
然后在handler中,只需解析t1.html一个文件即可。
func process(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("t1.html") t.Execute(w, "Hello World!") }
block块
{{block "name" pipeline}} T1 {{end}} A block is shorthand for defining a template {{define "name"}} T1 {{end}} and then executing it in place {{template "name" pipeline}} The typical use is to define a set of root templates that are then customized by redefining the block templates within.
根据官方文档的解释:block等价于define定义一个名为name的模板,并在"有需要"的地方执行这个模板,执行时将"."设置为pipeline的值。
但应该注意,block的第一个动作是执行名为name的模板,如果不存在,则在此处自动定义这个模板,并执行这个临时定义的模板。换句话说,block可以认为是设置一个默认模板。
例如:
{{block "T1" .}} one {{end}}
{{template "T1" .}}{{define "T1"}} one {{end}}
下面是正常情况下不使用block的示例。
home.html文件内容如下:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go Web Programming</title> </head> <body> {{ template "content" }} </body> </html>
在此文件中指定了要执行一个名为"content"的模板,但此文件中没有使用define定义该模板,所以需要在其它文件中定义名为content的模板。现在分别在两个文件中定义两个content模板:
red.html文件内容如下:
{{ define "content" }} <h1 style="color: red;">Hello World!</h1> {{ end }}
blue.html文件内容如下:
{{ define "content" }} <h1 style="color: blue;">Hello World!</h1> {{ end }}
在handler中,除了解析home.html,还根据需要解析red.html或blue.html:
func process(w http.ResponseWriter, r *http.Request) { rand.Seed(time.Now().Unix()) t := template.New("test") if rand.Intn(10) > 5 { t, _ = template.ParseFiles("home.html", "red.html") } else { t, _ = template.ParseFiles("home.html", "blue.html") } t.Execute(w,"") }
如果使用block,那么可以设置默认的content模板。例如将原本定义在blue.html中的content设置为默认模板。
修改home.html:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go Web Programming</title> </head> <body> {{ block "content" . }} <h1 style="color: blue;">Hello World!</h1> {{ end }} </body> </html>
然后修改handler:
func process(w http.ResponseWriter, r *http.Request) { rand.Seed(time.Now().Unix()) t := template.New("test") if rand.Intn(10) > 5 { t, _ = template.ParseFiles("home.html", "red.html") } else { t, _ = template.ParseFiles("home.html") } t.Execute(w,"") }
当执行else语句块的时候,发现home.html中要执行名为content的模板,但在ParseFiles()中并没有解析包含content模板的文件。于是执行block定义的content模板。而执行非else语句的时候,因为red.html中定义了content,会直接执行red.html中的content。
block通常设置在顶级的根文件中,例如上面的home.html中。
html/template的上下文感知
对于html/template包,有一个很好用的功能:上下文感知。text/template没有该功能。
上下文感知具体指的是根据所处环境css、js、html、url的path、url的query,自动进行不同格式的转义。
例如,一个handler函数的代码如下:
func process(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("test.html") content := `I asked: <i>"What's up?"</i>` t.Execute(w, content) }
上面content是Execute的第二个参数,它的内容是包含了特殊符号的字符串。
下面是test.html文件的内容:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go Web Programming</title> </head> <body> <div>{{ . }}</div> <div><a href="/{{ . }}" rel="external nofollow" >Path</a></div> <div><a href="/?q={{ . }}" rel="external nofollow" >Query</a></div> <div><a onclick="f('{{ . }}')">Onclick</a></div> </body> </html>
{{.}}
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go Web Programming</title> </head> <body> <div>I asked: <i>"What's up?"</i></div> <div> <a href="/I%20asked:%20%3ci%3e%22What%27s%20up?%22%3c/i%3e" rel="external nofollow" > Path </a> </div> <div> <a href="/?q=I%20asked%3a%20%3ci%3e%22What%27s%20up%3f%22%3c%2fi%3e" rel="external nofollow" > Query </a> </div> <div> <a onclick="f('I asked: \x3ci\x3e\x22What\x27s up?\x22\x3c\/i\x3e')"> Onclick </a> </div> </body> </html>
不转义
如果确实不想转义,可以进行类型转换。
type CSS type HTML type JS type URL
转换成指定个时候,字符都将是字面意义。
例如:
func process(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("tmpl.html") t.Execute(w, template.HTML(r.FormValue("comment"))) }
更多关于Go模板template用法详解请查看下面的相关链接