GoWeb——使用模板html/template包

1、 模板原理

1.1 模板和模板引擎

在基于MVC模型的Wb架构中,我们常将不变的部分提出成为模板,而可变部分由后端程序提供数据,借助模板引擎渲染来生成动态网页。

模板可以被理解为事先定义好的TML文档。模板渲染可以被简单理解为文本替换操作一使用相应的数据去替换HTML文档中事先准备好的标记。

模板的诞生是为了将显示与数据分离(即前后端分离)。模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的HTML文档。模板引擎很多,PHP的Smarty、Node.js的jade等都很好使用。

1.2 Go语言模板引擎

Go语言内置了文本模板引擎text/template包,以及用于生成HTML文档的html/template包。它们的使用方法类似,可以简单归纳如下:

  • 模板文件的后缀名通常是.tmpl和.tpl(也可以使用其他的后缀),必须使用UTF-8编码。
  • 模板文件中使用{{}}来包裹和标识需要传入的数据。
  • 传给模板的数据可以通过点号.来访问。如果是复合类型的数据,则可以通过{{.FieldName}}来访问它的字段。
  • {{}}包裹的内容外,其他内容均不做修改原样输出。

Go语言模板引擎的使用分为:定义模板文件、解析模板文件和渲染模板文件。

1. 定义模板文件。
定义模板文件是指,按照相应的语法规则去定义模板文件。
2. 解析模板文件。
html/template包提供了以下方法来解析模板文件,获得模板对象。可以通过New()函数创建模板对象,并为其添加一个模板名称。New()函数的定义如下:

func New(name string) *Template

可以使用Parse()方法来创建模板对象,并完成解析模板内容。Parse()方法的定义如下:

func (t *Template)Parse(src string)(*Template,error)

如果要解析模板文件,则使用ParseFiles()函数,该函数会返回模板对象。该函数的定义如下:

func ParseFiles(filenames ...string)(*Template,error)

如果要批量解析文件,则使用ParseGlob()函数。该函数的定义如下:

func ParseGlob(pattern string)(*Template,error)

可以使用ParseGlob()函数来进行正则匹配,比如在当前解析目录下有以a开头的模板文件,则使用template.ParseGlob(“a*”)即可。

3. 渲染模板文件。
html/template包提供了Execute(()和ExecuteTemplate)方法来渲染模板。这两个方法的定义如下:

func (t *Template)Execute(wr io.Writer, data interface(})error {}

func (t *Template)ExecuteTemplate(wr io.Writer,name string, data interface(})error {}

在创建New()函数时就为模板对象添加了一个模板名称,执行Execute()方法后会默认去寻找该名称进行数据融合。
使用ParseFiles()函数可以一次加载多个模板,此时不可以使用Execute()来执行数据融合,可以通过ExecuteTemplate()方法指定模板名称来执行数据融合。

2、使用html/template包

2.1、第一个模板

在Go语言中,可以通过将模板应用于一个数据结构(即把该数据结构作为模板的参数)来执行并输出HTML文档。
模板在执行时会遍历数据结构,并将指针指向运行中的数据结构中的“.”的当前位置。

用作模板的输入文本必须是UTF-8编码的文本。“Action”是数据运算和控制单位,“Action"由“{{”和“}}”界定;在Action之外的所有文本都会不做修改地复制到输出中。Action内部不能有换行,但注释可以有换行。

template_example.tmpl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板使用示例</title>
</head>
<body>
    <p>示例:{{.}}</p>
</body>
</html>
/*
解析和渲染模板
*/
func main() {
	http.HandleFunc("/", helloHandleFunc)
	http.ListenAndServe(":8080", nil)
}

func helloHandleFunc(w http.ResponseWriter, r *http.Request) {
	//解析模板
	t, err := template.ParseFiles("./src/bookWebPro/chapter2/template_example.tmpl")
	if err != nil {
		fmt.Println("template parsefile failed, err:", err)
		return
	}
	//渲染模板
	name := "hello world"
	t.Execute(w, name)
}

浏览器访问:127.0.0.1:8080:
在这里插入图片描述

2.2 模板语法

模板语法都包含在“{{”和“}}”中间,其中“{{}}”中的点表示当前对象。在传入一个结构体对象时,可以根据“.”来访问结构体的对应字段。例如:

<!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>
<p>Hello {{.Name}}</p>
<p>性别: {{.Gender}}</p>
<p>年龄: {{.Age}}</p>
</body>
</html>
func main() {
	http.HandleFunc("/", sayHello)
	http.ListenAndServe(":8080", nil)
}

type UserInfo struct {
	Name   string
	Gender string
	Age    int
}

