目录
- 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(及校验)