天天看点

GolangPkg text/template | 笔记1. Overview2. 关联模板3. 参考

目录

  • 1. Overview
    • 空白
    • Action
    • Argument/参数
    • Pipeline
    • Variable/变量
    • 例子
    • Function/函数
  • 2. 关联模板
    • Parse方法
    • ParseFiles函数/方法
    • AddParseTree方法
    • Template方法
    • 调用
  • 3. 参考

1. Overview

模板用双花括号"{{"、"}}" 包围action, action 之外的文字原封不动地输出. 例如:

"{{.Count}} items are made of {{.Material}}"
           

例如执行时传入字段Count=10、Material="foo"的struct, 则输出: “10 items are made of foo”.

空白

为辅助格式: "{{- "时, 前面的空白会被删除; " -}}"时, 后面的空白会被删掉 (空白指空格、Tab、CR、LF).

例如执行: “{{23 -}} < {{- 45}}”, 输出: “23<45”.

Action

包括数据求值和控制结构. 下文的dot指执行模板时的当前值.

{{/* 注释 */}}
    可换行; 不可嵌套

{{pipeline}}
    输出pipeline的文本表示(同fmt.Print的)

{{if pipeline}} T1 {{end}}
    pipeline非空(empty)时执行T1, 否则无输出.
    空指false, 0, nil指针/接口, 长度0的array/slice/map/string.
    Dot不变.

{{if pipeline}} T1 {{else}} T0 {{end}}
    类似, pipeline为空时执行T0.

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
    第一层的else里嵌套一个if: if {} else {if}.

{{range pipeline}} T1 {{end}}
    遍历pipeline(必须是array/slice/map/channel), 用每个值(dot)执行T1.
    如pipeline长度0, 无输出.
    如pipeline是有序map, 按该顺序.

{{range pipeline}} T1 {{else}} T0 {{end}}
    类似, pipeline长度0时, 执行T0.

{{with pipeline}} T1 {{end}}
    pipeline为空时, 无输出. 否则用其值(dot)执行T1.

{{with pipeline}} T1 {{else}} T0 {{end}}
    类似, pipeline为空时执行T0.

{{define "name"}} T1 {{end}}
    定义模板
{{template "name"}}
    调用模板
{{template "name" pipeline}}
    带参数调用模板
{{block "name" pipeline}} T1 {{end}}
    相当于定义模板:
        {{define "name"}} T1 {{end}}
    再调用:
        {{template "name" pipeline}}
           

Argument/参数

参数是个简单值:

- 一个boolean/string/character/integer/floating-pointer/complex常量

- nil

- '.' 即dot

- 一个变量如$piOver2. 单个美元符'$' 是最开始执行模板时的dot

- .Field struct的一个字段. 可链式如.Field1.Field2. 可用于变量如$x.Field1.Field2

- .Key map的一项(的值). 可与字段一起链式如.Field1.Key1.Field2.Key2.
  Key值不必以大写字母开头.
  也可用于变量如$x.key1.key2

- .Method 调用dot的一个方法. 类似可以链式如.Field1.Key1.Method1, 可用于变量如$x.Method1.Field1.
  方法需返回单值、或值+错误 - 当错误非nil时执行中止并返回该错误

- funcName 调用该函数. 返回 类似方法

- 上述用括号组合如:
  print (.F1 arg1) (.F2 arg2)
  (.StructValuedMethod "arg").Field1
           

Argument可以是任何类型, 指针会自动indirect.

如果argument是一个函数(如struct的一个函数字段), 不自动调用它 (调用参考后面的call函数). 函数可做例如"if" action的真值.

Pipeline

Pipeline是一个或链式的多个"命令": argument、方法/函数调用:

Argument1

.Method1.Method2.Method3(Argument2, Argument3)

function1(Argument2, Argument3)
           

仅在链尾时可以带参数. 方法/函数需返回单值、或值+错误 - 当错误非nil时执行中止并返回该错误.

可以用管道符号"|" 连接多个命令: 前一个命令的结果作为后一个命令的末参, 最终返回末命令的结果.

Variable/变量

Action内可定义变量:

$varName := pipeline
           

赋值:

$varName = pipeline
           

“range” action可以定义单个或两个变量:

range $element := pipeline
range $index, $element := pipeline
           

注意单个变量时与Go range相反, 值是遍历的value而非index/key.

变量范围(scope): 定义的"if/with/range" action的结尾, 或模板结尾. 模板调用不继承变量.

调用模板时传递的参数成为变量$ - 即最初的dot.

例子

以下模板均输出带引号的"output":

{{"\"output\""}}

{{`"output"`}}

{{printf "%q" "output"}}
    函数调用, 见后

{{"output" | printf "%q"}}
    管道, "output" 做printf的末参

{{printf "%q" (print "out" "put")}}

{{"put" | printf "%s%s" "out" | printf "%q"}}
{{"output" | printf "%s" | printf "%q"}}

{{with "output"}}{{printf "%q" .}}{{end}}

