博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go 语言标准库 text/template 包深入浅出
阅读量:6377 次
发布时间:2019-06-23

本文共 7364 字,大约阅读时间需要 24 分钟。

目录

官方定义:

Package template implements data-driven templates for generating textual output.

template 包是数据驱动的文本输出模板,其实就是在写好的模板中填充数据。

模板

什么是模板?

下面是一个简单的模板示例:

// 模板定义tepl := "My name is {
{ . }}"// 解析模板tmpl, err := template.New("test").Parse(tepl)// 数据驱动模板data := "jack"err = tmpl.Execute(os.Stdout, data)复制代码

{

{ 和 }} 中间的句号 . 代表传入模板的数据,根据传入的数据不同渲染不同的内容。

. 可以代表 go 语言中的任何类型,如结构体、哈希等。

至于 {

{ 和 }} 包裹的内容统称为 action,分为两种类型:

  • 数据求值(data evaluations)
  • 控制结构(control structures)

action 求值的结果会直接复制到模板中,控制结构和我们写 Go 程序差不多,也是条件语句、循环语句、变量、函数调用等等...

将模板成功解析(Parse)后,可以安全地在并发环境中使用,如果输出到同一个 io.Writer 数据可能会重叠(因为不能保证并发执行的先后顺序)。

Actions

模板中的 action 并不多,我们一个一个看。

注释

{
{
/* comment */}}复制代码

裁剪空格

// 裁剪 content 前后的空格{
{- content -}}// 裁剪 content 前面的空格{
{- content }}// 裁剪 content 后面的空格{
{ content -}}复制代码

文本输出

{
{ pipeline }}复制代码

pipeline 代表的数据会产生与调用 fmt.Print 函数类似的输出,例如整数类型的 3 会转换成字符串 "3" 输出。

条件语句

{
{ 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 }}{
{ if pipeline }} T1 {
{ else if pipeline }} T2 {
{ else }} T0 {
{ end }}复制代码

如果 pipeline 的值为空,不会输出 T1,除此之外 T1 都会被输出。

空值有 false、0、任意 nil 指针、接口值、数组、切片、字典和空字符串 ""(长度为 0 的字符串)。

循环语句

{
{ range pipeline }} T1 {
{ end }}// 这个 else 比较有意思,如果 pipeline 的长度为 0 则输出 else 中的内容{
{ range pipeline }} T1 {
{ else }} T0 {
{ end }}// 获取容器的下标{
{ range $index, $value := pipeline }} T1 {
{ end }}复制代码

pipeline 的值必须是数组、切片、字典和通道中的一种,即可迭代类型的值,根据值的长度输出多个 T1。

define

{
{ define "name" }} T {
{ end }}复制代码

定义命名为 name 的模板。

template

{
{ template "name" }}{
{ template "name" pipeline }}复制代码

引用命名为 name 的模板。

block

{
{ block "name" pipeline }} T1 {
{ end }}复制代码

block 的语义是如果有命名为 name 的模板,就引用过来执行,如果没有命名为 name 的模板,就是执行自己定义的内容。

也就是多做了一步模板是否存在的判断,根据这个结果渲染不同的内容。

with

{
{ with pipeline }} T1 {
{ end }}// 如果 pipeline 是空值则输出 T0{
{ with pipeline }} T1 {
{ else }} T0 {
{ end }}{
{ with arg }} . // 此时 . 就是 arg{
{ end }}复制代码

with 创建一个新的上下文环境,在此环境中的 . 与外面的 . 无关。

参数

参数的值有多种表现形式,可以求值任何类型,包括函数、指针(指针会自动间接取值到原始的值):

  • 布尔、字符串、字符、浮点数、复数的行为和 Go 类似
  • 关键字 nil 代表 go 语言中的 nil
  • 字符句号 . 代表值的结果
  • 以 $ 字符开头的变量则为变量对应的值
  • 结构体的字段表示为 .Field,结果是 Field 的值,支持链式调用 .Field1.Field2
  • 字典的 key 表示为 .Key 结果是 Key 对应的值
  • 如果是结构体的方法集中的方法 .Method 结果是方法调用后返回的值(The result is the value of invoking the method with dot as the receiver)**
    • 方法要么只有一个任意类型的返回值要么第二个返回值为 error,不能再多了,如果 error 不为 nil,会直接报错,停止模板渲染
    • 方法调用的结果可以继续链式调用 .Field1.Key1.Method1.Field2.Key2.Method2
    • 声明变量方法集也可以调用 $x.Method1.Field
    • 用括号将调用分组 print (.Func1 arg1) (.Func2 arg2)(.StructValuedMethod "arg").Field

