Files
the-way-to-go_ZH_CN/eBook/15.7.md
2019-07-15 19:55:54 -07:00

286 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 15.7 探索 template 包
template 包的文档可以在 [https://golang.org/pkg/text/template/](https://golang.org/pkg/text/template/) 找到。)
在前一章节,我们使用 template 对象把数据结构整合到 HTML 模板中。这项技术确实对网页应用程序非常有用然而模板是一项更为通用的技术方案数据驱动的模板被创建出来以生成文本输出。HTML 仅是其中的一种特定使用案例。
模板通过与数据结构的整合来生成,通常为结构体或其切片。当数据项传递给 `tmpl.Execute()` ,它用其中的元素进行替换, 动态地重写某一小段文本。**只有被导出的数据项**才可以被整合进模板中。可以在 `{{``}}` 中加入数据求值或控制结构。数据项可以是值或指针,接口隐藏了他们的差异。
## 15.7.1 字段替换:`{{.FieldName}}`
要在模板中包含某个字段的内容,使用双花括号括起以点(`.`)开头的字段名。例如,假设 `Name` 是某个结构体的字段,其值要在被模板整合时替换,则在模板中使用文本 `{{.Name}}`。当 `Name` 是 map 的键时这么做也是可行的。要创建一个新的 Template 对象,调用 `template.New`,其字符串参数可以指定模板的名称。正如 [15.5节](15.5.md) 出现过的,`Parse` 方法通过解析模板定义字符串,生成模板的内部表示。当使用包含模板定义字符串的文件时,将文件路径传递给 `ParseFiles` 来解析。解析过程如产生错误,这两个函数第二个返回值 error != nil。最后通过 `Execute` 方法,数据结构中的内容与模板整合,并将结果写入方法的第一个参数中,其类型为 `io.Writer`。再一次地,可能会有 error 返回。以下程序演示了这些步骤,输出通过 `os.Stdout` 被写到控制台。
示例 15.13 [template_field.go](examples/chapter_15/template_field.go)
```go
package main
import (
"fmt"
"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"}
if err := t.Execute(os.Stdout, p); err != nil {
fmt.Println("There was an error:", err.Error())
}
}
```
输出:`hello Mary!`
数据结构中包含一个未导出的字段,当我们尝试把它整合到类似这样的定义字符串:
```go
t, _ = t.Parse("your age is {{.nonExportedAgeField}}!")
```
会产生错误:
```
There was an error: template: nonexported template hello:1: cant evaluate field nonExportedAgeField in type main.Person.
```
如果只是想简单地把 `Execute()` 方法的第二个参数用于替换,使用 `{{.}}`
当在浏览器环境中进行这些步骤,应首先使用 `html` 过滤器来过滤内容,例如 `{{html .}}` 或者对 `FieldName` 过滤:`{{ .FieldName |html }}`
`|html` 这部分代码,是请求模板引擎在输出 `FieldName` 的结果前把值传递给 html 格式化器,它会执行 HTML 字符转义(例如把 `>` 替换为 `>`)。这可以避免用户输入数据破坏 HTML 文档结构。
## 15.7.2 验证模板格式
为了确保模板定义语法是正确的,使用 `Must` 函数处理 `Parse` 的返回结果。在下面的例子中 `tOK` 是正确的模板, `tErr` 验证时发生错误,会导致运行时 panic。
示例 15.14 [template_validation.go](examples/chapter_15/template_validation.go)
```go
package main
import (
"text/template"
"fmt"
)
func main() {
tOk := template.New("ok")
//a valid template, so no panic with Must:
template.Must(tOk.Parse("/* and a comment */ 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 }"))
}
```
输出:
The first one parsed OK.
The next one ought to fail.
panic: template: error_template:1: unexpected "}" in operand
模板语法出现错误比较少见,可以使用 [13.3节](13.3.md) 概括的 `defer/recover` 机制来报告并纠正错误。
在代码中常见到这 3 个基本函数被串联使用:
```go
var strTempl = template.Must(template.New("TName").Parse(strTemplateHTML))
```
练习 15.7 [template_validation_recover.go](exercises/chapter_15/template_validation_recover.go)
在上述示例代码上实现 defer/recover 机制。
## 15.7.3 `If-else`
运行 `Execute` 产生的结果来自模板的输出,它包含静态文本,以及被 `{{}}` 包裹的称之为*管道*的文本。例如,运行这段代码(示例 15.15 [pipline1.go](examples/chapter_15/pipeline1.go)
```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` 设置条件约束:如果管道是空的,类似于:
```html
{{if ``}} Will not print. {{end}}
```
那么 `if` 条件的求值结果为 `false`,不会有输出内容。但如果是这样:
```html
{{if `anything`}} Print IF part. {{else}} Print ELSE part.{{end}}
```
会输出 `Print IF part.`。以下程序演示了这点:
示例 15.16 [template_ifelse.go](examples/chapter_15/template_ifelse.go)
```go
package main
import (
"os"
"text/template"
)
func main() {
tEmpty := template.New("template test")
tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} Will not print. {{end}}\n")) //empty pipeline following if
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")) //non empty pipeline following if condition
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")) //non empty pipeline following if condition
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](examples/chapter_15/template_with_end.go)
```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](examples/chapter_15/template_variables.go)
```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-end` 结构格式为:`{{range pipeline}} T1 {{else}} T0 {{end}}`
`range` 被用于在集合上迭代:管道的值必须是数组、切片或 map。如果管道的值长度为零点号的值不受影响且执行 `T0`;否则,点号被设置为数组、切片或 map 内元素的值,并执行 `T1`
如果模板为:
```html
{{range .}}
{{.}}
{{end}}
```
那么执行代码:
```go
s := []int{1,2,3,4}
t.Execute(os.Stdout, s)
```
会输出:
```
1
2
3
4
```
如需更实用的示例,请参考 [20.7节](20.7.md),来自 App Engine 数据库的数据通过模板来显示:
```html
{{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 模板预定义函数
也有一些可以在模板代码中使用的预定义函数,例如 `printf` 函数工作方式类似于 `fmt.Sprintf`
示例 15.19 [predefined_functions.go](examples/chapter_15/predefined_functions.go)
```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节](15.6.md) 中使用:`{{ printf "%s" .Body|html}}`,否则字节切片 `Body` 会作为数字序列打印出来。
## 链接
- [目录](directory.md)
- 上一节:[用模板编写网页应用](15.6.md)
- 下一节:[精巧的多功能网页服务器](15.8.md)