(译)golang文本模板

概述

template包实现了用于生成文本输出的数据驱动(data-driven)模板。

要生成HTML输出,请参阅html / template 包,它与此包具有相同的接口,但会自动保护HTML输出免受某些攻击。

通过将模板应用于一个数据结构来执行模板。模板中的标记引用该数据结构的元素(通常是struct中的字段或map中的键)来控制执行和获取要显示的值。模板的执行遍历该数据结构并设置游标,以'.'表示,指向作为执行过程中该数据结构当前位置的值。

模板的输入文本是任何格式的UTF-8编码文本。 "动作(Actions)"用于执行数据求值或控制结构,位于 "{{" 和 "}}" 中。所有操作以外的文本都将被复制到输出中。除了原始字符串,操作通常不换行,尽管注释可以。

一旦解析,模板可以并行安全地执行,但如果并行执行共享同一个Writer,则输出可能会交错。

这是一个简单的例子,打印 "17 items are made of wool" 。

type Inventory struct {
	Material string
	Count    uint
}
sweaters := Inventory{"wool", 17}
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)
if err != nil { panic(err) }

下面显示更复杂的例子。

文本和空格

默认情况下,在执行模板时,操作之间的所有文本都会逐字复制。 例如,在程序运行时,上述示例中的字符串 "items are made of" 出现在标准输出中。

然而,为了帮助格式化模板源代码,如果操作的左分隔符(默认为"{{")后面紧跟着一个减号和ASCII空格字符("{{- "),则前面的文本中的所有尾部空白符将被丢弃。 同样,如果右分隔符("}}")前面有空格和减号(" -}}"),则所有前导空格都将从紧随其后的文本中删除。 在这些修剪标记中,ASCII空格必须存在,否则,如 "{{-3}}" 将被解析为包含数字 -3 的操作。

例如,当执行源代码为的模板时

"{{23 -}} < {{- 45}}"

生成的输出为

"23<45"

对于这种修剪,空白字符的定义与Go语言相同,包括:空格,水平制表符,回车符和换行符。

动作

下面是动作的列表,"Arguments"(参数) 和 "pipelines" (管道)是数据计算,在后面的相应部分详细说明。

{{/* a comment */}}
	注释,被丢弃。

{{pipeline}}
	管道的默认文本表示(与fmt.Print相同)将复制到输出中。

{{if pipeline}} T1 {{end}}
	如果管道是空值,不产生输出。
	否则,输出T1。空值包括false, 0, 值为nil的指针和接口值, 长度为0的array, slice, map,字符串。
	不影响游标。

{{if pipeline}} T1 {{else}} T0 {{end}}
	如果管道是空值, 输出T0。
	否则,输出T1。不影响游标。

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
	简化if-else链, 当else操作直接包括一个if操作时,效果等同于:
		{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}

{{range pipeline}} T1 {{end}}
	管道的值必须是一个array, slice, map, 或channel。
	如果管道的值的长度为0,不输出。
	否则,游标连续设置为array,slice,map的元素并输出T1。如果是值是map,而且其键为基本类型并存在自然顺序,将按键的排序顺序遍历元素。

{{range pipeline}} T1 {{else}} T0 {{end}}
	管道的值必须是一个array, slice, map, 或channel。
	如果pipeline的长度为0,不影响游标且输出T0;否则,游标连续设置为array,slice,map的元素并输出T1。

{{template "name"}}
	指定名称的模板将应用nil数据的方式执行。

{{template "name" pipeline}}
	指定名称的模板将应用游标为管道的方式执行。

{{block "name" pipeline}} T1 {{end}}
	块是用来定义并执行模板的简写形式:
		{{define "name"}} T1 {{end}}
		{{template "name" pipeline}}
	典型的用途是定义一组根模板,然后通过重新定义其中的块模板从而自定义。

{{with pipeline}} T1 {{end}}
	如果管道的值为空,则不会生成输出; 否则,游标被设置为管道的值并且T1被执行。

{{with pipeline}} T1 {{else}} T0 {{end}}
	如果管道的值为空,则游标不受影响并且T0被执行; 否则,dot被设置为管道的值并执行T1。

参数

参数是一个简单的值,表示为以下之一。

- 一个Go语言中的布尔值、字符串、字符、整数、浮点数、虚数、复数常量。 这些行为就像Go的未定义类型常量一样。
- nil,表示Go中的未定义类型nil。
- '.',结果为游标值。
- 变量名称, 跟在$后的数字字母组成的字符串(可能为空), 如
	$piOver2
  或
	$
  结果为变量值。
  
  变量描述如下。
