【声明】

非完全原创,部分内容来自于学习其他人的理论和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.tmplhome.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相关推荐

  1. c语言注释语句执行吗,C语言学习笔记之C语言概念解析(附资料分享)每一个语句都必须以分号结尾但预处理命令函数头和花括号“}”之后不能加分号...

    [[怪兽爱C语言]C语言学习笔记之C语言概念解析(附资料分享)]https://toutiao.com/group/6582429294901854728/?iid=15906422033&a ...

  2. C语言学习笔记---初始C语言006

    C语言程序设计笔记---006 C语言初识关键字和宏定义 1.数据类型关键字 2.存储类型关键字 3.控制语句关键字 3.1.跳转结构关键字: 3.2.循环结构关键字: 3.3.分支结构关键字: 3. ...

  3. Dart 语言学习笔记(Dart语言完全指南)

    Dart 语言学习笔记 Part 1:概览 Dart是一门纯OOP语言.所有的类都继承于Object类.null也是对象,变量仅仅存储对象的引用 Dart是强类型语言,即使Dart可以进行类型推断(使 ...

  4. C语言学习笔记---初始C语言001

    C语言程序设计笔记---001 初识C语言 1.C语言的标准 2.C语言的特点 3.第一个C程序 ***/知识点汇总/*** 基于VS2019编译器展示一下 代码片: 4.自我介绍 初识C语言 1.前 ...

  5. C语言学习笔记:C语言的指针函数与函数指针??

    前言 在C语言里函数.指针这两个词结合的顺序不同其意义也不同,即指针函数与函数指针的意义不同,这是什么情况呢?估计许多学习C语言的小伙伴已经感觉自己懵懵的了,别急且听小编来说个明白. 指针函数 指针函 ...

  6. C语言学习笔记之C语言简史(不看后悔系列)

    文章目录 C语言简史 一.什么是C语言? 只要计算机体系架构不变,C语言就会长盛不衰. C语言简史 一.什么是C语言? 麻省理工学院,AT&T贝尔实验室,通用电气,三方大佬,准备为GE-645 ...

  7. [R语言学习笔记1] R语言for循环的使用

    学习R语言的过程中,后期逐渐就会用循环语句来减少自己的重复工作.所以了解for循环,是必备技能之一. R语言中的for循环结构是: for (循环变量 in 序列向量){表达式1表达式2...} 要注 ...

  8. R语言学习笔记--《R语言实战》

    文章目录 R语言基础 一.数据结构 1. 向量 2. 矩阵 3. 数组 4. 数据框 5.列表 二.数据输入 1.键盘输入 2.分隔符文本输入 (csv) 图形初阶 一.图形参数 1.符号和线条 2. ...

  9. c v语言 小数后20位,V语言学习笔记-30集成C代码库

    集成C代码库 优势 V的代码库很多都直接调用C标准库函数来实现,对C标准库的依赖还是很重的 由于V代码编译后生成的是C代码,然后再调用C编译器编译成可执行文件 这样的机制决定了V语言可以很方便地调用C ...

  10. C语言学习笔记1——C语言程序

    C语言简介: BCPL ->newB ->C ->UNIX ->Minux ->Linux ->gcc C语言诞生于1970-1973年,在肯·汤姆逊和丹尼斯·里奇 ...

最新文章

  1. PHP Mysql 网站迁移,Linux+PHP+MySql网站迁移配置
  2. nvcc gcc g++混合编译器编程
  3. 征途手游2新开区服务器维护多久,《征途2手游》开启新服“星火燎原”
  4. Nginx 与 Tomcat,Apache的区别
  5. Redhat(Linux)上的JBoss管理配置
  6. python抓取图片数字_Python OCR提取普通数字图形验证中的数字
  7. OSPF 常见错误与排查方法
  8. Eclipse SVN插件版本
  9. 查看hadoop版本
  10. 计算系数(多项式展开+快速幂)
  11. 《惢客创业日记》2019.01.23(周三) 太苦涩的人生也会让人麻木
  12. abi-dumper 理解
  13. OSChina 周一乱弹 —— 老夫聊发少年狂
  14. 专访腾讯安全王雷雷丨构建智能风控体系,护航私域营销安全
  15. 伯特兰·阿瑟·威廉·罗素
  16. 静态IP和动态IP有什么区别?
  17. IDEA添加Java类注释模版
  18. java继承1—上溯造型
  19. poi读取Excel时日期为数字 的解决方法
  20. 说一说ADI公司的DSP发展历程

热门文章

  1. vs2003在win7下搜索或调试卡死
  2. 【附源码】计算机毕业设计SSM培训机构学生管理系统
  3. 基础会计-笔记 v1.0
  4. 学计算机文化基础,第一学期计算机文化基础-5次作业
  5. Java毕设项目网上摄影工作室(java+VUE+Mybatis+Maven+Mysql)
  6. 2015 百度之星 初赛1 1001(贪心)
  7. 通关GO语言03 控制结构:if、for、witch 逻辑语句的那些事儿
  8. 打开mdf、mds格式文件的方法
  9. Android常见绕过屏锁小技巧汇总
  10. Rocke Group团伙新挖矿变种AliyunMiner分析