原文連接 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的模板對於一些將對象插入到模板的文本轉換頗有幫助。他沒有使用功能強大的正則表達式,可是相比正則表達式它更快更易用。