翻译进度
10
分块数量
2
参与人数

15.7. 探索 Template 扩展的功能

这是一篇社区协同翻译的文章,你可以点击右边区块信息里的『改进』按钮向译者提交改进建议。

( template 包的文档可以从 http://golang.org/pkg/template/ 获取)

在上一节中,我们使用了模板去合并结构体与 html 模板中的数据。这对于构建 web 应用程序确实非常有用,但是模板技术比这更通用:数据驱动模板可以用于生成文本输出,HTML 仅仅是其中一个特例。

通过执行 template 将模板与数据结构合并,在大多数情况下是一个结构体或者一个结构体的切片。它可以重写一段文本,通过传递给 templ.Execute() 的数据项进行替换生成新的内容。只有能被导出的数据项才可以用于模板合并。操作可以是数据评估或控制结构,并通过 「{{」和「}}」定义。数据项可以是值或者指针;接口隐藏了间接引用(译者注:个人觉得应该是接口将传递的是值还是指针忽略了,因为实际使用中,无论是指针还是值都可以直接通过 .Title 来使用)。

BroQiang 翻译于 6个月前

15.7.1. 字段替代: {{.FieldName}}

在模板中包含字段的内容,需要把它放在一个双大括号中,并且在变量的前面加上一个点,例如: 如果 Name 是一个结构体中的字段,并且它的值需要在合并时替换,那么在模板中包含文本 {{.Name}} ;当 Name 是一个 map 的索引时,也可以这样使用。使用 template.New 创建一个新的模板,需要一个字符串的参数来作为模板的名称。正如我们在 15.5 章节 已经遇到的,Parse 方法通过解析一些模板定义的字符串来生成一个 template 作为内部表示,当参数是一个定义好的模板文件的路径时,使用 ParseFile。当解析出现问题的时候,第二个参数会返回 Error != nil 。在最后一步,数据结构通过 execute 方法与模板合并,并且将一个 io.Writer 写入到它的第一个参数;可以再次返回错误。这将在下面程序中说明,输出的结果通过 os.Stdout 显示到控制台:

BroQiang 翻译于 6个月前

示例 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}}!")
BroQiang 翻译于 6个月前

发生下面错误: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 的 | (管道)类似,将前面命令的输出作为 | 后面命令的输入

BroQiang 翻译于 6个月前

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 机制。

BroQiang 翻译于 6个月前

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.

*/

BroQiang 翻译于 6个月前

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!

*/

BroQiang 翻译于 6个月前

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!*/

*/

BroQiang 翻译于 6个月前

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 字段。

BroQiang 翻译于 6个月前

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 类型的数字)

BroQiang 翻译于 6个月前

本文章首发在 GolangCaff
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

参与译者:2
讨论数量: 0
发起讨论


暂无话题~