这里最难懂的可能就是函数被调用的方式,如果访问结构体方法集中的函数和字段中的函数,此时的行为有什么不同?

写个 demo 测一下:

type T struct {	Add func(int) int}func (t *T) Sub(i int) int {	log.Println("get argument i:", i)	return i - 1}func arguments() {	ts := &T{		Add: func(i int) int {			return i + 1		},	}	tpl := `		// 只能使用 call 调用		call field func Add: {
{ call .ts.Add .y }} // 直接传入 .y 调用 call method func Sub: {
{ .ts.Sub .y }} ` t, _ := template.New("test").Parse(tpl) t.Execute(os.Stdout, map[string]interface{}{ "y": 3, "ts": ts, })}output:call field func Add: 4call method func Sub: 2复制代码

可以得出结论:如果函数是结构体中的函数字段,该函数不会自动调用,只能使用内置函数 call 调用。

如果函数是结构体方法集中的方法,会自动调用该方法,并且会将返回值赋值给 .,如果函数返回新的结构体、map,可以继续链式调用。

变量

action 中的 pipeline 可以初始化变量存储结果,语法也很简单:

$variable = pipeline复制代码

此时,这个 action 声明了一个变量而没有产生任何输出。

range 循环可以声明两个变量:

range $index, $element := pipeline复制代码

在 if、with 和 range 中,变量的作用域拓展到 {

{ end }} 所在的位置。

如果不是控制结构,声明的变量的作用域会扩展到整个模板。

例如在模板开始时声明变量:

{
{ $pages := .pagination.Pages }}{
{ $current := .pagination.Current }}复制代码

在渲染开始的时候,$ 变量会被替换成 . 开头的值,例如 $pages 会被替换成 .pagenation.Pages。所以在模板间的相互引用不会传递变量,变量只在某个特定的作用域中产生作用。

函数

模板渲染时会在两个地方查找函数:

  • 自定义的函数 map
  • 全局函数 map,这些函数是模板内置的

自定义函数使用 func (t *Template) Funcs(funcMap FuncMap) *Template 注册。

全局函数列表:

and

返回参数之间 and 布尔操作的结果,其实就是 JavaScript 中的逻辑操作符 &&,返回第一个能转换成 false 的值,在 Go 中就是零值,如果都为 true 返回最后一个值。

tpl := "{
{ and .x .y .z }}"t, _ := template.New("test").Parse(tpl)t.Execute(os.Stdout, map[string]interface{}{ "x": 1, "y": 0, "z": 3,})output:0复制代码

or

逻辑操作符 ||,返回第一个能转换成 true 的值,在 Go 中就是非零值,如果都为 false 返回最后一个值。

tpl := "{
{ or .x .y .z }}"t, _ := template.New("test").Parse(tpl)t.Execute(os.Stdout, map[string]interface{}{ "x": 1, "y": 0, "z": 3,})output:1复制代码

call

返回调用第一个函数参数的结果,函数必须有一个或两个回值(第二个返回值必须是 error,如果值不为 nil 会停止模板渲染)

tpl := "call: {
{ call .x .y .z }} \n"t, _ := template.New("test").Parse(tpl)t.Execute(os.Stdout, map[string]interface{}{ "x": func(x, y int) int { return x+y}, "y": 2, "z": 3,})output:5复制代码

html

返回转义后的 HTML 字符串,这个函数不能在 html/template 中使用。

js

返回转义后的 JavaScript 字符串。

index

在第一个参数是 array、slice、map 时使用,返回对应下标的值。

index x 1 2 3 等于 x[1][2][3]

len

返回复合类型的长度。

not

返回布尔类型参数的相反值。

print

等于 fmt.Sprint

printf

等于 fmt.Sprintf

println

等于 fmt.Sprintln

urlquery

对字符串进行 url Query 转义,不能在 html/template 包中使用。

