( template 包的文档可以从 golang.org/pkg/template/ 获取)
在上一节中,我们使用了模板去合并结构体与 html 模板中的数据。这对于构建 web 应用程序确实非常有用,但是模板技术比这更通用:数据驱动模板可以用于生成文本输出,HTML 仅仅是其中一个特例。
通过执行 template 将模板与数据结构合并,在大多数情况下是一个结构体或者一个结构体的切片。它可以重写一段文本,通过传递给 templ.Execute () 的数据项进行替换生成新的内容。只有能被导出的数据项才可以用于模板合并。操作可以是数据评估或控制结构,并通过 「{{」和「}}」定义。数据项可以是值或者指针;接口隐藏了间接引用(译者注:个人觉得应该是接口将传递的是值还是指针忽略了,因为实际使用中,无论是指针还是值都可以直接通过 .Title
来使用)。
15.7.1. 字段替代: {{.FieldName}}
在模板中包含字段的内容,需要把它放在一个双大括号中,并且在变量的前面加上一个点,例如: 如果 Name 是一个结构体中的字段,并且它的值需要在合并时替换,那么在模板中包含文本 {{.Name}} ;当 Name 是一个 map 的索引时,也可以这样使用。使用 template.New
创建一个新的模板,需要一个字符串的参数来作为模板的名称。正如我们在 15.5 章节 已经遇到的,Parse 方法通过解析一些模板定义的字符串来生成一个 template 作为内部表示,当参数是一个定义好的模板文件的路径时,使用 ParseFile。当解析出现问题的时候,第二个参数会返回 Error != nil 。在最后一步,数据结构通过 execute 方法与模板合并,并且将一个 io.Writer 写入到它的第一个参数;可以再次返回错误。这将在下面程序中说明,输出的结果通过 os.Stdout 显示到控制台:
示例 15.13—template_field.go:
package main
import (
"os"
"text/template"
)
type Person struct {
Name string
nonExportedAgeField string // 译者添加: 原文中没有定义这个,代码会报错
}
func main() {
t := template.New("hello")
t, _ = t.Parse("hello {{.Name}}!")
p := Person{Name:"Mary", nonExportedAgeField: "31"} // data
if err := t.Execute(os.Stdout, p); err != nil {
fmt.Println("There was an error:", err.Error())
}
}
// Output: hello Mary!
我们的结构体包含了一个不能导出的字段,并且当我们尝试通过一个定义字符串去合并他时,像下面这样:
t, _ = t.Parse("your age is {{.nonExportedAgeField}}!")
发生下面错误:There was an error: template:nonexported template hello:1: can’t evaluate field nonExportedAgeField in type main.Person.
译者注: 可能是新版本已经更新了这部分代码,现在(go1.10.1 )已经不会出现上面的错误,如果在结构体中没有定义
nonExportedAgeField
字段,在p := Person{Name:"Mary", nonExportedAgeField: "31"}
的时候就直接编译不过去了,如果定义了nonExportedAgeField
字段(小写开头不能导出),也不会报错,只是模板中定义的{{.nonExportedAgeField}}
不显示内容。但是直接使用{{ . }}
,不管字段是否可以导出,会将两个字段全部输出。
你可以直接在 Execute()
中使用 {{.}}
直接显示两个参数,结果就是: hello {Mary 31}!
当模板应用在浏览器中时,要先用 html 过滤器去过滤输出的内容,像这样: {{html .}}
,或者使用一个 FieldName {{ .FieldName |html }}
|html
部分告诉 template 引擎在输出 FieldName 的值之前要通过 html 格式化它。他会转义特殊的 html 字符( 如:会将 > 替换成 >
), 这样可以防止用户的数据破坏 HTML 表单。
译者注:
|html
用起来就和 Linux 的|
(管道)类似,将前面命令的输出作为|
后面命令的输入
15.7.2. 模板验证
检查模板的语法是否定义正确,对 Parse 的结果执行 Must 函数。在下面的示例中 tOK 是正确, tErr 的验证会出现错误并会导致一个运行时恐慌(panic)!
示例 15.14—template_validation.go:
package main
import (
"text/template"
"fmt"
)
func main() {
tOk := template.New("ok")
// 一个有效的模板,所以 Must 时候不会出现恐慌(panic)
template.Must(tOk.Parse("/*这是一个注释 */ some static text: {{ .Name }}"))
fmt.Println("The first one parsed OK.")
fmt.Println("The next one ought to fail.")
tErr := template.New("error_template")
template.Must(tErr.Parse("some static text {{ .Name }"))
}
/* Output:
The first one parsed OK.
The next one ought to fail.
panic: template: error_template:1: unexpected "}" in command
*/
模板语法中的错误应该不常见,因为会使用像 13.3 章节 中的 defer/recover 机制去报告这个错误并纠正它。
下面的三个基本函数在代码中经常被链接使用,就像:
var strTempl = template.Must(template.New(“TName”).Parse(strTemplateHTML))
练习 15.7: template_validation_recover.go
在上面的示例中,实现 defer/recover 机制。
15.7.3. If-else
输出由 Execute 生成的模板结果中,包含了静态文本和在 {{}} 中包含的文本,它们被称为一个管道。例如: 运行这个代码 (示例程序 15.15 pipeline1.go ):
t := template.New("template test")
t = template.Must(t.Parse("This is just static text. \n{{\"This is pipeline data—because it is evaluated within the double braces.\"}} {{`So is this, but within reverse quotes.`}}\n"))
t.Execute(os.Stdout, nil)
获得这个输出结果:
This is just static text.
This is pipeline data—because it is evaluated within the double braces. So is this, but within reverse quotes.
现在我们可以使用 if-else-end 来调整管道数据的输出: 如果管道是空的,就像:
in: {{if ``}} Will not print. {{end}}
if 条件的判断结果是 false ,并不会输出任何任内容,但是这个:
{{if `anything`}} Print IF part. {{else}} Print ELSE part.{{end}}
Print IF part 将被输出。上面的内容在下面的程序中被说明:
示例 15.16—template_ifelse.go:
package main
import (
"os"
"text/template"
)
func main() {
tEmpty := template.New("template test")
// if 是一个空管道时的内容
tEmpty = template.Must(tEmpty.
Parse("Empty pipeline if demo: {{if ``}} Will not print. {{end}}\n"))
tEmpty.Execute(os.Stdout, nil)
tWithValue := template.New("template test")
// 如果条件满足,则为非空管道
tWithValue = template.Must(tWithValue.
Parse("Non empty pipeline if demo: {{if `anything`}} Will print. {{end}}\n"))
tWithValue.Execute(os.Stdout, nil)
tIfElse := template.New("template test")
// 如果条件满足,则为非空管道
tIfElse = template.Must(tIfElse.
Parse("if-else demo: {{if `anything`}} Print IF part. {{else}} Print ELSE part.{{end}}\n"))
tIfElse.Execute(os.Stdout, nil)
}
/* 输出:
Empty pipeline if demo:
Non empty pipeline if demo: Will print.
if-else demo: Print IF part.
*/
15.7.4. 点与 with-end
在 Go 模板中使用 (.)
: 他的值 {{.}}
被设置为当前管道的值。
with 语句将点的值设置为管道的值。如果管道是空的,就会跳过 with 到 end 之前的任何内容;当嵌套使用时,点会从最近的范围取值。在下面这个程序中会说明:
示例 15.17—template_with_end.go:
package main
import (
"os"
"text/template"
)
func main() {
t := template.New("test")
t, _ = t.Parse("{{with `hello`}}{{.}}{{end}}!\n")
t.Execute(os.Stdout, nil)
t, _ = t.Parse("{{with `hello`}}{{.}} {{with `Mary`}}{{.}}{{end}} {{end}}!\n")
t.Execute(os.Stdout, nil)
}
/* 输出:
hello!
hello Mary!
*/
15.7.5. 模板变量 $
你可以在变量名前加一个「$」符号来为模板中的管道创建一个局部变量。变量名称只能由字母、数字、下划线组成。在下面的示例中,我使用了几种可以使用的变量名称。
示例 15.18—template_variables.go:
package main
import (
"os"
"text/template"
)
func main() {
t := template.New("test")
t = template.Must(t.Parse("{{with $3 := `hello`}}{{$3}}{{end}}!\n"))
t.Execute(os.Stdout, nil)
t = template.Must(t.Parse("{{with $x3 := `hola`}}{{$x3}}{{end}}!\n"))
t.Execute(os.Stdout, nil)
t = template.Must(t.Parse("{{with $x_1 := `hey`}}{{$x_1}} {{.}} {{$x_1}} {{end}}!\n"))
t.Execute(os.Stdout, nil)
}
/* 输出:
hello!
hola!
hey hey hey!*/
*/
15.7.6. Range-end
这个构造的格式:
{{range pipeline}} T1 {{else}} T0 {{end}}
range 在循环的集合中使用: 管道的值必须是一个数组、切片或者 map 。如果管道的值的长度为零,点不会被影响并且 T0 会被执行;否则将点设置成拥有连续元素的数组、切片或者 map, T1 就会被执行。
如果它是模板: {{range .}}
{{.}}
{{end}}
然后是这个代码: s := []int{1,2,3,4}
t.Execute(os.Stdout, s)
将会输出:
1
2
3
4
可以查看 20.7 章节,它是一个更有用的示例,其中来自 App Engine 数据存储的数据通过一个模板展示:
{{range .}}
{{with .Author}}
<p><b>{{html .}}</b> wrote:</p>
{{else}}
<p>An anonymous person wrote:</p>
{{end}}
<pre>{{html .Content}}</pre>
<pre>{{html .Date}}</pre>
{{end}}
range . 这里循环了一个结构体的切片,每个结构体都包含了一个 Author、Content 和 Date 字段。
15.7.7. 预定义模板函数
还可以在你的代码中使用一些预定义的模板函数,例如: 和 fmt.Printf 函数类似的 printf 函数:
示例 15.19—predefined_functions.go:
package main
import (
"os"
"text/template"
)
func main() {
t := template.New("test")
t = template.Must(t.Parse("{{with $x := `hello`}}{{printf `%s %s` $x `Mary`}} {{end}}!\n"))
t.Execute(os.Stdout, nil)
}
// hello Mary!
在 15.6 章节 中也这样使用过:
{{ printf "%s" .Body|html}}
否则 Body 的字节会被当做数字显示(字节默认都是 int8 类型的数字)