原文连接 php
不少语言都有不少方式将字符串从一只形式转换成另外一种形式。Go 使用模板的方法经过提供一个对象做为参数来转换字符串。这个通常来说是用来将对象插入到HTML中的,不过它一样能够用在其余的状况下。注意这部分跟网络编程毫无关系,不过对于网络编程来讲颇有用。html
大多数后端语言都可以将动态生成的组件插入到静态页面中,例如一个list。典型的例子像JSP,PHP等等。Go 采用了一个相对来讲简单的脚本语言。
从新编写一个template包已经经过了。关于template包的文档不多。如今还能够在old/template
,中找到。如今在参考页尚未相关的包的文档。模板的变化能够再r60 (released 2011/09/07)中找到。
咱们在这里描述下新包。这个包设计的目的是经过使用一个对象的值改变原始文本从而达到输入一个文本输出一个不一样的文本的目的。跟JSP或者其余的不一样,Go的模板并无限制必须使用HTML文件,这样就最大程度的使用它。
被称做模板的原始文件会包含了没有被改变的文本和能够改变文本的命令。命令由”{ {} }” 分隔,跟JSP的命令<%= … =%> 和PHP的命令<?php … ?>类似。 golang
一个模板应用到Go的对象上。Go对象的字段能够插入到模板中,同时也可深刻到对象的字段,查找子字段等。当前对象使用”.”表明,所以若是插入的值是一个字符串就能够直接是用{ {.} }来表示。template经过使用fmt来将对象转换为字符串。
须要使用前缀’.’来将当前对象的字段插入到模板中,例以下面这个对象类型: 正则表达式
type Person struct { Name string Age int Emails []string Jobs []*Job }
The name is {{.Name}}. The age is {{.Age}}.
range
命令来循环访问数组或者列表中的元素,全部想要访问Emails的信息可使用以下代码:
{{range .Emails}}
...
{{end}}
type Job struct { Employer string Role string }
{{with .Jobs}} {{range .}} An employer is {{.Employer}} and the role is {{.Role}} {{end}} {{end}}
这个命令不只仅能够用在数组中,它能够用在任何字段。
当咱们有了一个模板后,咱们能够将其应用到一个对象中,经过将对象插入到模板中来生成新的字符串。包括解析模板,将模板应用到对象两步处理。而后结果能够写到Writer中输出。例如: 编程
t := template.New("Person template") t, err := t.Parse(templ) if err == nil { buff := bytes.NewBufferString("") t.Execute(buff, person) }
/** * PrintPerson */ package main import ( "fmt" "html/template" "os" ) type Person struct { Name string Age int Emails []string Jobs []*Job } type Job struct { Employer string Role string } const templ = `The name is {{.Name}}. The age is {{.Age}}. {{range .Emails}} An email is {{.}} {{end}} {{with .Jobs}} {{range .}} An employer is {{.Employer}} and the role is {{.Role}} {{end}} {{end}} ` func main() { job1 := Job{Employer: "Monash", Role: "Honorary"} job2 := Job{Employer: "Box Hill", Role: "Head of HE"} person := Person{ Name: "jan", Age: 50, Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"}, Jobs: []*Job{&job1, &job2}, } t := template.New("Person template") t, err := t.Parse(templ) checkError(err) err = t.Execute(os.Stdout, person) checkError(err) } func checkError(err error) { if err != nil { fmt.Println("Fatal error ", err.Error()) os.Exit(1) } }
输出结果是:注意这里有不少空格输出,是由于这些空格在原始字符串就有,若是想要减小空格能够以下输入:The name is jan. The age is 50. An email is jan@newmarch.name An email is jan.newmarch@gmail.com An employer is Monash and the role is Honorary An employer is Box Hill and the role is Head of HE输出结果是:{{range .Emails}} An email is {{.}} {{end}}
在这个例子中咱们使用了一个字符串做为模板,咱们一样可使用template.ParseFiles()
方法从一个文件中获取模板。 后端
上面的转换掺入了一些文本到模板中。这些文本基本上都是随意的,无论这些文本是什么。若是咱们想要将他们插入到HTML文件或者其余形式的文件中,这样咱们就须要对一些字符进行转义。例如,为了再HTML中显示任意文本,咱们不得不讲”<” 转换为”<”。Go 的模板有不少内建的函数,其中一个就是’html’,这个函数跟unix的管道很想,从标准输入中读取而后写到标准输出。
取当前对象的值并将其转义输出到HTML中,以下: 数组
{{ . | html }}
其余的函数使用方法相同。 网络
模板经过使用一个对象插入相关值,经过使用fmt
将对象转为字符串。有时候这并非咱们想要的。例如为了防止垃圾邮件发送的人获得你的邮箱,就须要将”@”转为“at”。例如“jane at newmarch.name”。若是咱们想要template这样输出,咱们能够编写一个转换函数。
每个模板都有一个名字供本身使用,同时能够关联一个Go的函数。这个经过下面这个类型关联: 函数
type FuncMap map[string]interface{}
EmailExpander
和
emailExpand
关联,我么能够在template中加入这个语句:
t = t.Funcs(template.FuncMap("emailExpand", EmailExpander))
EmailExpander
的签名以下:
func EmailExpander(args ...interface{}) string
/** * PrintEmails */ package main import ( "fmt" "os" "strings" "text/template" ) type Person struct { Name string Emails []string } const templ = `The name is {{.Name}}. {{range .Emails}} An email is "{{. | emailExpand}}" {{end}} ` func EmailExpander(args ...interface{}) string { ok := false var s string if len(args) == 1 { s, ok = args[0].(string) } if !ok { s = fmt.Sprint(args...) } // find the @ symbol substrs := strings.Split(s, "@") if len(substrs) != 2 { return s } // replace the @ by " at " return (substrs[0] + " at " + substrs[1]) } func main() { person := Person{ Name: "jan", Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"}, } t := template.New("Person template") // add our function t = t.Funcs(template.FuncMap{"emailExpand": EmailExpander}) t, err := t.Parse(templ) checkError(err) err = t.Execute(os.Stdout, person) checkError(err) } func checkError(err error) { if err != nil { fmt.Println("Fatal error ", err.Error()) os.Exit(1) } }
The name is jan. An email is "jan at newmarch.name" An email is "jan.newmarch at gmail.com"
模板包容许咱们定义并使用变量。为了这个目的,咱们能够在每一个email的输出前加一个名字做为前缀,入下: spa
type Person struct { Name string Emails []string }
{{range .Emails}}
{{.}}
{{end}}
{{$name := .Name}} {{range .Emails}} Name is {{$name}}, email is {{.}} {{end}}
/** * PrintNameEmails */ package main import ( "html/template" "os" "fmt" ) type Person struct { Name string Emails []string } const templ = `{{$name := .Name}} {{range .Emails}} Name is {{$name}}, email is {{.}} {{end}} ` func main() { person := Person{ Name: "jan", Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"}, } t := template.New("Person template") t, err := t.Parse(templ) checkError(err) err = t.Execute(os.Stdout, person) checkError(err) } func checkError(err error) { if err != nil { fmt.Println("Fatal error ", err.Error()) os.Exit(1) } }
Name is jan, email is jan@newmarch.name Name is jan, email is jan.newmarch@gmail.com
继续咱们Person的例子,假设咱们想要输出emails的列表而不是深刻这个字段,那么咱们能够这么写模板:
Name is {{.Name}} Emails are {{.Emails}}
Name is jan Emails are [jan@newmarch.name jan.newmarch@gmail.com]
这个就是fmt输出的格式。
若是这是你想要的记过,那么在大多数状况下这样输出是没有问题的。让咱们考虑一下那里差很少。有个JSON的包用来序列化对象,这个咱们在第四章 讲过。他的输出方式为:
{"Name": "jan", "Emails": ["jan@newmarch.name", "jan.newmarch@gmail.com"] }
{"Name": "{{.Name}}", "Emails": {{.Emails}} }
{"Name": "jan", "Emails": [jan@newmarch.name jan.newmarch@gmail.com] }
这个还有两个问题,邮箱地址没有被引号括起来,列表没有用逗号隔开。
若是用下面的形式怎么样?
{"Name": {{.Name}}, "Emails": [ {{range .Emails}} "{{.}}", {{end}} ] }
{"Name": "jan", "Emails": ["jan@newmarch.name", "jan.newmarch@gmail.com",] }
差很少正确了,不过仔细看。你会发如今列表末尾的元素有个逗号,根据JSON规范,是不容许这样实现的。
咱们能够经过if语句来搞定这个问题,以下:
{"Name": "{{.Name}}", "Emails": [ {{range $index, $elmt := .Emails}} {{if $index}} , "{{$elmt}}" {{else}} "{{$elmt}}" {{end}} {{end}} ] }
这个程序以下:
/** * PrintJSONEmails */ package main import ( "html/template" "os" "fmt" ) type Person struct { Name string Emails []string } const templ = `{"Name": "{{.Name}}", "Emails": [ {{range $index, $elmt := .Emails}} {{if $index}} , "{{$elmt}}" {{else}} "{{$elmt}}" {{end}} {{end}} ] } ` func main() { person := Person{ Name: "jan", Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"}, } t := template.New("Person template") t, err := t.Parse(templ) checkError(err) err = t.Execute(os.Stdout, person) checkError(err) } func checkError(err error) { if err != nil { fmt.Println("Fatal error ", err.Error()) os.Exit(1) } }
这个就会是一个正确的输出。
在结束本章以前,我么发现处理逗号能够经过定义一个Go的函数来实现。为了重用咱们能够这样写:
/** * Sequence.go * Copyright Roger Peppe */ package main import ( "errors" "fmt" "os" "text/template" ) var tmpl = `{{$comma := sequence "" ", "}} {{range $}}{{$comma.Next}}{{.}}{{end}} {{$comma := sequence "" ", "}} {{$colour := cycle "black" "white" "red"}} {{range $}}{{$comma.Next}}{{.}} in {{$colour.Next}}{{end}} ` var fmap = template.FuncMap{ "sequence": sequenceFunc, "cycle": cycleFunc, } func main() { t, err := template.New("").Funcs(fmap).Parse(tmpl) if err != nil { fmt.Printf("parse error: %v\n", err) return } err = t.Execute(os.Stdout, []string{"a", "b", "c", "d", "e", "f"}) if err != nil { fmt.Printf("exec error: %v\n", err) } } type generator struct { ss []string i int f func(s []string, i int) string } func (seq *generator) Next() string { s := seq.f(seq.ss, seq.i) seq.i++ return s } func sequenceGen(ss []string, i int) string { if i >= len(ss) { return ss[len(ss)-1] } return ss[i] } func cycleGen(ss []string, i int) string { return ss[i%len(ss)] } func sequenceFunc(ss ...string) (*generator, error) { if len(ss) == 0 { return nil, errors.New("sequence must have at least one element") } return &generator{ss, 0, sequenceGen}, nil } func cycleFunc(ss ...string) (*generator, error) { if len(ss) == 0 { return nil, errors.New("cycle must have at least one element") } return &generator{ss, 0, cycleGen}, nil }
Go的模板对于一些将对象插入到模板的文本转换颇有帮助。他没有使用功能强大的正则表达式,可是相比正则表达式它更快更易用。