Go语言中的模板引擎
包text/template实现了一个数据驱动的模板引擎,类似的还有html/template包,两者接口一样,但是后者针对HTML进行处理,可以防止某些注入式攻击。
模板的源码是一段UTF-8文本,其中会有一些 {{ }}包围的动作(Action)。模板执行时,动作中的内容 —— pipeline —— 被计算、替换,外部的内容则直接原样输出。一旦模板被解析,就可以被多次、并行的执行。
{{}}内部可以包含以点号开头的表达式,这表示读取上下文对象(dot对象)的属性。例如 {{.name}}则意味着读取上下文对象的name属性。
{{-可以用于去除左外侧的空白符(空格、水平制表、回车、换行), -}}则可以清除右外侧的空白符,例如:
1 2 3 |
"{{1 -}} < {{- 2}}" // 输出 1<2 |
{{/* a comment */}} 注释,执行模板时直接被丢弃,支持多行注释 | ||
{{pipeline}},流水线(一系列Go表达式或者Go表达式通过管道操作符连接)的文本展示形式(即fmt.Print输出形式),直接输出到结果 | ||
条件分支结构,T*可以是普通文本,或者其它任何Action |
||
循环结构,T1中的{{}}内的点号(dot对象,即上下文对象)被设置为数组、切片、映射或通道的元素 range可以声明变量: range $index, $element := pipeline |
||
{{template "name"}} 就地引入并执行名为name的模板 {{template "name" pipeline}} 就地引入并执行名为name的模板,设置该模板的dot对象为pipeline的结果 |
||
{{block "name" pipeline}} T1 {{end}} 相当于定义模板 {{define "name"}} T1 {{end}}并原地执行 {{template "name" .}} |
||
{{with pipeline}} T1 {{end}}如果pipeline结果不为空则设置dot对象为pipeline的结果,并执行T1 {{with pipeline}} T1 {{else}} T0 {{end}}如果pipeline结果不为空则设置dot对象为pipeline的结果,并执行T1;否则dot对象不变且执行T0 |
所谓流水线,类似于Linux的命令管道,是一系列“命令”的链条。每个命令可以是:
- 简单的值: Argument
- 方法调用: .Method [Argument...]
- 函数调用: functionName [Argument...]
这些命令通过 | 符号连接在一起,前一个命令的结果作为后一个命令的最后一个入参传递。流水线中最后一个命令的结果,作为整个流水线的结果。命令的结果(返回值)可以1-2个,如果第2个值非nil则意味着出现错误,模板调用者会收到此错误。
也叫Argument,可以是如下形式之一:
- 各种类型的字面值,以及nil
- 点号( .),上下文对象(结构体,dot)
- 键值访问,针对map对象,例如 .key、 $map.key,可以和字段访问混合进行点号导航,键值访问不像字段访问,key不需要首字母大写
- 字段访问,例如 .Field、.Field1.Field2、 $v.Field1.Field2
- .Method,上下文对象为接收者的方法调用
- .func,函数调用
- $varname,模板变量,美元符号开头
任何一个Action中的Pipeline都可以初始化变量,示例: {{ $variable := pipeline }}
变量总是以$符号开头,不管是声明还是后续引用。声明过的变量可以被赋值:
1 |
$variable = pipeline |
range动作可以声明变量,并且在循环体内部使用:
1 |
range $index, $element := pipeline |
当作用域改变后,你可以使用 $来访问解析模板最初使用的根对象,例如 $.Chart.Name
在执行模板时,根据名称,首先寻找为模板绑定的函数:
1 2 3 4 5 |
// 创建模板 绑定函数 tmpl, err = template.New("hello").Funcs(template.FuncMap{ "hasPermission": func(user User, feature string) bool { }, }).ParseFiles("hello.tpl") |
如果找不到则寻找全局函数。全局函数列表:
函数 | 说明 |
and | 返回所有参数的逻辑与结果,实际上就是返回第一个空参数或者最后一个参数 |
call | 调用第一个参数,后续参数作为入参。例如 call .X.Y 1 2等价于 .X.Y(1, 2) |
html | 返回参数的文本展示形式,并执行HTML转义 |
index | 返回第一个参数指定索引上的值。例如 index x 1 2 3等价于 x[1][2][3] |
js | 返回参数的文本展示形式,并执行JavaScript转义 |
len | 返回参数的长度 |
not | 逻辑非操作 |
or | 返回所有参数的逻辑或操作,实际上就是返回第一个非空参数或最后一个参数 |
fmt.Sprint | |
printf | fmt.Sprintf |
println | fmt.Sprintln |
urlquery | 返回参数的文本展示形式,并进行处理保证其适合作为HTTP查询串 |
作为函数的二元操作符 | |
eq | arg1 == arg2 |
ne | arg1 != arg2 |
lt | arg1 < arg2 |
le | arg1 <= arg2 |
gt | arg1 > arg2 |
ge | arg1 >= arg2 |
每个模板都具有名称,在创建模板时你需要给出此名称。此外,每个模板都可以关联0-N个它可以调用的其它模板,这些关联是传递性的。关联模板可以用define来声明:
1 2 3 4 5 6 7 8 9 10 |
// 定义关联模板 tmplText := `{{define "T1"}}ONE{{end}}TWO` // 给出模板名称 tmpl, _ := template.New("test").Parse(tmplText) // 执行模板 tmpl.Execute(os.Stdout, nil) // 输出TWO // 执行指定的关联模板(Associated templates) tmpl.ExecuteTemplate(os.Stdout, "T1", nil) // 输出ONE |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
type User struct { Name string Age uint8 } func main() { # 从文本解析出一个模板 tmpl, _ := template.New("hello").Parse(` {{- range $key, $val := . -}} {{$key}} = {{ $val.Name }}, {{ $val.Age }} {{ end -}} `) users := make(map[string]User) users["10000"] = User{"Alex", 32} users["10001"] = User{"Meng", 29} // 执行模板,最后一个参数为上下文对象 err := tmpl.Execute(os.Stdout, users) if err != nil { log.Fatalf(err.Error()) } } |
包github.com/Masterminds/sprig扩充了template包的函数库。要使用此函数库,调用template.Funcs:
1 2 3 4 5 6 7 8 9 |
import ( "html/template" "github.com/Masterminds/sprig" ) func main() { tmplText := `{{}}` tmpl, _ := template.New("test").Funcs(sprig.FuncMap()).Parse(tmplText) } |
函数 | 说明 |
date | date FORMAT TIME:格式化日期,FORMAT为格式化字符串,TIME为time.Time |
dateModify | date OFFSET TIME:修改给出的时间,例如 dateModify "-1.5h" now |
now | 返回当前时间 |
htmlDate | 返回用于HTML中date类型表单字段的时间字符串 |
dateInZone | dateInZone FORMAT TIME TZ:格式化为指定时区 |
htmlDateInZone | htmlDateInZone TIME TZ:返回用于HTML中date类型表单字段的时间字符串,转换为指定时区 |
函数 | 说明 | ||
abbrev | 缩写参数,超出的字符以...代替。例如 abbrev 5 "hello world"输出 he... | ||
abbrevboth | abbrevboth N STR:从双侧缩写 | ||
trunc | trunc N STR:截断到指定长度 | ||
trim | 去除空白 | ||
trimAll | trimAll T STR:去除所有指定字符。例如 trimAll "$" "$5.00"输出 5.00 | ||
trimSuffix | trimSuffix T STR:去除结尾的指定字符 | ||
trimPrefix | trimPrefix T STR:去除前导的指定字符 | ||
upper | 转换为大写 | ||
lower | 转换为小写 | ||
nospace | 移除所有空格 | ||
title | string.Title | ||
untitle | 移除Title Case | ||
repeat | repeat N STR:重复指定次数 | ||
substr | substr STR START LEN,截取子串:
|
||
initials | 返回字符串中每个单词的首字母组成的缩写字符串 | ||
randAlphaNum | randAlphaNum N:返回指定长度的随机字符串 | ||
randAlpha | |||
randAscii | |||
randNumeric | |||
swapcase | 切换大小写 | ||
shuffle | 随机改变字符串中字符的顺序 | ||
snakecase | 驼峰式大写转换为下划线 | ||
camelcase | 下划线后的小写字符转换为大写,去除下划线 | ||
wrap | wrap N STR:在指定长度处插入换行 | ||
wrapWith | wrapWith N W STR:在指定长度处插入字符串W | ||
contains | contains SUB STR:是否包含子串 | ||
hasPrefix | hasPrefix SUB STR:是否包含指定前缀 | ||
hasSuffix | hasSuffix SUB STR:是否包含指定后缀 | ||
quote | 以双引号包围,将"转义为\" | ||
squote | 以双引号包围,不进行转义 | ||
cat | CAT S1 S2... 连接字符串,用空格 | ||
indent | indent N STR:缩进字符串,例如 indent 4 "foo\nbar"输出 foo\n bar | ||
nindent | indent N STR:缩进字符串,并且为每行前添加一个换行符 | ||
replace | 替换出现的子串,例如 $name | replace " " "-" | ||
sha256sum | 生成sha256哈希 | ||
toString | 转换为字符串 |
函数 | 说明 |
字符串切片 | |
join | join SEP SLICE:使用SEP来连接一个切片 |
split | split SEP STRING:使用SEP来分割字符串。返回一个键为_N的映射,其中N为子串索引(0开始) |
splitList | 类似上面,返回字符串数组 |
toStrings | 返回一个列表的字符串形式,例如 list 1 2 3 | toStrings输出 ["1" "2" "3"] |
sortAlpha | 按照字典序排列一个列表 |
整数切片 | |
until | until N:返回从0-N组成的切片 |
untilStep | untilStep START STOP STEP |
函数 | 说明 |
atoi | 字符串转换为整数,无法解析则返回0 |
int64 | 将字符串或者其它数字类型转换为int64 |
int | 将字符串或者其它数字类型转换为int |
float64 | 将字符串或者其它数字类型转换为float64 |
函数 | 说明 |
default | default DEFAULT VALUE:如果VALUE为空则返回DEFAULT |
empty | empty VALUE:如果VALUE是其类型的零值则返回true,注意:结构绝不会返回true,应该使用if判断其是否为空 |
coalesce | coalesce V1 V2 ... 返回第一个非空值 |
compact | compact V1 V2 ... 返回非零值组成的列表 |
函数 | 说明 |
b64enc | 执行Base64编码 |
b64dec | 执行Base64解码 |
genPrivateKey | 生成指定算法的私钥,支持的算法rsa、dsa、ecdsa |
derivePassword | 根据Master Password算法衍生密码 |
函数 | 说明 |
env | 提取环境变量 |
expandenv | 通过环境变量来展开字符串。例如 expandenv "$HOME"输出/home/alex |
base | 返回文件路径的最后一段 |
dir | 移除文件路径的最后一段 |
ext | 获取扩展名 |
isAbs | 是否绝对路径 |
函数 | 说明 |
add1 | add1 N 返回 N + 1 |
add | 任意数量整数求和 |
sub | 减法操作 |
div | 除法操作 |
mod | 取模操作 |
mul | 乘法操作 |
max | 返回一组整数中的最大值 |
min | 返回一组整数中的最小值 |
函数 | 说明 | ||
tuple | 将一系列条目转换为元组 | ||
list | 将一系列条目转换为列表,优先于tuple | ||
dict | 将一系列键值对转换为 map[string]interface{} | ||
list操作 | |||
first | 返回列表第一个元素 | ||
last | 返回列表最后个元素 | ||
rest | 返回除了第一个元素的子列表 | ||
initial | 返回除了第后个元素的子列表 | ||
append | 添加一个元素到列表尾部 | ||
prepend | 添加一个元素到列表头部 | ||
reverse | 返回逆序的列表 | ||
uniq | 移除列表中的重复项 | ||
without | without LIST ITEM,返回剔除掉ITEM的列表 | ||
has | has ITEM LIST,判断列表中是否存在ITEM | ||
dict操作 | |||
set | set DICT KEY VALUE,设置键值 | ||
unset | unset DICT KEY,移除键值 | ||
hasKey | hasKey,判断是否存在键。示例:
|
||
pluck | pluck KEY D1 D2,获取所有字典中指定的键的值 | ||
keys | keys D1 D2,返回所有字典的键的数组 | ||
pick | pick K1 K2 DICT,提取指定的键值,返回新的字典 |
您好,看到您的资料,十分有用,但有个问题,我现在解决不了,麻烦您帮忙看下:
我将一个路径按“/”分隔后, 得到一个map,然后获取其最后一个,理论上得到长度再减1就是最后一个的索引,但在模板语言中,实在不知道怎么写,麻烦了
路径分隔后应该得到的是数组吧?用index函数就可以了
您好!我想问下,如果我有个变量是数字,我想让它变成可以循环使用的列表啥的。比如:
replica: 3 # 当然数字可以自定义
我想循环,0到3。得到想要的字符串:
"test-0,test-1,test-2"
大概这样:
import (
"html/template"
"github.com/Masterminds/sprig"
"os"
)
func main() {
tmplText := `{{ range $index, $element := until 3 }}test-{{ $index}},{{ end }}`
tmpl, _ := template.New("test").Funcs(sprig.FuncMap()).Parse(tmplText)
tmpl.Execute(os.Stdout, "1111")
}