- struct字段名, 前面有一个“.”,如:
	.Field
  结果为字段值。可以链式调用字段:
    .Field1.Field2
  还可以对变量进行字段计算,包括链接:
    $x.Field1.Field2
- map的键名,前面有一个“.”,如:
	.Key
  结果为key对应的value值。
  键调用可以链接并与字段组合任意深度:
    .Field1.Key1.Field2.Key2
  尽管key必须是字母数字标识符,但与字段名称不同,不需要以大写字母开头。
  键也可以在变量上进行计算,包括链接:
    $x.key1.key2
- 数据的方法名,前面有一个“.”,如:
	.Method
  结果为以游标为接收者执行该方法的返回值, dot.Method()。方法必须返回一个(可以是任意类型)或两个值, 第二个值为err。
  如err不为nil, 执行中断,并将err作为执行的返回值,返回给调用者。
  方法调用可以链接并与字段、键组成任意深度:
    .Field1.Key1.Method1.Field2.Key2.Method2
  键也可以在变量上进行计算,包括链接:
    $x.Method1.Field
- 函数名,如:
	fun
  结果为执行函数返回值, fun()。返回值表现与方法中相同。函数和函数名描述如下:
- 括号用于分组。结果参与字段或映射键调用。
	print (.F1 arg1) (.F2 arg2)
	(.StructValuedMethod "arg").Field

参数可以计算为任何类型; 如果它们是指针,则实现会在需要时自动指向基本类型。 如果评估产生一个函数值,例如结构体的函数值域,该函数不会自动调用,但它可以用作if操作等的真值。 要调用它,请使用下面定义的调用函数。

管道

管道是一个可能链接的 “命令” 序列。 一个命令是一个简单的值(参数)或一个函数或方法调用,可能有多个参数。

Argument
	参数的评估值。
.Method [Argument...]
	该方法可以是单独的,也可以是链中的最后一个元素,但与链中间的方法不同,它可能需要参数。 结果是使用参数调用方法的值:
		dot.Method(Argument1, etc.)
functionName [Argument...]
	结果是调用该名称函数的值:
		function(Argument1, etc.)
	函数和函数名称如下所述。

管道可以通过用管道符'|'分隔一系列命令来“链接”。 在链式管道中,每个命令的结果都作为后续命令的最后一个参数传递。 管道中最终命令的输出是管道的值。

命令的输出可以是一个值或两个值,其中第二个具有类型错误。 如果第二个值存在并且评估为非零,则执行终止,并将错误返回给Execute的调用者。

变量

操作中的管道可以初始化一个变量来捕获结果。 初始化语法为:

$variable := pipeline

其中variable是变量的名称。 声明变量的操作不会产生输出。

如果一个“range”操作初始化一个变量,则该变量将设置为迭代的连续元素。 另外,“范围”可以声明两个变量,用逗号分隔。

range $index, $element := pipeline

在这种情况下,index和element分别被设置为数组/slice的索引和元素或map的键和元素的连续值。 请注意,如果只有一个变量,则会赋值元素; 这与Go range的约定冲突。

变量的范围扩展到控制结构(“if”,“with”或“range”)的“end”操作,在没有控制结构的情况下扩展到模板的末尾。 模板调用不会从其调用点继承变量。

当执行开始时,$被设置为传递给Execute的数据参数,也就是点的起始值。

示例

下面是一些演示流水线和变量的单行模板示例。 所有输出带引号的单词“output”:

{{"\"output\""}}
	一个字符串常量。
{{`"output"`}}
	一个原始字符串常量。
{{printf "%q" "output"}}
	一个函数调用。
{{"output" | printf "%q"}}
	参数来自前一个命令结果的函数调用。
{{printf "%q" (print "out" "put")}}
	一个带括号的参数。
{{"put" | printf "%s%s" "out" | printf "%q"}}
	一个更展开的函数调用。
{{"output" | printf "%s" | printf "%q"}}
	一个长链条。
{{with "output"}}{{printf "%q" .}}{{end}}
	一个使用.的with操作。
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
	一个创建并使用变量的with操作。
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
	一个在另一个操作中使用变量的with操作。
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
	相同,但使用管道。

函数

在执行期间,函数可以在两个函数表中找到:首先在模板中,然后在全局函数表中找到。 默认情况下,模板中没有定义函数,但可以使用Funcs方法添加它们。

