導入方式:
import "text/template"
template包實作了資料驅動的用于生成文本輸出的模闆。其實簡單來說就是将一組文本嵌入另一組文本模版中,傳回一個你期望的文本
如果要生成HTML格式的輸出,參見html/template包,該包提供了和本包相同的接口,但會自動将輸出轉化為安全的HTML格式輸出,可以抵抗一些網絡攻擊。
用作模闆的輸入文本必須是utf-8編碼的文本。"Action",即資料運算和控制機關由"{{"和"}}"界定(即{{Action}});在Action之外的所有文本都不做修改的拷貝到輸出中。Action内部不能有換行,但注釋可以有換行。
{{Action}}中的運算可以通過()進行分組,如:
//執行結果可以通路其字段或者鍵對應的值:
print (.F1 arg1) (.F2 arg2)
(.StructValuedMethod "arg").Field
經解析生成模闆後,一個模闆可以安全的并發執行。
有兩個常用的傳入參數的類型:
- 一個是struct,在模闆内可以讀取該struct域的内容來進行渲染。下面的例子中使用更多的是這種方法,即自定義一個struct,然後将其值作為Execute()函數的第二個參數,然後.就表示該對象,然後通過它來調用相應的字元串值,甚至是函數
- 一個是map[string]interface{},在模闆内可以使用key來進行渲染
type Template
type Template struct {
*parse.Tree
// 内含隐藏或非導出字段
}
代表一個解析好的模闆,*parse.Tree字段僅僅是暴露給html/template包使用的,是以其他人應該視字段未導出。
func New
func New(name string) *Template
建立一個名為name的模闆。
func (*Template) Parse
func (t *Template) Parse(text string) (*Template, error)
Parse方法将字元串text解析為模闆。嵌套定義的模闆會關聯到最頂層的t。Parse可以多次調用,但隻有第一次調用可以包含空格、注釋和模闆定義之外的文本。如果後面的調用在解析後仍剩餘文本會引發錯誤、傳回nil且丢棄剩餘文本;如果解析得到的模闆已有相關聯的同名模闆,會覆寫掉原模闆。
func (*Template) Execute
func (t *Template) Execute(wr io.Writer, data interface{}) (err error)
Execute方法将解析好的模闆應用到data上,并将輸出寫入wr。如果執行時出現錯誤,會停止執行,但有可能已經寫入wr部分資料。模闆可以安全的并發執行。
1.調用的是變量時
1)舉一個最簡單的例子,傳入的是string字元串:
package main
import(
"os"
"text/template"
)
func main() {
str := "world"
tmpl, err := template.New("test").Parse("hello, {{.}}\n") //建立一個名字為test的模版"hello, {{.}}"
if err != nil{
panic(err)
}
err = tmpl.Execute(os.Stdout, str) //将str的值合成到tmpl模版的{{.}}中,并将合成得到的文本輸入到os.Stdout,傳回hello, world
if err != nil{
panic(err)
}
}
{{.}}:此标簽輸出目前對象的值,在這裡即代表str對象,但是在下面的例子中,.代表的是Execute中傳入的sweaters對象的值
2)另一個例子,傳入的是struct對象的值:
package main
import(
"os"
"text/template"
)
type Inventory struct {
Material string
Count uint
}
func main() {
sweaters := Inventory{"wool", 17}
tmpl, err := template.New("test").Parse("{{.Count}} of {{.Material}}\n")//{{.Count}}擷取的是struct對象中的Count字段的值
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)//傳回 17 of wool
if err != nil { panic(err) }
}
如果上面的例子中Count的值也是一個struct對象,可以使用{{.Count.Field1}}來通路其字段
3)自定義的變量,可以先看下面的方法的例子
舉例:
package main
import(
"os"
"text/template"
)
type MyMethod struct{
Say string
Name string
}
func (my *MyMethod)SayHello() string{//沒參數
return "world"
}
func (my *MyMethod)SayYouName(name string) string { //有參數
return "my name is : " + name
}
func main() {
mine := &MyMethod{ Say : "hello", Name : "student"}
//先對變量$str1,$str2,$str3指派,一個是直接将字元串值指派,另兩個是調用函數,将傳回值指派,然後再将變量值輸出
tmpl, err := template.New("test").Parse("{{$str1 := .Say}}{{$str2 := .SayHello}}{{$str3 := .SayYouName .Name}}{{$str1}} {{$str2}}\n{{$str3}}\n")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, mine)
if err != nil {
panic(err)
}
}
傳回:
bogon:~ user$ go run testGo.go
hello world
my name is : student
2.函數:
執行模闆時,函數從兩個函數字典中查找:首先是模闆函數字典,然後是全局函數字典。一般不在模闆内定義函數,而是使用Funcs方法添加函數到模闆裡。
方法必須有一到兩個傳回值,如果是兩個,那麼第二個一定是error接口類型
1)模版内定義函數
舉例:
package main
import(
"os"
"text/template"
)
type MyMethod struct{
Name string
}
func (my *MyMethod)SayHello() string{//沒參數
return "hello world"
}
func (my *MyMethod)SayYouName(name string) string { //有參數
return "my name is : " + name
}
func main() {
mine := &MyMethod{ Name : "boss"}
tmpl, err := template.New("test").Parse("{{.SayHello}}\n{{.SayYouName .Name}}\n")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, mine)
if err != nil {
panic(err)
}
}
傳回:
bogon:~ user$ go run testGo.go
hello world
my name is : boss
2)使用Funcs方法添加函數到模闆
func (*Template) Funcs
func (t *Template) Funcs(funcMap FuncMap) *Template
Funcs方法向模闆t的函數字典裡加入參數funcMap内的鍵值對。如果funcMap某個鍵值對的值不是函數類型或者傳回值不符合要求會panic。但是,可以對t函數清單的成員進行重寫。方法傳回t以便進行鍊式調用。
type FuncMap
type FuncMap map[string]interface{}
FuncMap類型定義了函數名字元串到函數的映射,每個函數都必須有1到2個傳回值,如果有2個則後一個必須是error接口類型;如果有2個傳回值的方法傳回的error非nil,模闆執行會中斷并傳回給調用者該錯誤。
舉例:
package main
import(
"os"
"text/template"
)
func SayHello() string{//沒參數
return "hello world"
}
func SayYouName(name string) string { //有參數
return "my name is : " + name
}
func main() {
funcMap := template.FuncMap{
//在FuncMap中聲明相應要使用的函數,然後就能夠在template字元串中使用該函數
"SayHello" : SayHello,
"SayYouName" : SayYouName,
}
name := "boss"
tmpl, err := template.New("test").Funcs(funcMap).Parse("{{SayHello}}\n{{SayYouName .}}\n")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, name)
if err != nil {
panic(err)
}
}
傳回:
bogon:~ user$ go run testGo.go
hello world
my name is : boss
根據pipeline的定義,上面的{{SayYouName .}}等價于{{.|SayYouName}}
預定義的全局函數如下:
就是在{{}}中可以直接使用的函數
and
函數傳回它的第一個empty參數或者最後一個參數;
就是說"and x y"等價于"if x then y else x";所有參數都會執行;
or
傳回第一個非empty參數或者最後一個參數;
亦即"or x y"等價于"if x then x else y";所有參數都會執行;
not
傳回它的單個參數的布爾值的否定
len
傳回它的參數的整數類型長度
index
執行結果為第一個參數以剩下的參數為索引/鍵指向的值;
如"index x 1 2 3"傳回x[1][2][3]的值;每個被索引的主體必須是數組、切片或者字典。
print
即fmt.Sprint
printf
即fmt.Sprintf
println
即fmt.Sprintln
html
傳回其參數文本表示的HTML逸碼等價表示。
urlquery
傳回其參數文本表示的可嵌入URL查詢的逸碼等價表示。
js
傳回其參數文本表示的JavaScript逸碼等價表示。
call
執行結果是調用第一個參數的傳回值,該參數必須是函數類型,其餘參數作為調用該函數的參數;
如"call .X.Y 1 2"等價于go語言裡的dot.X.Y(1, 2);
其中Y是函數類型的字段或者字典的值,或者其他類似情況;
call的第一個參數的執行結果必須是函數類型的值(和預定義函數如print明顯不同);
該函數類型值必須有1到2個傳回值,如果有2個則後一個必須是error接口類型;
如果有2個傳回值的方法傳回的error非nil,模闆執行會中斷并傳回給調用模闆執行者該錯誤;
布爾函數會将任何類型的零值視為假,其餘視為真。
舉例:
package main
import(
"os"
"text/template"
)
func main() {
name := "boss"
tmpl, err := template.New("test").Parse(`{{printf "%q\n" .}}`)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, name)
if err != nil {
panic(err)
}
}
傳回:
bogon:~ user$ go run testGo.go
"boss"
下面是定義為函數的二進制比較運算的集合:
eq 如果arg1 == arg2則傳回真
ne 如果arg1 != arg2則傳回真
lt 如果arg1 < arg2則傳回真
le 如果arg1 <= arg2則傳回真
gt 如果arg1 > arg2則傳回真
ge 如果arg1 >= arg2則傳回真
為了簡化多參數相等檢測,eq(隻有eq)可以接受2個或更多個參數,它會将第一個參數和其餘參數依次比較,傳回下式的結果:
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
(和go的||不一樣,不做惰性運算,所有參數都會執行)
比較函數隻适用于基本類型(或重定義的基本類型,如"type Celsius float32")。它們實作了go語言規則的值的比較,但具體的類型和大小會忽略掉,是以任意類型有符号整數值都可以互相比較;任意類型無符号整數值都可以互相比較;等等。但是,整數和浮點數不能互相比較。
3.pipelines
這上面舉的所有例子中的{{ }}内的操作我們将其稱作pipelines
pipeline通常是将一個command序列分割開,再使用管道符'|'連接配接起來(但不使用管道符的command序列也可以視為一個管道),上面的例子都是最簡單的pipelines的類型,因為一個{{}}中隻有一個command。而上面自定義變量中的文法為:
$variable := pipeline
更複雜的有:
range $index, $element := pipeline
這時,$index和$element分别設定為數組/切片的索引或者字典的鍵,以及對應的成員元素。注意這和go range從句隻有一個參數時設定為索引/鍵不同!
一個變量的作用域隻到聲明它的控制結構("if"、"with"、"range")的"end"為止,如果不是在控制結構裡聲明會直到模闆結束為止。子模闆的調用不會從調用它的位置(作用域)繼承變量。
如果沒有定義變量的名字,而是隻使用$,那麼在模闆開始執行時,$會設定為傳遞給Execute方法的參數,就是說,dot的初始值。
在一個鍊式的pipeline裡,每個command的結果都作為下一個command的最後一個參數。pipeline最後一個command的輸出作為整個管道執行的結果。
command的輸出可以是1到2個值,如果是2個後一個必須是error接口類型。如果error類型傳回值非nil,模闆執行會中止并将該錯誤傳回給執行模闆的調用者。
Actions
下面是一個action(動作)的清單。"Arguments"和"pipelines"代表資料的執行結果,細節定義在後面。
{{/* a comment */}}
注釋,執行時會忽略。可以多行。注釋不能嵌套,并且必須緊貼分界符始止,就像這裡表示的一樣。
{{pipeline}}
pipeline的值的預設文本表示會被拷貝到輸出裡。
{{if pipeline}} T1 {{end}}
如果pipeline的值為empty,不産生輸出,否則輸出T1執行結果。不改變dot的值。
Empty值包括false、0、任意nil指針或者nil接口,任意長度為0的數組、切片、字典。
{{if pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值為empty,輸出T0執行結果,否則輸出T1執行結果。不改變dot的值。
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
用于簡化if-else鍊條,else action可以直接包含另一個if;等價于:
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
{{range pipeline}} T1 {{end}}
pipeline的值必須是數組、切片、字典或者通道。
如果pipeline的值其長度為0,不會有任何輸出;
否則dot依次設為數組、切片、字典或者通道的每一個成員元素并執行T1;
如果pipeline的值為字典,且鍵可排序的基本類型,元素也會按鍵的順序排序。
{{range pipeline}} T1 {{else}} T0 {{end}}
pipeline的值必須是數組、切片、字典或者通道。
如果pipeline的值其長度為0,不改變dot的值并執行T0;否則會修改dot并執行T1。
{{template "name"}}
執行名為name的模闆,提供給模闆的參數為nil,如模闆不存在輸出為""
{{template "name" pipeline}}
執行名為name的模闆,提供給模闆的參數為pipeline的值。
{{with pipeline}} T1 {{end}}
如果pipeline為empty不産生輸出,否則将dot設為pipeline的值并執行T1。不修改外面的dot。
{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline為empty,不改變dot并執行T0,否則dot設為pipeline的值并執行T1。
4.條件判斷-if
{{if pipeline}} T1 {{end}}
如果pipeline的值為empty,不産生輸出,否則輸出T1執行結果。不改變dot的值。
Empty值包括false、0、任意nil指針或者nil接口,任意長度為0的數組、切片、字典。
{{if pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值為empty,輸出T0執行結果,否則輸出T1執行結果。不改變dot的值。
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
用于簡化if-else鍊條,else action可以直接包含另一個if;等價于:
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
将其與全局函數結合使用為:
{{if not .condition}}
{{end}}
{{if and .condition1 .condition2}} //即如果condition1成立則傳回condition2,否則傳回condition1
{{end}}
{{if or .condition1 .condition2}} //即如果condition1成立則傳回condition1,否則傳回condition2
{{end}}
{{if eq .var1 .var2}}
{{end}}
...
還有:
{{with pipeline}} T1 {{end}}
如果pipeline為empty不産生輸出,否則将dot設為pipeline的值并執行T1。不修改外面的dot。
{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline為empty,不改變dot并執行T0,否則dot設為pipeline的值并執行T1。
func Must
func Must(t *Template, err error) *Template
Must函數用于包裝傳回(*Template, error)的函數/方法調用,它會在err非nil時panic,一般用于變量初始化:
var t = template.Must(template.New("name").Parse("text"))
這樣就不用像上面的例子一樣還要使用if err != nil來判斷是否出錯
舉例:
package main
import(
"os"
"text/template"
"log"
)
func main() {
//建立一個模版
const letter = `
Dear {{.Name}},
{{if .Attended}}
It was a pleasure to see you at the wedding.
{{- else}}
It is a shame you couldn't make it to the wedding.
{{- end}}
{{with .Gift -}}
Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
`
type Recipient struct {
Name, Gift string
Attended bool
}
var recipients = []Recipient{
{"Aunt Mildred", "bone china tea set", true},
{"Uncle John", "moleskin pants", false},
}
// Create a new template and parse the letter into it.
t := template.Must(template.New("letter").Parse(letter))
// Execute the template for each recipient.
for _, r := range recipients {
err := t.Execute(os.Stdout, r)
if err != nil {
log.Println("executing template:", err)
}
}
}
傳回:
bogon:~ user$ go run testGo.go
Dear Aunt Mildred,
It was a pleasure to see you at the wedding.
Thank you for the lovely bone china tea set.
Best wishes,
Josie
Dear Uncle John,
It is a shame you couldn't make it to the wedding.
Thank you for the lovely moleskin pants.
Best wishes,
Josie
注意:
- 在{{- else}}、{{- end}}和{{with .Gift -}}中的-表示消除{{else}}等會導緻的空行
- {{with .Gift}}表示如果Gift不為空的話,則列印下面的句子
5.周遊-range
{{range pipeline}} T1 {{end}}
pipeline的值必須是數組、切片、字典或者通道。
如果pipeline的值其長度為0,不會有任何輸出;
否則dot依次設為數組、切片、字典或者通道的每一個成員元素并執行T1;
如果pipeline的值為字典,且鍵可排序的基本類型,元素也會按鍵的順序排序。
{{range pipeline}} T1 {{else}} T0 {{end}}
pipeline的值必須是數組、切片、字典或者通道。
如果pipeline的值其長度為0,即沒有可周遊的值時,不改變dot的值并執行T0;否則會修改dot并執行T1。
常見用法有:
{{range $i, $v := .Var}} //顯示得到周遊的index和value
{{$i}} => {{$v}}
{{end}}
{{range .Var}} //沒有顯示去擷取周遊得到的index和value,這時候要獲得value值,使用{{.}}表示
{{.}}
{{end}}
{{range .slice}} //如果想要在range...end中通路非周遊得到的value,即外部的其他值,則在前面添加$來表示
{{$.OutsideContent}}
{{end}}
舉例:
package main
import(
"os"
"text/template"
"log"
)
func main() {
//建立一個模版
rangeTemplate := `
{{if .Kind}}
{{range $i, $v := .MapContent}}
{{$i}} => {{$v}} , {{$.OutsideContent}}
{{end}}
{{else}}
{{range .MapContent}}
{{.}} , {{$.OutsideContent}}
{{end}}
{{end}}`
str1 := []string{"this is the first range", "use its index and value"}
str2 := []string{"this is the second range", "do not use its index and value"}
type Content struct {
MapContent []string
OutsideContent string
Kind bool
}
var contents = []Content{
{str1, "this is the first outside content", true},
{str2, "this is the second outside content", false},
}
// Create a new template and parse the letter into it.
t := template.Must(template.New("range").Parse(rangeTemplate))
// Execute the template for each recipient.
for _, c := range contents {
err := t.Execute(os.Stdout, c)
if err != nil {
log.Println("executing template:", err)
}
}
}
傳回:
bogon:~ user$ go run testGo.go
0 => this is the first range , this is the first outside content
1 => use its index and value , this is the first outside content
this is the second range , this is the second outside content
do not use its index and value , this is the second outside content
模版回車
上面的空行與模版中的回車有關,如果想要沒有輸出的空行,上面的模版應該寫成:
rangeTemplate := `{{if .Kind}}{{range $i, $v := .MapContent}}
{{$i}} => {{$v}} , {{$.OutsideContent}}
{{end}}
{{else}}{{range .MapContent}}
{{.}} , {{$.OutsideContent}}
{{end}}
{{end}}`
6.模版嵌套
{{template "name"}}
執行名為name的模闆,提供給模闆的參數為nil,如模闆不存在輸出為""。當然首先要使用{{define "name"}}{{end}}定義好該模版
{{template "name" pipeline}}
執行名為name的模闆,提供給模闆的參數為pipeline的值。将管道的值賦給子模闆中的"."(即"{{.}}"),即{{template "name" .}}
1)使用{{define "name"}}...{{end}}定義模版
舉例1:
package main
import(
"os"
"text/template"
"log"
)
func main() {
//建立一個模版
templateContent := `{{define "T1"}}ONE{{end}}{{define "T2"}}TWO{{end}}{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}{{template "T3"}}`
// Create a new template and parse the letter into it.
t := template.Must(template.New("template").Parse(templateContent))
// Execute the template for each recipient.
err := t.Execute(os.Stdout, nil)
if err != nil {
log.Println("executing template:", err)
}
}
傳回:
bogon:~ user$ go run testGo.go
ONE TWObogon:~ user$
2)使用template.New("name")定義模版
舉例2:
等價于上面的例子,隻是寫法不同
package main
import(
"os"
"text/template"
"log"
)
func main() {
//建立一個模版
template1 := "ONE"
template2 := "TWO"
template3 := `{{template "T1"}} {{template "T2"}}`
// Create a new template and parse the letter into it.
t := template.Must(template.New("T1").Parse(template1))
t = template.Must(t.New("T2").Parse(template2))
t = template.Must(t.New("T3").Parse(template3))
// Execute the template for each recipient.
err := t.Execute(os.Stdout, nil)
if err != nil {
log.Println("executing template:", err)
}
}
傳回:
bogon:~ user$ go run testGo.go
ONE TWObogon:~ user$
7.多模版
其實在上面的模版嵌套中我們就使用了多模版的概念
func (*Template) New
func (t *Template) New(name string) *Template
New方法建立一個和t關聯的名字為name的模闆并傳回它。這種可以傳遞的關聯允許一個模闆使用template action調用另一個模闆。
func (*Template) Lookup
func (t *Template) Lookup(name string) *Template
Lookup方法傳回與t關聯的名為name的模闆,如果沒有這個模闆傳回nil。
func (*Template) Templates
func (t *Template) Templates() []*Template
Templates方法傳回與t相關聯的模闆的切片,包括t自己。
當一個Template中有多個模版時,你需要指定解析的模版,是以在這裡使用的是ExecuteTemplate,而不是Execute
func (*Template) Name
func (t *Template) Name() string
傳回模闆t的名字。
func (*Template) ExecuteTemplate
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
ExecuteTemplate方法類似Execute,但是使用名為name的t關聯的模闆産生輸出。
舉例:
package main
import(
"os"
"text/template"
"fmt"
)
type Inventory struct {
Material string
Count uint
}
func main() {
sweaters := Inventory{"wool", 17}
template1 := "{{.Count}} of {{.Material}}\n"
template2 := "{{.Material}} of {{.Count}}\n"
tmpl := template.Must(template.New("T1").Parse(template1))
fmt.Println(tmpl.Name()) //T1
tmpl = template.Must(tmpl.New("T2").Parse(template2))
fmt.Println(tmpl.Name()) //T2
err := tmpl.ExecuteTemplate(os.Stdout, "T1", sweaters)//傳回 17 of wool
if err != nil { panic(err) }
err = tmpl.ExecuteTemplate(os.Stdout, "T2", sweaters)//傳回 wool of 17
if err != nil { panic(err) }
tmpl = tmpl.Lookup("T1")
fmt.Println(tmpl.Name()) //T1
mapTemplate := tmpl.Templates()
for _, v := range mapTemplate{ //先得到T2,再得到T1
fmt.Println(v.Name())
}
}
傳回:
bogon:~ user$ go run testGo.go
T1
T2
17 of wool
wool of 17
T1
T2
T1
8.檔案模版
其實就是你可以将你的模版内容寫到檔案當中,然後再從檔案中調用
func ParseFiles
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
ParseFiles函數建立一個模闆并解析filenames指定的檔案裡的模闆定義。傳回的模闆的名字是第一個檔案的檔案名(不含擴充名),内容為解析後的第一個檔案的内容。至少要提供一個檔案。如果發生錯誤,會停止解析并傳回nil。
func ParseGlob
func (t *Template) ParseGlob(pattern string) (*Template, error)
ParseGlob建立一個模闆并解析比對pattern的檔案(參見glob規則)裡的模闆定義。傳回的模闆的名字是第一個比對的檔案的檔案名(不含擴充名),内容為解析後的第一個檔案的内容。至少要存在一個比對的檔案。如果發生錯誤,會停止解析并傳回nil。ParseGlob等價于使用比對pattern的檔案的清單為參數調用ParseFiles。
1)ParseFiles—一個模版檔案
ParseFiles接受一個字元串,字元串的内容是一個模闆檔案的路徑(絕對路徑or相對路徑)
将模版寫到檔案templateContent.txt中:
{{.Count}} of {{.Material}}
例子:
package main
import(
"os"
"text/template"
)
type Inventory struct {
Material string
Count uint
}
func main() {
sweaters := Inventory{"wool", 17}
tmpl, err := template.ParseFiles("templateContent.txt")//這裡不需要使用new(),因為會預設使用檔案名來命名
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)//傳回 17 of wool
if err != nil { panic(err) }
}
傳回:
bogon:~ user$ cat templateContent.txt
{{.Count}} of {{.Material}}bogon:~ user$
bogon:~ user$ go run testGo.go
17 of woolbogon:~ user$
2)ParseGlob—多個模版檔案
ParseGlob是用正則的方式比對多個檔案,如當你要選取某檔案夾下的所有txt模版檔案時,就可以調用ParseGlob("*.txt")
比如修改上面多模版的例子:
templateContent.txt(回車一行來實作換行)
{{.Count}} of {{.Material}}
anotherTemplate.txt(回車一行來實作換行)
{{.Material}} of {{.Count}}
舉例:
package main
import(
"os"
"text/template"
"fmt"
)
type Inventory struct {
Material string
Count uint
}
func main() {
sweaters := Inventory{"wool", 17}
tmpl := template.Must(template.ParseGlob("*.txt"))
mapTemplate := tmpl.Templates()
for _, v := range mapTemplate{ //先得到anotherTemplate.txt,再得到templateContent.txt
fmt.Println(v.Name())
}
err := tmpl.ExecuteTemplate(os.Stdout, "templateContent.txt", sweaters)//傳回 17 of wool
if err != nil { panic(err) }
err = tmpl.ExecuteTemplate(os.Stdout, "anotherTemplate.txt", sweaters)//傳回 wool of 17
if err != nil { panic(err) }
}
傳回:
bogon:~ user$ cat templateContent.txt
{{.Count}} of {{.Material}}
bogon:~ user$ cat anotherTemplate.txt
{{.Material}} of {{.Count}}
bogon:~ user$ go run testGo.go
anotherTemplate.txt
templateContent.txt
17 of wool
wool of 17
9.其他
func (*Template) Clone
func (t *Template) Clone() (*Template, error)
Clone方法傳回模闆的一個副本,包括所有相關聯的模闆。模闆的底層表示樹并未拷貝,而是拷貝了命名空間,是以拷貝調用Parse方法不會修改原模闆的命名空間。Clone方法用于準備模闆的公用部分,向拷貝中加入其他關聯模闆後再進行使用。
例子:
// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
// T0.tmpl is a plain template file that just invokes T1.
{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
// T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// Load the drivers.
drivers := template.Must(template.ParseGlob(pattern))
// We must define an implementation of the T2 template. First we clone
// the drivers, then add a definition of T2 to the template name space.
// 1. Clone the helper set to create a new name space from which to run them.
first, err := drivers.Clone()
if err != nil {
log.Fatal("cloning helpers: ", err)
}
// 2. Define T2, version A, and parse it.
_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
if err != nil {
log.Fatal("parsing T2: ", err)
}
// Now repeat the whole thing, using a different version of T2.
// 1. Clone the drivers.
second, err := drivers.Clone()
if err != nil {
log.Fatal("cloning drivers: ", err)
}
// 2. Define T2, version B, and parse it.
_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
if err != nil {
log.Fatal("parsing T2: ", err)
}
// Execute the templates in the reverse order to verify the
// first is unaffected by the second.
err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
if err != nil {
log.Fatalf("second execution: %s", err)
}
err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
if err != nil {
log.Fatalf("first: execution: %s", err)
}
這樣對first或second的操作都不會影響drivers,first和second之間也互不影響
傳回:
T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
func (*Template) Delims
func (t *Template) Delims(left, right string) *Template
Delims方法用于設定action的分界字元串,應用于之後的Parse、ParseFiles、ParseGlob方法。嵌套模闆定義會繼承這種分界符設定。空字元串分界符表示相應的預設分界符:{{或}}。傳回值就是t,以便進行鍊式調用。
預設的分界符即left = {{ , right = }}
如果把分界符改成<>,舉例:
package main
import(
"os"
"text/template"
)
func main() {
str := "world"
tmpl, err := template.New("test").Delims("<",">").Parse("hello, <.>\n") //建立一個名字為test的模版"hello, <.>}"
if err != nil{
panic(err)
}
err = tmpl.Execute(os.Stdout, str) //将str的值合成到tmpl模版的{{.}}中,并将合成得到的文本輸入到os.Stdout,傳回hello, world
if err != nil{
panic(err)
}
}
傳回:
bogon:~ user$ go run testGo.go
hello, world
func HTMLEscape
func HTMLEscape(w io.Writer, b []byte)
函數向w中寫入b的HTML轉義等價表示。
func HTMLEscapeString
func HTMLEscapeString(s string) string
傳回s的HTML轉義等價表示字元串。
func HTMLEscaper
func HTMLEscaper(args ...interface{}) string
函數傳回其所有參數文本表示的HTML轉義等價表示字元串。
舉例:
package main
import(
"fmt"
"net/http"
"log"
"text/template"
)
func index(w http.ResponseWriter, r *http.Request){
r.ParseForm() //解析URL傳遞的參數,對于POST則解析響應包的主體(request body),如果不調用它則無法擷取表單的資料
fmt.Println(r.Form)
fmt.Println(r.PostForm)
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"]) //如果使用的是方法FormValue()方法(它隻會傳回同名參數slice中的第一個,不存在則傳回空字元串),則可以不用調用上面的ParseForm()方法
for k, v := range r.Form{
fmt.Println("key :", k)
fmt.Println("value :", v)
}
fmt.Fprintf(w, "hello world") //将html寫到w中,w中的内容将會輸出到用戶端中
}
func login(w http.ResponseWriter, r *http.Request){
fmt.Println("method", r.Method) //獲得請求的方法
r.ParseForm()
if r.Method == "GET"{ //
html := `<html>
<head>
<title></title>
</head>
<body>
<form action="http://localhost:9090/login" method="post">
username: <input type="text" name="username">
password: <input type="text" name="password">
<input type="submit" value="login">
</form>
</body>
</html>`
t := template.Must(template.New("test").Parse(html))
t.Execute(w, nil)
}else{
fmt.Println("username : ", template.HTMLEscapeString(r.Form.Get("username")))//在終端即用戶端輸出
fmt.Println("password : ", template.HTMLEscapeString(r.Form.Get("password")))//把r.Form.Get("password")轉義之後傳回字元串
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //在用戶端輸出,把r.Form.Get("username")轉義後寫到w
}
}
func main() {
http.HandleFunc("/", index) //設定通路的路由
http.HandleFunc("/login", login) //設定通路的路由
err := http.ListenAndServe(":9090", nil) //設定監聽的端口
if err != nil{
log.Fatal("ListenAndServe : ", err)
}
}
通路http://localhost:9090/
通路http://localhost:9090/login
如果僅傳入字元串:
服務端傳回:
method POST
username : hello
password : allen
map[]
map[]
path /favicon.ico
scheme
[]
用戶端:
當時如果username輸入的是<script>alert()</script>
用戶端傳回:
可見html/template包預設幫你過濾了html标簽
func JSEscape
func JSEscape(w io.Writer, b []byte)
函數向w中寫入b的JavaScript轉義等價表示。
func JSEscapeString
func JSEscapeString(s string) string
傳回s的JavaScript轉義等價表示字元串。
func JSEscaper
func JSEscaper(args ...interface{}) string
函數傳回其所有參數文本表示的JavaScript轉義等價表示字元串。
func URLQueryEscaper
func URLQueryEscaper(args ...interface{}) string
函數傳回其所有參數文本表示的可以嵌入URL查詢的轉義等價表示字元串