// URLQueryEscaper returns the escaped value of the textual representation of// its arguments in a form suitable for embedding in a URL query.func URLQueryEscaper(args ...interface{}) string {	return url.QueryEscape(evalArgs(args))}复制代码

从源码可以看到这个函数直接调用 url.QueryEscape 对字符串进行转义,并没有什么神秘的。

比较函数

  • eq: ==
  • ge: >=
  • gt: >
  • le: <=
  • lt: <
  • ne: !=

分析两个源码:

// eq evaluates the comparison a == b || a == c || ...func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {	v1 := indirectInterface(arg1)	k1, err := basicKind(v1)	if err != nil {		return false, err	}	if len(arg2) == 0 {		return false, errNoComparison	}	for _, arg := range arg2 {		v2 := indirectInterface(arg)		k2, err := basicKind(v2)		if err != nil {			return false, err		}		truth := false		if k1 != k2 {			// Special case: Can compare integer values regardless of type's sign.			switch {			case k1 == intKind && k2 == uintKind:				truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()			case k1 == uintKind && k2 == intKind:				truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())			default:				return false, errBadComparison			}		} else {			switch k1 {			case boolKind:				truth = v1.Bool() == v2.Bool()			case complexKind:				truth = v1.Complex() == v2.Complex()			case floatKind:				truth = v1.Float() == v2.Float()			case intKind:				truth = v1.Int() == v2.Int()			case stringKind:				truth = v1.String() == v2.String()			case uintKind:				truth = v1.Uint() == v2.Uint()			default:				panic("invalid kind")			}		}		if truth {			return true, nil		}	}	return false, nil}// ne evaluates the comparison a != b.func ne(arg1, arg2 reflect.Value) (bool, error) {	// != is the inverse of ==.	equal, err := eq(arg1, arg2)	return !equal, err}复制代码

eq 先判断接口类型是否相等,然后判断值是否相等,没什么特殊的地方。

ne 更是简单的调用 eq,然后取反。

ge、gt、le、lt 与 eq 类似,先判断类型,然后判断大小。

嵌套模板

下面是一个更复杂的例子:

// 加载模板template.ParseFiles("templates/")// 加载多个模板到一个命名空间(同一个命名空间的模块可以互相引用)template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")// must 加载失败时 panictmpl := template.Must(template.ParseFiles("layout.html"))// 执行加载后的模板文件,默认执行第一个tmpl.Execute(w, "test")// 如果 tmpl 中有很多个模板,可以指定要执行的模板名tmpl.ExecuteTemplate(w, "layout", "Hello world")复制代码

ExecuteTemplate 指定的名字就是模板文件中 define "name" 的 name。

总结

Parse 系列函数初始化的 Template 类型实例。

Execute 系列函数则将数据传递给模板渲染最终的字符串。

模板本质上就是 Parse 函数加载多个文件到一个 Tempalte 类型实例中,解析文件中的 define 关键字注册命名模板,命名模板之间可以使用 template 互相引用,Execute 传入对应的数据渲染。

转载地址:http://vzxqa.baihongyu.com/

你可能感兴趣的文章
导出DC数据以便以介质方式安装另一台域控制器
查看>>
2、Gerrit配置--用户配置
查看>>
Centos7 Nginx 服务器的安装配置
查看>>
Hibernate学习(八):检索方式
查看>>
RIPv1 PK RIPv2
查看>>
基于WorsPress+Xampp搭建博客
查看>>
Weblogic多应用部署在一个域下导致session冲突
查看>>
安装Centos6 分区时出现缺少/boot/efi 经验分享
查看>>
javascript的一些基本概念
查看>>
关于Tomcat上请求的编解码问题
查看>>
WPF“动画序列”框架的初步研究与实现(附源码)
查看>>
Windows Server 2008 多元密码策略配置
查看>>
.NET中的泛型和Java泛型中的类型擦除
查看>>
白利用的集大成者:新型远控木马上演移形换影大法
查看>>
SAS 2016年全球营收达32亿美元 继续保持稳步增长
查看>>
2017必备的八款最佳反勒索软件工具
查看>>
从Effective Java总结一些有助安卓开发的建议
查看>>
以一当十的程序员不是传说
查看>>
Vizinex RFID 和Brady SmartID推出航空标签
查看>>
Facebook 否认趋势话题存在政治偏见,但将做出调整
查看>>