这里说的是go 语言中自带的包html/template 里的一些基本操作
当然还有text/template ,也是个模板包,但是这个并不是网页安全的,如果 往模板上传的是标签,它是会将标签转成html格式的,这一般来说是不允许的。
在本博客中所有的例子都是经过go build,打包成可执行文件执行的,如果使用goland启动标志其中会导致找不到包而报错。如果过真的想用启动标志启动,建议改下解析文件模板的路径。 例子码云地址:go-templateLearn
## 简单使用
要使用模板需要分三个步骤,分别是定义、解析和渲染,下面咱一步一步来
-
定义 创建一个.tmpl 或是.tpl 文件,在goland中第一次创建这种类型的文件他会让你选用什么文件的格式去提示这类文件,这时候选择 go template files。
-
解析 t, err := template.ParseFiles("./test.tmpl")
将刚才定义的模板文件解析到程序中,上方调用的方法是解析多个文件
解析方法
func ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(nil, readFileOS, filenames...)
}
func ParseGlob(pattern string) (*Template, error) {
return parseGlob(nil, pattern)
}
func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
return parseFS(nil, fs, patterns)
}
-
渲染 mp := map[string]interface{}{
"name": "张三",
"sex": "男",
}
err = t.Execute(w, mp)
将数据渲染到刚解析的模板中
func (t *Template) Execute(wr io.Writer, data interface{}) error {
if err := t.escape(); err != nil {
return err
}
return t.text.Execute(wr, data)
}
另外一种渲染方法,指定模板文件进行渲染,适用于有好多解析文件在一起时使用 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
tmpl, err := t.lookupAndEscapeTemplate(name)
if err != nil {
return err
}
return tmpl.text.Execute(wr, data)
}
-
main.go package main
import (
"fmt"
"html/template"
"net/http"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
t, err := template.ParseFiles("./test.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
mp := map[string]interface{}{
"name": "张三",
"sex": "男",
}
err = t.Execute(w, mp)
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
注释 注释使用的符号为{{/* */}},支持多行注释,如 {{/*
注释内容
*/}}
变量的使用 在模板中使用变量是利用 $ 符号 赋值 {{ $obj := 数据 }}
声明变量之后就可以在模板文件中使用了。 例子: test.tmpl <!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>变量使用</title>
</head>
<body>
MSG :{{ . }}<br>
姓名 :{{ .Name }}<br>
性别 : {{ .Sex }}<br>
{{/* 使用变量 */}}
<div>
{{ $num := "123" }}
年岁 :{{ $num }}
</div>
</body>
</html>
main.go package main
import (
"fmt"
"html/template"
"net/http"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
t, err := template.ParseFiles("./test.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
err = t.Execute(w, struct {
Name string
Sex string
}{
Name: "张三",
Sex: "女",
})
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
? 这里要注意一点,就是当传给模板的数据为结构体时,根据go语言的特性,属性名小写的属性外界将获取不到值。
判断与清楚空白符操作
判断
在模板中的判断语句写法同go语言类似
{{ if [比较函数] 变量 [比较对象] }}
如果为真要执行的语句
{{ end }}
如果是只有变量的话,就判断变量是否存在值,如果存在就执行。
也可以使用 if … else … 语句,或if … else if … 语句,如
{{ if [比较函数] 变量 [比较对象] }}
{{ else }}
{{ end }}
{{ if [比较函数] 变量 [比较对象] }}
{{ else if [比较函数] 变量 [比较对象]}}
{{ end }}
去空白字符
使用
清楚变量左右两侧的空白符号,当然也可以只清除一侧,只需要将不需要清除的一侧的 - 舍去
{{- -}}
例子:
test.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>判断与清楚空白符操作</title>
</head>
<body>
MSG :{{ . }}<br>
姓名 :{{ .Name }}<br>
性别 : {{ .Sex }}<br>
{{ $num := .Age }}
年龄 :{{ $num }}
{{/* 判断使用 注意,要先写条件之后跟上比较对象*/}}
{{/* 比较函数
eq == eq可以进行多个值比较 如 eq n1 n2 n3 ,就会拿n1 分别跟n2,n3比较
ne !=
lt <
le <=
gt >
ge >=
*/}}
<div>
{{ if lt $num 18 }}
好好吃饭
{{ else if ge $num 18 }}
别在熬夜了
{{end}}
</div>
{{/* {{- -}} 取出空白符的符号使用*/}}
{{ $num }} = {{ $num }}<br>
{{/* 取出空白符,让左侧或右侧能与其他的文本贴贴*/}}
{{ $num -}} = {{- $num }}
</body>
</html>
main.go
package main
import (
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
t, err := template.ParseFiles("./test.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
err = t.Execute(w, struct {
Name string
Sex string
Age int
}{
Name: " 张三 ",
Sex: "女",
Age: 20,
})
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
root := filepath.Dir(os.Args[0])
fmt.Println(root)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
循环、with和与预定义函数的使用
循环
使用range 关键字进行遍历
这个变量只能是数组、切片、map或者通道
{{ range 变量 }}
{{ end }}
{{ range $index,$valuse = 变量 }}
{{ end }}
在range 中也可以使用else语句,如果所遍历的这个变量长度为0,则执行else语句
{{ range 变量 }}
{{ else }}
{{ end }}
with
with 的作用为重新定义 . 所代表的数据,这个重新定义有一个范围,只有在范围内 . 才代表with 重新定义的那个数据
{{ with 变量 }}
在这中间 . 都将被替换为变量的数据
{{ end }}
当赋值的变量为空时,可以使用 else 语句来检测 . 有没有被重新赋值
{{ with 变量 }}
{{ else }}
如果变量为空则执行这里的语句
{{ end }}
预定义函数
模板中的预定义函数有:
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。
这个函数在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,模板执行会中断并返回给调用模板执行者该错误;
例子:
test.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>循环,with和预定义函数的使用</title>
</head>
<body>
{{/* 循环 */}}
{{ range $index,$v1 := . }}
下标:{{ $index }}
姓名:{{ $v1 }}
<br>
{{ end }}
<hr>
{{/* with 更改点的值*/}}
开始时:{{ . }}<br>
{{ with "斗地主研讨会"}}
转换后:{{ . }}
{{ end }}
<hr>
{{/* 预定义函数 */}}
{{/*
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。
这个函数在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,模板执行会中断并返回给调用模板执行者该错误;
*/}}
研讨会人数:{{ len . }}
</body>
</html>
main.go
package main
import (
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
t, err := template.ParseFiles("./test.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
strings := []string{"张安","潘凤","李翔"}
err = t.Execute(w,strings)
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
root := filepath.Dir(os.Args[0])
fmt.Println(root)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
自定义函数
若是觉得模板中的预定义函数不够用,这时候就需要定义自己的函数了。
我们一般不在模板中定义自己的函数,而是在渲染模板之前给定自己所定义的函数,这将使用了Fancs方法。
scold := func(a string) string {
return a + "TNND"
}
t, err := template.New("test").Funcs(template.FuncMap{"scold" : scold}).ParseFiles("./test.tmpl")
Funcs方法:
func (t *Template) Funcs(funcMap FuncMap) *Template {
t.text.Funcs(template.FuncMap(funcMap))
return t
}
type FuncMap map[string]interface{}
在模板中使用自定义函数时,要用到管道的方法
{{ a | b }}
这种写法的意思是将 a 的输出通过管道再作为 b 的输入,最后呈现出来的是 b 的输出
例子:
test.tmpl
{{ define "test" }}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>自定义函数</title>
</head>
<body>
{{/*
{{ a | b }}
这种写法的意思是将 a 的输出通过管道再作为 b 的输入,最后呈现出来的是 b 的输出
*/}}
{{ . | scold }}
</body>
</html>
{{end}}
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
scold := func(a string) string {
return a + "TNND"
}
t, err := template.New("test").Funcs(template.FuncMap{"scold" : scold}).ParseFiles("./test.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
err = t.Execute(w, "为什么不喝,")
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
嵌套
就是在一个模板中嵌套另外的一个模板。
需要使用的语法为
在需要嵌套的地方
{{ template 模板名 . }}
其中,这个 . 是在模板中传递数据用的
例子:
test.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>嵌套</title>
</head>
<body>
ul :<br>
{{ template "ul.tmpl" . }}
<hr>
ol :<br>
{{ template "ol.tmpl" . }}
</body>
</html>
ol.tmpl
<ol>
<li>{{ .name }}</li>
<li>{{ .sex }}</li>
</ol>
ul.tmpl
<ul>
<li>{{ .name }}</li>
<li>{{ .sex }}</li>
</ul>
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func nest(w http.ResponseWriter,r *http.Request) {
t, err := template.ParseFiles("./test.tmpl","./ul.tmpl","./ol.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
mp := map[string]interface{}{
"name": "张大炮",
"sex": "女",
}
err = t.Execute(w, mp)
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",nest)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
填空 block
相当于继承,一个网页项目会有一些公共的页面信息,为了不每个页面都将这些信息写上一边,就要有根模板,这时候其他的页面就需要继承根模板。
使用block
将数据传递给该模板名的模板
{{ block 模板名 数据}}
{{ end }}
在使用时一定要引入根模板
用 . 来接受根模板传来的所有数据
{{ template 跟模板名 .}}
例子:
test.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>填空</title>
</head>
<body>
<div>
<p style="text-align: center">
{{ block "content" .}}
{{ end }}
</p>
</div>
</body>
</html>
content.tmpl
{{ template "test.tmpl" .}}
{{/*
一个项目里面肯定要有好些个功能,如果每一个功能都有一些相同作用的文件,
这样就很难保证这些文件不会出现重复的问题,
有两个解决方案,
1. 在每一个模板上方定义一个名字
2. 创建一个包,包里分层放,不同层代表着不同的功能 然后使用template.ParseGlob() 正则解析
*/}}
{{ define "content" }}
{{ . }}
{{end}}
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
t, err := template.ParseFiles("./test.tmpl","./content.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
err = t.ExecuteTemplate(w,"content", "这都是大棚的瓜")
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
安全测试
就像开头所说,html/template这个包传递给的模板的数据会进行安全处理。
测试:
test.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>安全测试</title>
</head>
<body>
{{/*并不会出现执行标签的现象*/}}
弹窗:{{ .msg }}<br>
链接1:{{ .a }}<br>
链接2:{{ .a | trust }}<br>
</body>
</html>
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
t, err := template.New("test.tmpl").Funcs(template.FuncMap{
"trust": func(s string) template.HTML {
return template.HTML(s)
},
}).ParseFiles("./test.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
m := map[string]interface{}{
"msg":"<script>alert('张三撺掇着李四去王五家打了赵六')</script>",
"a": "<a href='https://blog.csdn.net/weixin_52025712'>本人博客</a>",
}
err = t.Execute(w, m)
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
修改默认标识符
模板中默认标识符 {{ }} 可能会与其他语言中的表示符冲突,这时候我们就要重新定义默认标识符,需要使用Delims 方法
t, err := template.New("test.tmpl").Delims("[","]").ParseFiles("./test.tmpl")
Delims:
func (t *Template) Delims(left, right string) *Template {
t.text.Delims(left, right)
return t
}
例子:
test.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>修改默认标识符</title>
</head>
<body>
[ . ]
</body>
</html>
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func sayHello(w http.ResponseWriter,r *http.Request) {
t, err := template.New("test.tmpl").Delims("[","]").ParseFiles("./test.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
err = t.Execute(w, "hello")
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
func main() {
http.HandleFunc("/",sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
一个简单的表单传值的例子
get.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>表单传值</title>
</head>
<body>
<form action="/" method="post">
<label>
<input type="text" name="name">
</label><br>
<label>
<input type="text" name="age">
</label><br>
<label>
<input type="radio" name="sex" value="男">
</label> 男<br>
<label>
<input type="radio" name="sex" value="女">
</label> 女<br>
<button type="submit">提交</button>
</form>
</body>
</html>
post.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>接受表单传过来的值</title>
</head>
<body>
姓名:{{ .name }}<br>
性别:{{ .sex }}<br>
年龄:{{ .age }}
</body>
</html>
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func form(w http.ResponseWriter,r *http.Request) {
switch r.Method {
case "GET":
t, err := template.ParseFiles("./get.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
err = t.Execute(w, nil)
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
case "POST":
t, err := template.ParseFiles("./post.tmpl")
if err != nil {
fmt.Printf("parse file failed err := %v",err)
}
name := r.FormValue("name")
sex := r.FormValue("sex")
age := r.FormValue("age")
m := map[string]interface{}{
"name":name,
"sex":sex,
"age":age,
}
err = t.Execute(w, m)
if err != nil {
fmt.Printf("execute file failed err := %v",err)
}
}
}
func main() {
http.HandleFunc("/",form)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("listen address failed err = %v",err)
}
}
参考文章:
-
李文周的博客 -
xyz098的简书
希望大家能多去支持一下上面的两位大佬
最后再向大家推荐一首今天在单曲循环的歌遇见 西安话版 吴昊晨
|