Go 语言标准库 text/template 包深入浅出

栏目: Go · 发布时间: 6年前

内容简介:官方定义:什么是模板?下面是一个简单的模板示例:

官方定义:

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: 4
call 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 加载失败时 panic
tmpl := 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 传入对应的数据渲染。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Probability and Computing

Probability and Computing

Michael Mitzenmacher、Eli Upfal / Cambridge University Press / 2005-01-31 / USD 66.00

Assuming only an elementary background in discrete mathematics, this textbook is an excellent introduction to the probabilistic techniques and paradigms used in the development of probabilistic algori......一起来看看 《Probability and Computing》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具