參考:https://studygolang.com/pkgdoc
導入方式:
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) } }
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查詢的轉義等價表示字符串。
