html/template包实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用text/template包。
在基于MVC的Web架构中,我们通常需要在后端渲染一些数据到HTML文件中,从而实现动态的网页效果。
声明模板,放到web包下
<!DOCTYPE html>
<html lang="en">
<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>Hello</title>
</head>
<body>
<!-- 注意,此处使用的构造体实例属性必须是public的也就是首字母大写 -->
<p>Id: {{.Id}}</p>
<p>Name: {{.Name}}</p>
<p>Age: {{.Age}}</p>
</body>
</html>
搭建web服务
package main
import (
"fmt"
"net/http"
"text/template"
)
type Stu struct {
Id string
Name string
Age int
}
func main() {
http.HandleFunc("/", indexHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
}
}
// indexHandler 首页操作
func indexHandler(resp http.ResponseWriter, req *http.Request) {
//解析文件,获取模板实例
t, err := template.ParseFiles("web/index.html")
if err != nil {
fmt.Println(err)
}
s := Stu{"A123456", "Lucy", 18}
//将模板写出,并绑定实例对象,解析模板
t.Execute(resp, s)
}
{{.}}
:处于顶级作用域时,表示Execute函数第二个参数传入的实例对象,或者任何数据
如果是在{{range}} {{.}} {{end}}
中,代表range迭代出的元素
{{/* a comment */}}
pipeline是指产生数据的操作。比如{{.}}
、{{.Name}}
等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|
前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
{{"output" | printf "%q"}}
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为0,不会有任何输出
{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为0,则会执行T0。
template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,对于要解析的内容,不要随意缩进、随意换行。
可以在{{
符号的后面加上短横线并保留一个或多个空格-
来去除它前面的空白(包括换行符、制表符、空格等),即{{- xxxx
。
在}}
的前面加上一个或多个空格以及一个短横线-
来去除它后面的空白,即xxxx -}}
{{23}} < {{45}} -> 23 < 45
{{23}} < {{- 45}} -> 23 <45
{{23 -}} < {{45}} -> 23< 45
{{23 -}} < {{- 45}} -> 23<45
$
,它代表模板的最顶级作用域对象(通俗地理解,是以模板为全局作用域的全局变量),在Execute()执行的时候进行赋值,且一直不变。// 未定义过的变量
$var := pipeline
// 已定义过的变量
$var = pipeline
{{with "xx"}}{{println .}}{{end}}
上面将输出xx
,因为.
已经设置为xx
。
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,模板执行会中断并返回给调用模板执行者该错误;
开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成header
、content
、footer
三个部分。
声明模板的语法
{{define "子模板名称"}}内容{{end}}
调用模板的方法,pipeline为给模板传递的参数,可以不写则为nil
{{template "子模板名称" pipeline}}
例如,我们可以写三个html文件,header.html
、content.html
、footer.html
header.html
{{define "header"}}
<div>
header
{{.}}
</div>
{{end}}
footer.html
{{define "footer"}}
<div>
footer
</div>
{{end}}
content.html
<!DOCTYPE html>
<html lang="en">
<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>template</title>
</head>
<body>
{{template "header" .}}
<div>
content
</div>
{{template "footer"}}
</body>
</html>
package main
import (
"fmt"
"os"
"text/template"
)
func main() {
// 解析文件,获取模板实例
s1, _ := template.ParseFiles("header.html", "content.html", "footer.html")
// 分别执行渲染三个模板
s1.ExecuteTemplate(os.Stdout, "header", nil)
s1.ExecuteTemplate(os.Stdout, "content", nil)
s1.ExecuteTemplate(os.Stdout, "footer", nil)
s1.Execute(os.Stdout, nil)
}