Go语言学习笔记【18】 Go语言常见库:html/template
【声明】
非完全原创,部分内容来自于学习其他人的理论和B站视频。如果有侵权,请联系我,可以立即删除掉。
一、html/template
主要参考文档:
(1)Go语言标准库之http/template
(2)Go模板template用法详解
template包(html/template)实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。本包提供了和text/template包相同的接口,无论何时当输出是HTML的时候都应使用本包
在一些前后端不分离的Web架构中,我们通常需要在后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。
我们这里说的模板可以理解为事先定义好的HTML文档文件,模板渲染的作用机制可以简单理解为文本替换操作–使用相应的数据去替换HTML文档中事先准备好的标记
1、模板引擎
1.1、简介
Go语言内置了文本模板引擎text/template和用于HTML文档的html/template。它们的作用机制可以简单归纳如下:
- 模板文件通常定义为
.tmpl
和.tpl
为后缀(也可以使用其他的后缀),必须使用UTF8编码
。 - 模板文件中使用
{{
和}}
包裹和标识需要传入的数据。 - 传给模板这样的数据就可以通过点号
.
来访问,如果数据是复杂类型的数据,可以通过{{.FieldName}}
来访问它的字段。 - 除
{{
和}}
包裹的内容外,其他内容均不做修改原样输出
1.2、使用流程
分为三部分:定义模板文件、解析模板文件和模板渲染
1.2.1、定义模板文件
定义一个html
文件即可,后缀名成可以设定为 tmpl
或者 tpl
,不过一般使用 tmpl
作为模板文件后缀
1.2.2、解析模板文件
上面定义好了模板文件之后,可以使用下面的常用方法去解析模板文件,得到模板对象
func New(name string) *Template
//创建一个名为name的模板func (t *Template) Funcs(funcMap FuncMap) *Template
//Funcs方法向模板t的函数字典里加入参数funcMap内的键值对。
//如果funcMap某个键值对的值不是函数类型或者返回值不符合要求会panic。
//但是,可以对t函数列表的成员进行重写。方法返回t以便进行链式调用func (t *Template) Parse(src string) (*Template, error)
//Parse方法将字符串text解析为模板。嵌套定义的模板会关联到最顶层的t。
//Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。
//如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;
//如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板func ParseFiles(filenames ...string) (*Template, error)
//ParseFiles函数创建一个模板并解析filenames指定的文件里的模板定义。
//返回的模板的名字是第一个文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。
//至少要提供一个文件。如果发生错误,会停止解析并返回nil。func ParseGlob(pattern string) (*Template, error)
//ParseGlob创建一个模板并解析匹配pattern的文件(参见glob规则)里的模板定义。
//返回的模板的名字是第一个匹配的文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要存在一个匹配的文件。
1.2.3、模板渲染
渲染模板简单来说就是使用数据去填充模板
func (t *Template) Execute(wr io.Writer, data interface{}) error
//Execute方法将解析好的模板应用到data上,并将输出写入wr。
//如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。模板可以安全的并发执行。func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
//ExecuteTemplate方法类似Execute,但是使用名为name的t关联的模板产生输出
1.3、使用示例
1.3.1、定义index.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>Person</title>
</head>
<body><p> Id {{.p.Id}}</p><p> Name {{.p.Name}}</p><p> Age {{.p.Age}}</p><p> Address {{.p.Addr}}</p>
</body>
</html>
1.3.2、代码解析
type Person struct {Id intName stringAge intAddr string
}func main() {http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {// 根据模板文件生成模板对象tmpl, err := template.ParseFiles("index.tmpl")if err != nil {fmt.Println("parse template failed, err = ", err)return}// 构造数据渲染模板,将结果写入到wyufei := Person{Id: 20220821,Name: "YuFei",Age: 26,Addr: "WuHan",}//也可写为/*yufei = map[string]interface{}{"Id": 20220821,"Name": "YuFei","Age": 26,"Addr": "WuHan",}*/err = tmpl.Execute(w, map[string]interface{}{"p": yufei,})if err != nil {fmt.Println("execute template failed, err = ", err)return}})if err := http.ListenAndServe(":8088", nil); err != nil {fmt.Println("http listen and accept failed, err = ", err)return}
}
1.3.3、运行结果
2、基础语法
2.1、{{.}}
模板语法都包含在{{
和}}
中间,其中{{.}}
中的点表示当前对象。当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段
<body><p> Id {{.p.Id}}</p><p> Name {{.p.Name}}</p><p> Age {{.p.Age}}</p><p> Address {{.p.Addr}}</p>
</body>
2.2、注释
{{/* 需要注释的内容 */}}
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止
2.3、pipeline
pipeline
是指产生数据的操作。比如{{.}}
、{{.Name}}
等。Go的模板语法中支持使用管道符号|
链接多个命令,用法和unix
下的管道类似:|
前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
注意:并不是只有使用了|
才是pipeline
。Go的模板语法中,pipeline
的概念是传递数据,只要能产生数据的,都是pipeline。
例如,下面的(len "output")
是pipeline
,它整体先运行;{{.}}
是pipeline
,它的结果将传递给printf,且传递的参数位置是"abcd"
之后
{{println (len "output")}}
{{.}} | printf "%s\n" "abcd"
2.4、变量
可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果
{{ $v := 66 }}
{{ $id := .p.Id}}
2.5、移除空格
template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。使用{{-
语法去除模板内容左侧的所有空白符号, 使用-}}
去除模板内容右侧的所有空白符号。语法:{{- .Name -}}
。示例:
{{23 -}} < {{- 45}} => 23<45
注意:-
要紧挨{{
和}}
,同时与模板值之间需要使用空格分隔。
2.6、条件判断
有以下几种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}}
2.7、range...end
迭代
Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。
{{range pipeline}} T1 {{end}}
//如果pipeline的值其长度为0,不会有任何输出{{range pipeline}} T1 {{else}} T0 {{end}}
//如果pipeline的值其长度为0,则会执行T0。
range
的参数部分是pipeline
,所以在迭代的过程中是可以进行赋值的。但有两种赋值情况:
{{range $value := .}}
{{range $key,$value := .}}
如果range
中只赋值给一个变量,则这个变量是当前正在迭代元素的值。
如果赋值给两个变量,则第一个变量是索引值(map/slice是数值,map是key),第二个变量是当前正在迭代元素的值。
2.8、with...end
with用来设置"."的值。两种格式
{{with pipeline}} T1 {{end}}
//如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。{{with pipeline}} T1 {{else}} T0 {{end}}
//如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。
如:{{with "xx"}}{{println .}}{{end}}
。代码将输出xx,因为"."
已经设置为"xx"
2.9、内置函数
2.9.1、常见的内置函数
- 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、printf、println
分别等价于fmt包中的Sprint、Sprintf、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,模板执行会中断并返回给调用模板执行者该错误;
2.9.2、内置的比较函数
eq 如果arg1 == arg2则返回真
ne 如果arg1 != arg2则返回真
lt 如果arg1 < arg2则返回真
le 如果arg1 <= arg2则返回真
gt 如果arg1 > arg2则返回真
ge 如果arg1 >= arg2则返回真
eq
函数,支持多个参数:eq arg1 arg2 arg3 arg4...
它们都和第一个参数arg1进行比较。它等价于:arg1==arg2 || arg1==arg3 || arg1==arg4
2.10、修改默认的标识符
Go标准库的模板引擎使用的花括号{{
和}}
作为标识,而许多前端框架(如Vue和 AngularJS)也使用{{
和}}
作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。
func (t *Template) Delims(left, right string) *Template
//Delims方法用于设置action的分界字符串,应用于之后的Parse、ParseFiles、ParseGlob方法。
//嵌套模板定义会继承这种分界符设置。空字符串分界符表示相应的默认分界符:{{或}}。返回值就是t,以便进行链式调用。
例如,将默认的标识符{{
和}}
修改为{[
和]}
:
template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")
2.11、使用示例
2.11.1、index.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>Person</title>
</head>
<body>{{/* 打印基本信息 */}}<p> Id: {{.Id}}</p><p> Name: {{.Name}}</p><p> Age: {{.Age}}</p><p> Address: {{.Addr}}</p>{{/* .Name作为自定义函数fly的参数 */}}<p> Fly: {{fly .Name}}</p><hr>{{ $age := .Age }}{{ if lt $age 20 }}好好学习, 天天向上{{ else if and (ge $age 20) (le $age 50) }}努力赚钱, 实现自由{{ else }}退休生活, 颐养天年{{ end }}<hr>{{ with .hobby}}{{/* with...end的范围内, '.'代表的就是'.hobby' */}}{{ range $i, $hobby := . }}<p>{{$i}} - {{$hobby}}</p>{{ else }}no hobby{{ end }}{{ end }}
</body>
</html>
2.11.2、加载模板的代码
import ("fmt""html/template""io/ioutil""net/http"
)func main() {http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {//读取模板中的内容tmplbyte, err := ioutil.ReadFile("./index.tmpl")if err != nil {fmt.Println("ioutil.ReadFile index.tmpl failed, err = ", err)return}//自定义一个函数fly := func(name string) (string, error) {return name + " can fly", nil}//New创建模板对象,Funcs在这个对象里面进行注册函数,一定要在解析模板之前进行注册//采用链式操作在Parse之前调用Funcs添加自定义的fly函数tmpl, err := template.New("go").Funcs(template.FuncMap{"fly": fly}).Parse(string(tmplbyte))if err != nil {fmt.Println("create template failed, err = ", err)return}yufei := map[string]interface{}{"Id": 20220821,"Name": "YuFei","Age": 26,"Addr": "WuHan","hobby": []string{"乒乓球", "羽毛球", "排球"},}tmpl.Execute(w, yufei)})if err := http.ListenAndServe(":8088", nil); err != nil {fmt.Println("http listen and accept failed, err = ", err)return}
}
2.11.3、运行结果
3、模板嵌套
3.1、定义模板文件
3.1.1、``index.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<body><h1>测试嵌套template语法</h1><hr>{{template "ul.tmpl"}}<hr>{{template "ol.tmpl"}}
</body>
</html>{{/* 通过define定义一个模板, 上面可直接调用 */}}
{{ define "ol.tmpl"}}
{{/* <ol> 标签与 <li> 标签一起使用, 创建有序列表 */}}
<ol><li>王者</li><li>吃鸡</li><li>阴阳师</li>
</ol>
{{end}}
3.1.2、ul.tmpl
{{/* <ul> 标签与 <li> 标签一起使用,创建无序列表 */}}
<ul><li>sleep</li><li>eat</li><li>drink</li>
</ul>
3.2、解析嵌套模板
注意:在解析模板时,被嵌套的模板一定要在后面解析
func main() {http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {//解析模板, 被嵌套的模板一定要在后面解析tmpl, err := template.ParseFiles("./index.tmpl", "./ul.tmpl")if err != nil {fmt.Println("create template failed, err = ", err)return}//模板渲染tmpl.Execute(w, nil)})if err := http.ListenAndServe(":8088", nil); err != nil {fmt.Println("http listen and accept failed, err = ", err)return}
}
3.3、运行结果
4、block
(模板继承)
根据官方文档的解释:block等价于define定义一个名为name的模板,并在"有需要"的地方执行这个模板,执行时将"."设置为pipeline的值。
但应该注意,block的第一个动作是执行名为name的模板,如果不存在,则在此处自动定义这个模板,并执行这个临时定义的模板。换句话说,block可以认为是设置一个默认模板,即父类模板
例如:{{block "T1" .}} one {{end}}
。它先找到T1模板,如果T1存在,则执行找到的T1,如果没找到T1,则临时定义一个模板{{define "T1"}} one {{end}}
,并执行它。
在很多网页中,大体上的布局都差不多,因此可以将布局封装为一个默认模板,以供其他模板调用
4.1、示例格式
4.1.1、定义根模板
根模板templates/base.tmpl
,其中的{{block "content" . }}{{end}}
是子模板需要填充的内容
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>Go Templates</title>
</head>
<body>
<div class="container-fluid">{{block "content" . }}{{end}}
</div>
</body>
</html>
4.1.2、定义子模板
定义一个templates/index.tmpl
,”继承”base.tmpl
{{template "base.tmpl"}}{{define "content"}}<div>Hello world!</div>
{{end}}
4.1.3、解析和渲染模板
使用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”)解析模板。
4.2、网页布局模板的示例
4.2.1、定义根模板base.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>定义base模板</title><style>* {margin: 0;}.nav {height: 50px;width: 100%;position: fixed;top: 0;background-color:deepskyblue;}.main {margin-top: 50px;}.menu {width: 20%;height: 100%;position: fixed;left: 0;background-color: lightgrey;}.center {text-align: center;}</style>
</head>
<body>
<div class = "nav"></div>
<div class="main"><div class = "menu"></div><div class = "content center">{{/* 此处为子模板预留位置进行填充 */}}{{block "content" .}}{{end}}</div>
</div>
</body>
</html>
4.2.2、定义子模板index.tmpl
、home.tmpl
index.tmpl
{{/* 继承根模板, 需要使用`.`将数据接收过来 */}}
{{template "base.tmpl" .}}{{/* 重新定义块模板 */}}
{{define "content"}}<h1>这是index页面</h1><p>{{.}}</p>
{{end}}
home.tmpl
{{/* 继承根模板, 需要使用`.`将数据接收过来 */}}
{{template "base.tmpl" .}}{{/* 重新定义块模板 */}}
{{define "content"}}<h1>这是home页面</h1><h3>{{.}}</h3>
{{end}}
4.2.3、解析模板
func indexFunc(w http.ResponseWriter, r *http.Request) {//定义模板文件//解析模板tmpl, err := template.ParseGlob("./*.tmpl")if err != nil {fmt.Println("parse template failed, err = ", err)return}//渲染模板tmpl.ExecuteTemplate(w, "index.tmpl", "this is a index page")
}func homeFunc(w http.ResponseWriter, r *http.Request) {//定义模板文件//解析模板tmpl, err := template.ParseFiles("./base.tmpl", "./home.tmpl")if err != nil {fmt.Println("parse template failed, err = ", err)return}//渲染模板tmpl.ExecuteTemplate(w, "home.tmpl", "this is a home page")
}func main() {http.HandleFunc("/index", indexFunc)http.HandleFunc("/home", homeFunc)if err := http.ListenAndServe(":8088", nil); err != nil {fmt.Println("listen failed, err = ", err)return}
}
4.2.4、运行结果
Go语言学习笔记【18】 Go语言常见库:html/template相关推荐
- c语言注释语句执行吗,C语言学习笔记之C语言概念解析(附资料分享)每一个语句都必须以分号结尾但预处理命令函数头和花括号“}”之后不能加分号...
[[怪兽爱C语言]C语言学习笔记之C语言概念解析(附资料分享)]https://toutiao.com/group/6582429294901854728/?iid=15906422033&a ...
- C语言学习笔记---初始C语言006
C语言程序设计笔记---006 C语言初识关键字和宏定义 1.数据类型关键字 2.存储类型关键字 3.控制语句关键字 3.1.跳转结构关键字: 3.2.循环结构关键字: 3.3.分支结构关键字: 3. ...
- Dart 语言学习笔记(Dart语言完全指南)
Dart 语言学习笔记 Part 1:概览 Dart是一门纯OOP语言.所有的类都继承于Object类.null也是对象,变量仅仅存储对象的引用 Dart是强类型语言,即使Dart可以进行类型推断(使 ...
- C语言学习笔记---初始C语言001
C语言程序设计笔记---001 初识C语言 1.C语言的标准 2.C语言的特点 3.第一个C程序 ***/知识点汇总/*** 基于VS2019编译器展示一下 代码片: 4.自我介绍 初识C语言 1.前 ...
- C语言学习笔记:C语言的指针函数与函数指针??
前言 在C语言里函数.指针这两个词结合的顺序不同其意义也不同,即指针函数与函数指针的意义不同,这是什么情况呢?估计许多学习C语言的小伙伴已经感觉自己懵懵的了,别急且听小编来说个明白. 指针函数 指针函 ...
- C语言学习笔记之C语言简史(不看后悔系列)
文章目录 C语言简史 一.什么是C语言? 只要计算机体系架构不变,C语言就会长盛不衰. C语言简史 一.什么是C语言? 麻省理工学院,AT&T贝尔实验室,通用电气,三方大佬,准备为GE-645 ...
- [R语言学习笔记1] R语言for循环的使用
学习R语言的过程中,后期逐渐就会用循环语句来减少自己的重复工作.所以了解for循环,是必备技能之一. R语言中的for循环结构是: for (循环变量 in 序列向量){表达式1表达式2...} 要注 ...
- R语言学习笔记--《R语言实战》
文章目录 R语言基础 一.数据结构 1. 向量 2. 矩阵 3. 数组 4. 数据框 5.列表 二.数据输入 1.键盘输入 2.分隔符文本输入 (csv) 图形初阶 一.图形参数 1.符号和线条 2. ...
- c v语言 小数后20位,V语言学习笔记-30集成C代码库
集成C代码库 优势 V的代码库很多都直接调用C标准库函数来实现,对C标准库的依赖还是很重的 由于V代码编译后生成的是C代码,然后再调用C编译器编译成可执行文件 这样的机制决定了V语言可以很方便地调用C ...
- C语言学习笔记1——C语言程序
C语言简介: BCPL ->newB ->C ->UNIX ->Minux ->Linux ->gcc C语言诞生于1970-1973年,在肯·汤姆逊和丹尼斯·里奇 ...
最新文章
- PHP Mysql 网站迁移,Linux+PHP+MySql网站迁移配置
- nvcc gcc g++混合编译器编程
- 征途手游2新开区服务器维护多久,《征途2手游》开启新服“星火燎原”
- Nginx 与 Tomcat,Apache的区别
- Redhat(Linux)上的JBoss管理配置
- python抓取图片数字_Python OCR提取普通数字图形验证中的数字
- OSPF 常见错误与排查方法
- Eclipse SVN插件版本
- 查看hadoop版本
- 计算系数(多项式展开+快速幂)
- 《惢客创业日记》2019.01.23(周三) 太苦涩的人生也会让人麻木
- abi-dumper 理解
- OSChina 周一乱弹 —— 老夫聊发少年狂
- 专访腾讯安全王雷雷丨构建智能风控体系,护航私域营销安全
- 伯特兰·阿瑟·威廉·罗素
- 静态IP和动态IP有什么区别?
- IDEA添加Java类注释模版
- java继承1—上溯造型
- poi读取Excel时日期为数字 的解决方法
- 说一说ADI公司的DSP发展历程