当前位置:网站首页>标准库template学习入门原创
标准库template学习入门原创
2022-06-12 18:26:00 【二丫讲梵】
html/template包实现了数据驱动的模板,用于生成可防止代码注入的安全的HTML内容。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用html/template这个包。
1,模板与渲染
在一些前后端不分离的Web架构中,我们通常需要在后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。
我们这里说的模板可以理解为事先定义好的HTML文档文件,模板渲染的作用机制可以简单理解为文本替换操作–使用相应的数据去替换HTML文档中事先准备好的标记。
很多编程语言的Web框架中都使用各种模板引擎,比如Python语言中Flask框架中使用的jinja2模板引擎。
1,Go语言的模板引擎
Go语言内置了文本模板引擎text/template和用于HTML文档的html/template。它们的作用机制可以简单归纳如下:
- 模板文件通常定义为
.tmpl和.tpl为后缀(也可以使用其他的后缀),必须使用UTF8编码。 - 模板文件中使用
{{和}}包裹和标识需要传入的数据。 - 传给模板这样的数据就可以通过点号(
.)来访问,如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。 - 除
{{和}}包裹的内容外,其他内容均不做修改原样输出。
2,模板引擎的使用
Go语言模板引擎的使用可以分为三部分:定义模板文件、解析模板文件和模板渲染.
1,定义模板文件
其中,定义模板文件时需要我们按照相关语法规则去编写,后文会详细介绍。
2,解析模板文件
上面定义好了模板文件之后,可以使用下面的常用方法去解析模板文件,得到模板对象:
func (t *Template) Parse(src string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)当然,你也可以使用func New(name string) *Template函数创建一个名为name的模板,然后对其调用上面的方法去解析模板字符串或模板文件。
3,模板渲染
渲染模板简单来说就是使用数据去填充模板,当然实际上可能会复杂很多。
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error4,基本示例
1,定义模板文件
我们按照Go模板语法定义一个hello.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>Hello</title>
</head>
<body>
<p>Hello {{.}}</p>
</body>
</html>2,解析和渲染模板文件
然后我们创建一个main.go文件,在其中写下HTTP server端代码如下:
// main.go
func sayHello(w http.ResponseWriter, r *http.Request) {
// 解析指定文件生成模板对象
tmpl, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
// 利用给定数据渲染模板,并将结果写入w
tmpl.Execute(w, "沙河小王子")
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("HTTP server failed,err:", err)
return
}
}将上面的main.go文件编译执行,然后使用浏览器访问http://127.0.0.1:9090就能看到页面上显示了“Hello 沙河小王子”。 这就是一个最简单的模板渲染的示例,Go语言模板引擎详细用法请往下阅读。
2,模板语法
1,{{.}}
模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象。
当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如:
// main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
type User struct {
Name string
Gender string
Age int
}
func sayHello(w http.ResponseWriter, r *http.Request) {
//定义模板
//解析模板
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println("parse file failed, err", err)
return
}
//渲染模板
//u1 := User{
// Name: "eryajf",
// Gender: "男",
// Age: 10,
//}
//通过结构体传递的话,字段名称首字母必须大写才能访问到
//而通过map的形式,则不需要首字母大写,因为是直接通过key访问的
m1 := map[string]interface{}{
"Name": "小明",
"Gender": "男",
"Age": 20,
}
t.Execute(w, m1) // 此处传递结构体,或者map都是可以的
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Println("Http server start failed, err", err)
return
}
}模板文件hello.tmpl内容如下:
<html lang="zh=CN">
<head>
<title>Hello</title>
</head>
<body>
<p>姓名: {{ .Name }} </p>
<p>性别: {{ .Gender }} </p>
<p>年龄: {{ .Age }} </p>
</body>
</html>同理,当我们传入的变量是map时,也可以在模板文件中通过.根据key来取值。
2,注释
{{/* a comment */}}
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。3,pipeline
pipeline是指产生数据的操作。比如{{.}}、{{.Name}}等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
**注意:**并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。
4,变量
我们还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下:
$obj := {{.}}其中$obj是变量的名字,在后续的代码中就可以使用该变量了。
5,移除空格
有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。
例如:
{{- .Name -}}注意:-要紧挨{{和}},同时与模板值之间需要使用空格分隔。
6,条件判断
Go模板语法中的条件判断有以下几种:
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}7,range
Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。
{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为0,不会有任何输出
{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为0,则会执行T0。8,with
{{with pipeline}} T1 {{end}}
如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。
{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。9,预定义函数
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用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。
这个函数在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,模板执行会中断并返回给调用模板执行者该错误;10,比较函数
布尔函数会将任何类型的零值视为假,其余视为真。
下面是定义为函数的二元比较运算的集合:
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”)。但是,整数和浮点数不能互相比较。
11,自定义函数
Go的模板支持自定义函数。
main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func f1(w http.ResponseWriter, r *http.Request) {
// 定义一个函数 kua
k := func(name string) (string, error) {
return name + "年轻又帅气!", nil
}
t := template.New("f.tmpl") // 创建一个模板对象,模板名字要与下边解析的模板文件对应上
// 告诉模板引擎,我现在定义了一个kua这个函数,这一步要在解析模板之前
t.Funcs(template.FuncMap{
"kua99": k,
})
_, err := t.ParseFiles("f.tmpl")
if err != nil {
fmt.Printf("parse template file failed, err:%v\n", err)
return
}
name := "eryajf"
// 渲染模板
t.Execute(w, name)
}
func demo1(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("t.tmpl", "ul.tmpl") //注意:被嵌套的模板应该放在后边
if err != nil {
fmt.Printf("parse template failed, err:%v\n", err)
return
}
//渲染模板
name := "二丫讲梵"
t.Execute(w, name)
}
func main() {
http.HandleFunc("/", f1)
http.HandleFunc("/tmpl", demo1)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Println("Http server start failed, err", err)
return
}
}f.tmpl
<html lang="zh=CN">
<head>
<title>自定义函数</title>
</head>
<body>
<p> {{ kua99 . }} </p>
</body>
</html>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"}}
</body>
<p> 你好 {{ . }} </p>
</html>
{{ define "ol.tmpl"}}
<ol>
<li>吃饭</li>
<li>睡觉</li>
<li>打豆豆</li>
</ol>
{{end}}ul.tmpl
<ul>
<li>注释</li>
<li>日志</li>
<li>测试</li>
</ul>我们可以在模板文件hello.tmpl中按照如下方式使用我们自定义的kua函数了。
{{kua .Name}}12,嵌套template
我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。
举个例子: 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>
{{template "ol.tmpl"}}
</body>
</html>
{{ define "ol.tmpl"}}
<ol>
<li>吃饭</li>
<li>睡觉</li>
<li>打豆豆</li>
</ol>
{{end}}ul.tmpl文件内容如下:
<ul>
<li>注释</li>
<li>日志</li>
<li>测试</li>
</ul>我们注册一个templDemo路由处理函数.
http.HandleFunc("/tmpl", tmplDemo)tmplDemo函数的具体内容如下:
func tmplDemo(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfo{
Name: "小王子",
Gender: "男",
Age: 18,
}
tmpl.Execute(w, user)
}注意:在解析模板时,被嵌套的模板一定要在后面解析,例如上面的示例中t.tmpl模板中嵌套了ul.tmpl,所以ul.tmpl要在t.tmpl后进行解析。
13,block
{{block "name" pipeline}} T1 {{end}}block是定义模板{{define "name"}} T1 {{end}}和执行{{template "name" pipeline}}缩写,典型的用法是定义一组根模板,然后通过在其中重新定义块模板进行自定义。
定义一个根模板templates/base.tmpl,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Go Templates</title>
</head>
<body>
<div class="container-fluid">
{{block "content" . }}{{end}}
</div>
</body>
</html>然后定义一个templates/index.tmpl,”继承”base.tmpl:
{{template "base.tmpl"}}
{{define "content"}}
<div>Hello world!</div>
{{end}}然后使用template.ParseGlob按照正则匹配规则解析模板文件,然后通过ExecuteTemplate渲染指定的模板:
func index(w http.ResponseWriter, r *http.Request){
tmpl, err := template.ParseGlob("templates/*.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
err = tmpl.ExecuteTemplate(w, "index.tmpl", nil)
if err != nil {
fmt.Println("render template failed, err:", err)
return
}
}如果我们的模板名称冲突了,例如不同业务线下都定义了一个index.tmpl模板,我们可以通过下面两种方法来解决。
- 在模板文件开头使用
{{define 模板名}}语句显式的为模板命名。 - 可以把模板文件存放在
templates文件夹下面的不同目录中,然后使用template.ParseGlob("templates/**/*.tmpl")解析模板。
14,修改默认的标识符
Go标准库的模板引擎使用的花括号{{和}}作为标识,而许多前端框架(如Vue和 AngularJS)也使用{{和}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:
template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")3,text/template与html/tempalte的区别
html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。
例如,我定义下面的模板文件:
<!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>Hello</title>
</head>
<body>
{{.str1}}
{{.str2 | safe }}
</body>
</html>这个时候传入一段JS代码并使用html/template去渲染该文件,会在页面上显示出转义后的JS内容。 alert('嘿嘿嘿') 这就是html/template为我们做的事。
但是在某些场景下,我们如果相信用户输入的内容,不想转义的话,可以自行编写一个safe函数,手动返回一个template.HTML类型的内容。示例如下:
package main
import (
"fmt"
"html/template"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
t, err := template.New("index.tmpl").Funcs(template.FuncMap{
"safe": func(s string) template.HTML {
return template.HTML(s)
},
}).ParseFiles("./index.tmpl")
//t, err := template.ParseFiles("./index.tmpl")
if err != nil {
fmt.Printf("parse template failed, err:%v\n", err)
return
}
str1 := "<script>alert(123);</script>"
str2 := "<a href='http://eryajf.net'>二丫讲梵的博客</a>"
t.Execute(w, map[string]string{
"str1": str1,
"str2": str2,
})
}
func main() {
http.HandleFunc("/index", index)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Println("Http server start failed, err", err)
return
}
}这样我们只需要在模板文件不需要转义的内容后面使用我们定义好的safe函数就可以了。
{{ . | safe }}4,加载静态文件
先创建两个静态文件,模拟项目结构。
templates/posts/index.tmpl
{{define "posts/index.tmpl"}}
<!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>posts/index</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}templates/posts/index.tmpl
{{define "users/index.tmpl"}}
<!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">
<link rel="stylesheet" href="/xxx/index.css">
<title>users/index</title>
</head>
<body>
{{ .title | safe }}
</body>
<script src="/xxx/index.js"></script>
</html>
{{end}}然后可以统一引用,一个 r.LoadHTMLGlob("templates/**/*"),一个 r.Static("xxx", "static")
package main
import (
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)
func main() {
r := gin.Default()
// 加载静态文件
r.Static("xxx", "static")
// gin 框架中给模板添加自定义函数
r.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
})
//r.LoadHTMLFiles("templates/index.tmpl") //模板解析
r.LoadHTMLGlob("templates/**/*")
r.GET("/posts/index", func(c *gin.Context) {
// HTTP请求
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ //模板渲染
"title": "posts page",
})
})
r.GET("/users/index", func(c *gin.Context) {
// HTTP请求
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ //模板渲染
"title": "<a href='http://eryajf.net'>二丫讲梵的博客</a>",
})
})
r.GET("home", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
r.Run(":9090") //启动server
}边栏推荐
- Review of MySQL (V): Joint table query and sub query
- 快速复制浏览器F12中的请求到Postman/或者生成相关语言的对应代码
- C语言学习——数据在内存中的存储
- Getting started with the go language is simple: read / write lock
- Leetcode 674 longest incrementing substring
- 间隔两个月,我的第二次上榜纪念日【2022.6.2】
- Is it safe to open an account in flush
- Leetcode491 increasing subsequence
- 静态内存分配和动态内存分配小结
- A story on the cloud of the Centennial Olympic Games belonging to Alibaba cloud video cloud
猜你喜欢