预定义的全局函数名称如下。

and
	返回参数的布尔AND,通过返回第一个空参数会最后一个参数,也就是
	"and x y" 表现为 "if x then y else x"。所有参数被计算。
	//非原文补充:如果熟悉javascript的&&操作符逻辑,就容易理解,这与其类同
call
	调用第一个参数,其必须为函数/方法,后续参数为目标方法的参数。
	因此 "call .X.Y 1 2" , 在Go语言中为 dot.X.Y(1, 2), Y是方法字段或map的key。
	第一个参数必须是产生函数类型值的评估结果(与预定义函数(如print)不同)。 
	该函数必须返回一个或两个结果值,其中第二个结果值是类型错误。 
	如果参数与函数不匹配,或者返回的错误值非零,则停止执行。
html
	返回参数的文本表示的HTML转义。该方法无法在html/template中使用, 存在错误。
index
	返回第一个参数的后续参数表示的索引的目标元素值。如"index x 1 2 3"在Go语言中为
	x[1][2][3]。被索引对象必须是map,slice,array。
js
	返回参数的文本表示的javascript转义。
len
	返回参数的长度。
not
	返回单个参数的布尔NOT。
or
	返回参数的布尔OR,通过返回第一个非空元素,或最后一个元素。即
	"or x y"表现为"if x then x else y"。所有参数都被计算。
	//非原文补充:理解了javascript的||,则容易理解该逻辑。
print
	fmt.Sprint的别名
printf
	fmt.Sprintf的别名
println
	fmt.Sprintln的别名
urlquery
	返回参数的文本表示的URL查询字符串转义。
	该方法无法在html/template中使用, 存在错误。

布尔函数将任何零值设为false,并将非零值设为true。

还有一组定义为函数的二元比较运算符:

eq
	返回 arg1 == arg2 的布尔值
ne
	返回 arg1 != arg2 的布尔值
lt
	返回 arg1 < arg2 的布尔值
le
	返回 arg1 <= arg2 的布尔值
gt
	返回 arg1 > arg2 的布尔值
ge
	返回 arg1 >= arg2 的布尔值

对于更简单的多路相等测试,eq(只)接受两个或更多参数,并将第一个与第二个及后续参数相比,等效于:

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

(与Go中的||不同,然而,eq是一个函数调用,所有参数都将被计算。)

比较函数仅适用于基本类型(或命名的基本类型,例如“type Celsius float32”)。 它们实现Go语言的比较规则,但忽略大小和确切类型,因此可以将任何有符号或无符号的整数值与任何其他整数值进行比较。 (算术值被比较,而不是位模式,因此所有负整数都小于所有无符号整数。)但是,像往常一样,不能将int与float32等进行比较。

关联的模板

每个模板都由创建时指定的字符串命名。 而且,每个模板都与零个或多个其他模板相关联,可以按名称调用它们; 这种关联是可传递的,并形成模板的命名空间。

模板可以使用模板调用来实例化另一个相关联的模板; 请参阅上面“模板”操作的解释。 该名称必须是与包含该调用的模板关联的模板的名称。

嵌套的模板定义

解析模板时,可能会定义另一个模板并将其与正在解析的模板关联。 模板定义必须出现在模板的顶层,非常像Go程序中的全局变量。

这些定义的语法是用“定义”和“结束”操作包围每个模板声明。

定义操作通过提供字符串常量来命名正在创建的模板。 这是一个简单的例子:

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

这定义了两个模板T1和T2,以及第三个T3在执行时调用其他两个模板。 最后它调用T3。 如果执行此模板将生成文本

ONE TWO

通过构建,模板可以仅存在于一个关联中。 如果必须有可从多个关联中寻址的模板,则必须多次解析模板定义以创建不同的* Template值,或者必须使用Clone或AddParseTree方法复制该模板定义。

可以多次调用解析来组装各种关联的模板; 请参阅ParseFiles和ParseGlob函数和方法,以获取解析存储在文件中的相关模板的简单方法。

模板可以直接执行或通过ExecuteTemplate执行,后者执行由名称标识的相关模板。 为了调用我们上面的例子,我们可以写,

err := tmpl.Execute(os.Stdout, "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}

或通过名称显式调用特定的模板,

err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}
posted @ 2019-05-06 12:55  redreampt  阅读(253)  评论(0编辑  收藏  举报