func sayHello(w http.ResponseWriter, r *http.Request) {
	//解析模板
	t, err := template.ParseFiles("./src/bookWebPro/chapter2/template_example2.tmpl")
	if err != nil {
		fmt.Println("template parsefile failed, err:", err)
		return
	}
	//渲染模板
	user := UserInfo{
		Name:   "李四",
		Gender: "男",
		Age:    28,
	}
	t.Execute(w, user)
}

在这里插入图片描述
同理,传入的变量是map时,也可以在模板文件中通过"{{.}}"的键值来取值。

1. 注释:
在Go语言中,HTML模板的注释结构如下:

({/*这是一个注释,不会解析*/}}

注释在执行时会被忽略。可以有多行注释。注释不能嵌套,并且必须紧贴分界符始止。

2. 管道(pipeline):

管道是指产生数据的操作。比如“{{.}}”“{{.Name}}”等。Go的模板语法中支持使用管道符号“|”链接多个命令,用法和UNX下的管道类似:“|”前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。

3.变量:

在Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下:

$variable := pipeline

其中$variable是变量的名字。声明变量的Action不会产生任何输出。

4.条件判断:

Go模板语法中的条件判断有以下几种:

{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 ({else if pipeline}} T0 {{end}}

5. range关键字:

在Go的模板语法中,使用range关键字进行遍历,其中pipeline的值必须是数组、切片、字典或者通道。其语法以“{{range pipeline}}”开头,以“{{end}}”结尾,形式如下:

{{range pipeline}} T1 {{end}}

如果pipeline的值其长度为0,则不会有任何输出。中间也可以有“{{else}}”,形如:

{{range pipeline}} T1 {{else)} T0 ((end}}

如果pipeline的值其长度为0,则会执行T0。
range关键字的使用示例如下。

//range使用示例
func main() {
	//创建一个模板
	rangeTemplate := `
		{{if .Kind}}
			{{range $i, $v := .MapContent}}
			{{$i}} => {{$v}}, {{$.OutsideContent}}
			{{end}}
		{{else}}
			{{range .MapContent}}
			{{.}}, {{$.OutsideContent}}
			{{end}}
		{{end}}`

	str1 := []string{"第一次range", "用index和value"}
	str2 := []string{"第二次range", "没有用index和value"}

	type Content struct {
		MapContent     []string
		OutsideContent string
		Kind           bool
	}

	var contens = []Content{
		{str1, "第一次外面的内容", true},
		{str2, "第二次外面的内容", false},
	}

	//创建模板并将字符解析进去
	t := template.Must(template.New("range").Parse(rangeTemplate))
	//接收并执行
	for _, c := range contens {
		err := t.Execute(os.Stdout, c)
		if err != nil {
			log.Panicln("executing template:", err)
		}
	}
}

                
                        
                        0 => 第一次range, 第一次外面的内容
                        
                        1 => 用index和value, 第一次外面的内容
                        
                
                
                        
                        第二次range, 第二次外面的内容
                        
                        没有用index和value, 第二次外面的内容
                        
       

6. with关键字:
在Go的模板语法中,with关键字和if关键字有点类似,“{{with}}”操作仅在传递的管道不为空时有条件地执行其主体。形式如下:

{{with pipeline}} T1 {{end}}

如果pipeline为空,则不产生输出。中间也可以加入“{{else}}”,形如:

{{with pipeline}} T1 {{else}}T0 ({end}}

如果pipeline为空,则不改变“.”并执行T0,否则将“.”设为pipeline的值并执行T1。

7. 比较函数:
布尔函数会将任何类型的零值视为假,将其余视为真。下面是常用的二元比较运算符:

eq //如果argl=arg2,则返回真
ne //如果arg1!=arg2,则返回真
lt //如果arg1<arg2,则返回真
le //如果arg1<=arg2,则返回真
gt //如果arg1>arg2,则返回真
ge //如果arg1>=arg2,.则返回真

为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第1个参数和其余参数依次比较,形式如下:

((eq arg1 arg2 arg3}}

即只能做如下比较:

arg1==arg2 I|argl==arg3

比较函数只适用于基本类型(或重定义的基本类型,如“type Balance float32”)。但整数和浮点数不能互相比较。

8. 预定义函数:

函数名功能
and函数返回其第1个空参数或者最后一个参数,即“and x y”等价于“if x then y else x”。所有参数都会执行
or返回第一个非空参数或者最后一个参数,即“or x y”等价于“if x then × else y”。所有参数都会执行
not返回其单个参数的布尔值“不是”
len返回其参数的整数类型长度
index执行结果为index()函数后第1个参数以第1个参数后面剩下的参数为索引指向的值,例如“index y 1 2 3”返回y[1][2][3]的值。每个被索引的主体必须是数组、切片或者字典
print即fmt.Sprint
printf即fmt.Sprintf
printin即fmt.Sprintln
html返回其参数文本表示的HTML逸码等价表示
urlquery返回其参数文本表示的可嵌入URL查询的逸码等价表示
js返回其参数文本表示的JavaScript逸码等价表示
call执行结果是调用第1个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;如“cal .X .Y 1 2”等价于Go语言里的dot.X.Y(1,2);其中丫是函数类型的字段或者字典的值,或者其他类似情况;
call的第1个参数的执行结果必须是函数类型的值,和预定义函数(如prit0)明显不同;该函数类型值必须有1个或2个返回值。如果有2个返回值,则后一个必须是error接口类型;如果有2个返回值的方法返回的error非nil,则模板执行会中断并返回给调用模板执行者该错误

8.自定义函数:
Go语言的模板支持自定义函数。自定义函数通过调用Fucs()方法实现,其定义如下:

func (t *Template)Funcs(funcMap FuncMap)*Template

Funcs()方法向模板对象的函数字典里加入参数funcMap内的键值对。如果funcMap的某个键值对的值不是函数类型,或者返回值不符合要求,则会报panic错误,但可以对模板对象的函数列表的成员进行重写。方法返回模板对象以便进行链式调用。FuncMap类型的定义如下:

type FuncMap map[string]interface(}

FuncMap类型定义了函数名字符串到函数的映射,每个函数都必须有1个或2个返回值。如果有2个返回值,则后一个必须是error接口类型;如果有2个返回值的方法返回error非nil,则模板执行会中断并返回该错误给调用者。

在执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Fucs()方法添加函数到模板里。其使用示例如下。

/*
自定义函数
*/
func main() {
	http.HandleFunc("/", sayHello2)
	http.ListenAndServe(":8080", nil)
}

func Welcome() string {
	return "Welcome"
}

func Doing(name string) string {
	return name + ", Learning Go Web template "
}

func sayHello2(w http.ResponseWriter, r *http.Request) {
	htmlByte, err := ioutil.ReadFile("./src/bookWebPro/chapter2/funcs.html")
	if err != nil {
		fmt.Println("read html failed, err:", err)
		return
	}
	//自定义一个匿名模板函数
	loveGo := func() string {
		return "欢迎学习Go语言"
	}
	//链式操作在Parse()方法之前调用Funcs()函数,用来添加自定义的loveGo函数
	tmpl1, err := template.New("funcs").Funcs(template.FuncMap{"loveGo": loveGo}).Parse(string(htmlByte))
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}
	funcMap := template.FuncMap{
		//声明要使用的函数
		"Welcome": Welcome,
		"Doing":   Doing,
	}
	name := "Shirdon"
	tmpl2, err2 := template.New("test").Funcs(funcMap).Parse("{{Welcome}}\n{{Doing .}}\n")
	if err2 != nil {
		panic(err2)
	}

	//使用user渲染模板,并将结果写入w
	tmpl1.Execute(w, name)
	tmpl2.Execute(w, name)
}
<!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>tmpl test</title>
</head>
<body>
    <h1>{{loveGo}}</h1>