Review of MySQL (I): go deep into MySQL

基于halcon—缺陷检测常用方法与示例总结
![Free measurement of rectangular card [manual drawing ROI] Based on Halcon](/img/c5/d9109ed4024aff521e1788c63bff4e.png)
Free measurement of rectangular card [manual drawing ROI] Based on Halcon

Gospel of audio and video developers, rapid integration of AI dubbing capability

Remote gadget putty (Alibaba cloud mirror station address sharing)

Shenzhen has been shut down for 7 days since March 14. Home office experience | community essay solicitation

General differences between SQL server versions released by Microsoft in different periods so far, for reference

Review of MySQL (3): query operation

有源差分晶振原理圖以及LV-PECL、LVDS、HCSL區別

VirtualLab基础实验教程-5.泊松亮斑
随机推荐
Gd32f4xx communicates with electric energy meter conforming to dlt645_ two
MIPS general purpose register + instruction
leetcode 647. 回文子串
网盘和对象云存储管理之磁盘映射工具比较
静态内存分配和动态内存分配小结
Problems that the sap Spartacus e-commerce cloud UI shipping method does not display in the unit test environment
VirtualLab基础实验教程-4.单缝衍射
MYSQL:Expression #4 of SELECT list is not in GROUP BY clause and contains nonaggregated column
在思科模拟器Cisco Packet Tracer实现自反ACL
leetcode 647. Palindrome substring
Solution to the problem that the anaconda navigator card logo cannot be opened and the card will flash back - replace the alicloud image source
用grep awk提取字符串
Free measurement of rectangular card [manual drawing ROI] Based on Halcon
深圳3月14日起全市停工停业7天居家办公心得|社区征文
Interior design style type, rendering 100 invitation code [1a12]
Remote gadget putty (Alibaba cloud mirror station address sharing)
Write a select based concurrent server
01-复杂度
Can tonghuashun open an account? Can tonghuashun directly open the security of securities companies on the app
Pytest automated testing framework (II)