{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
    pipeline = "output" | printf "%q"

{{with $x := "output"}}{{printf "%q" $x}}{{end}}
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
           

Function/函数

内置的全局函数:

and
    如"and x y z". 参数全部非空才返回true. 所有参数均求值.
or
    如"or x y z". 有一个参数非空即返回true. 所有参数均求值.
not
    单参数, 返回其逻辑反.
    
call
    调用函数. 参数1必须是一个函数 - 后续参数做它的参数.
    例如"call .X.Y 1 2", ".Y"一定要产生一个函数(如struct的函数字段、map的函数项值). 返回单值、或值+错误 - 当错误非nil时执行中止并返回该错误.

html
urlquery
js
    Escaping.
    
index
    如"index x 1 2 3" 即x[1][2][3]. 中间结果是map/slice/array.

slice
    如"slice x 1 2" 即x[1:2].
    "slice x" 即x[:].
    "slice x 1" 即x[1:].
    "slice x 1 2 3" 即x[1:2:3].
    参数1是string/slice/array.

len
    参数长度

print
printf
println
    fmt.Sprint/Springf/Sprintln
           

另, 比较函数(返回boolean):

eq    arg1 == arg2
ne    arg1 != arg2
lt    arg1 < arg2
le    arg1 <= arg2
gt    arg1 > arg2
ge    arg1 >= arg2
           

eq也支持超过2个参数, 等价于:

arg1 == arg2 || arg1 == arg3 || arg1 == arg4 ...
           

(注: 与Go || 运算不同, 这里由于是函数, 每个参数都会求值.)

2. 关联模板

在模板内可以用"{{define}} {{end}}" 再定义模板, 例如:

t, _ := template.New("xx").Parse(`xx body {{define "foo"}} foo body {{end}}`)
println(t.Name(), t.DefinedTemplates())
// 输出: xx ; defined templates are: "foo", "xx"
           

模板t 的名称是"xx", 它又定义了名称"foo"的模板.

Template对象主要有三个字段: 名称Name、解析内容Tree、一个map. 该map包含所有它关联的模板(name=>Template), 也包含它自己(name=自己的Name).

Parse方法

将text内所有模板定义("{{define}} {{end}}"、"{{block}} {{end}}") 抽出来, 分别解析放入map. 剩余文本解析为t 的内容(Tree), 也放入map(name=t.Name).

  • map里的所有模板(除了自己) 称为该模板的关联模板
  • 模板和其所有关联模板 共享该map

如果模板的文本内容只含空白和注释, 则不会覆盖已有的模板定义. 示意代码:

t := template.New("xx")
t.Parse(`xx body {{define "foo"}} foo body {{end}}`)
t.Execute(os.Stdout, nil) // xx body
t.ExecuteTemplate(os.Stdout, "foo", nil) //  foo body

t.Parse(`{{/* xx body */}} {{define "foo"}} new foo body {{end}}`)
t.Execute(os.Stdout, nil) // xx body
t.ExecuteTemplate(os.Stdout, "foo", nil) //  new foo body
           

第二次Parse时"foo" 的定义被覆盖了, 而抽出"foo" 后只余注释和空白, 所以t 的定义未被覆盖.

另, 模板只能在顶层定义(例如不能在"if" action 内定义); 并且不能互相重名(也不能与t 重名).

ParseFiles函数/方法

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

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

顺序: 解析一个参数文件、将每个解析结果模板与结果t 关联.

函数: 结果t 对应第一个文件参数(Name=文件名-不含路径, 内容Tree-如果该文件还有实际剩余内容). 方法: 如果一个解析结果模板的Name=t.Name, 它将覆盖结果t 的内容(Tree).

例如在目录"a"建"over.tmpl"文件:

{{define "foo"}}
this is foo.
{{end}}

{{define "bar"}}
this is a/bar.
{{end}}
           

在目录"b"也建"over.tmpl"文件("bar"的输出不同):

{{define "bar"}}
this is b/bar.
{{end}}

{{define "baz"}}
this is baz.
{{end}}
           

测试:

func test_over() {
	t, err := template.ParseFiles("./a/over.tmpl", "./b/over.tmpl")
	if err != nil {
		panic(err)
	}
	// 输出: over.tmpl ; defined templates are: "foo", "bar", "over.tmpl", "baz"
	println(t.Name(), t.DefinedTemplates())

	err = t.ExecuteTemplate(os.Stdout, "bar", "") // b/bar
	if err != nil {
		panic(err)
	}

	// 输出: foo ; defined templates are: "over.tmpl", "foo", "bar", "baz"
	println("foo", t.Lookup("foo").DefinedTemplates())
}
           

上述代码表明: 每个文件里的"define"模板都关联了、且后解析的文件(b)里的覆盖先解析的文件(a)里的 (最后"foo"的输出证明所有关联模板共享map).

AddParseTree方法

如果参数name=t.name, 就是覆盖t 的内容/Tree, 返回t.

否则新建一个模板对象返回 - 它新增/覆盖到t.map.

Template方法

func (t *Template) Template() []*Template
           

返回t.map 里的所有模板对象 (无序).

调用

“{{template}}” action调用关联模板. 例如:

{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}
           

执行时输出:

ONE TWO
           

如前所述, 参数里的"{{define}} {{end}}" 会抽出来, 所以define 的顺序不重要(并且调用的模板在解析时可以不存在, 在运行时存在就行了). 此例可改写为:

func test_order() {
	t, err := template.New("xx").Parse(`
	{{template "T3"}}
	{{define "T1"}}ONE-changed!!{{end}}
	{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
	`)
	if err != nil {
		panic(err)
	}

	_, err = t.New("T1").Parse(`ONE{{define "T2"}}TWO{{end}}`)
	if err != nil {
		panic(err)
	}

	t.ExecuteTemplate(os.Stdout, "T3", "") // ONE TWO
}
           

参数里"T1" 内容先做了修改, 但后续通过t.New(“T1”).Parse改回去了(Parse时使用"T1" 覆盖到map - T1和t 共享map).

3. 参考

一个Golang模板的include设计

使用Golang模板拼sql(及校验)