</body>
</html>

在这里插入图片描述

9. 使用嵌套模板:
html/template包支持在一个模板中嵌套其他模板。被嵌套的模板可以是单独的文件,也可以是通过“define”关键字定义的模板。通过“define”关键字可以直接在待解析内容中定义一个模板。例如定义一个名称为name的模板的形式如下:

{{define "name"}} T {{end}}

通过“template”关键字来执行模板。例如,执行名为“name”的模板的形式如下:

{{template "name"}}
{{template "name" pipeline}}

“block”关键字等价于“define”关键字。“block”关键字用于定义一个模板,并在有需要的地方执行这个模板。其形式如下:

{{block "name" pipeline }} T {{end}}

等价于:先执行{{define “name”}} T {{end}},再执行{{template “name” pipeline}}。

创建用于模板嵌套的代码:
t.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>tmpl test</title>
</head>
<body>
    <h1>测试嵌套template语法</h1>
    <hr>
    {{template "ul.html"}}
    <hr>
    {{template "ol.html"}}
</body>
</html>
{{define "ol.html"}}
<h1>这是ol.html</h1>
<ol>
    <li>I love Go</li>
    <li>I love java</li>
    <li>I love c</li>
</ol>
{{end}}

ul.html

<ul>
    <li>注释</li>
    <li>日志</li>
    <li>测试</li>
</ul>
//定义一个UserInfo
type UserInfoA struct {
	Name   string
	Gender string
	Age    int
}

func tmplSample(w http.ResponseWriter, r *http.Request) {
	tmpl, err := template.ParseFiles("./src/bookWebPro/chapter2/t.html", "./src/bookWebPro/chapter2/ul.html")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}
	user := UserInfoA{
		Name:   "张三",
		Gender: "男",
		Age:    28,
	}
	tmpl.Execute(w, user)
}

func main() {
	http.HandleFunc("/", tmplSample)
	http.ListenAndServe(":8080", nil)
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值