天天看點

go語言text/template标準庫

作者:幹飯人小羽
go語言text/template标準庫

導入方式:

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字元串:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

{{.}}:此标簽輸出目前對象的值,在這裡即代表str對象,但是在下面的例子中,.代表的是Execute中傳入的sweaters對象的值

2)另一個例子,傳入的是struct對象的值:

go語言text/template标準庫
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) }
}           
go語言text/template标準庫

如果上面的例子中Count的值也是一個struct對象,可以使用{{.Count.Field1}}來通路其字段

3)自定義的變量,可以先看下面的方法的例子

舉例:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

傳回:

bogon:~ user$ go run testGo.go 
hello world
my name is : student           

2.函數:

執行模闆時,函數從兩個函數字典中查找:首先是模闆函數字典,然後是全局函數字典。一般不在模闆内定義函數,而是使用Funcs方法添加函數到模闆裡。

方法必須有一到兩個傳回值,如果是兩個,那麼第二個一定是error接口類型

1)模版内定義函數

舉例:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

傳回:

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,模闆執行會中斷并傳回給調用者該錯誤。

舉例:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

傳回:

bogon:~ user$ go run testGo.go 
hello world
my name is : boss           

根據pipeline的定義,上面的{{SayYouName .}}等價于{{.|SayYouName}}

預定義的全局函數如下:

就是在{{}}中可以直接使用的函數

go語言text/template标準庫
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,模闆執行會中斷并傳回給調用模闆執行者該錯誤;           
go語言text/template标準庫

布爾函數會将任何類型的零值視為假,其餘視為真。

舉例:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

傳回:

bogon:~ user$ go run testGo.go 
"boss"           

下面是定義為函數的二進制比較運算的集合:

go語言text/template标準庫
eq 如果arg1 == arg2則傳回真
ne 如果arg1 != arg2則傳回真
lt 如果arg1 < arg2則傳回真
le 如果arg1 <= arg2則傳回真
gt 如果arg1 > arg2則傳回真
ge 如果arg1 >= arg2則傳回真           
go語言text/template标準庫

為了簡化多參數相等檢測,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"代表資料的執行結果,細節定義在後面。

go語言text/template标準庫
{{/* 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。           
go語言text/template标準庫

4.條件判斷-if

go語言text/template标準庫
{{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}}           
go語言text/template标準庫

将其與全局函數結合使用為:

go語言text/template标準庫
{{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}}
...           
go語言text/template标準庫

還有:

{{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來判斷是否出錯

舉例:

go語言text/template标準庫
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)
        }
    }
}           
go語言text/template标準庫

傳回:

go語言text/template标準庫
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           
go語言text/template标準庫

注意:

  • 在{{- else}}、{{- end}}和{{with .Gift -}}中的-表示消除{{else}}等會導緻的空行
  • {{with .Gift}}表示如果Gift不為空的話,則列印下面的句子

5.周遊-range

go語言text/template标準庫
{{range pipeline}} T1 {{end}}
    pipeline的值必須是數組、切片、字典或者通道。
    如果pipeline的值其長度為0,不會有任何輸出;
    否則dot依次設為數組、切片、字典或者通道的每一個成員元素并執行T1;
    如果pipeline的值為字典,且鍵可排序的基本類型,元素也會按鍵的順序排序。
{{range pipeline}} T1 {{else}} T0 {{end}}
    pipeline的值必須是數組、切片、字典或者通道。
    如果pipeline的值其長度為0,即沒有可周遊的值時,不改變dot的值并執行T0;否則會修改dot并執行T1。           
go語言text/template标準庫

常見用法有:

go語言text/template标準庫
{{range $i, $v := .Var}} //顯示得到周遊的index和value
{{$i}} => {{$v}} 
{{end}} 

{{range .Var}} //沒有顯示去擷取周遊得到的index和value,這時候要獲得value值,使用{{.}}表示
{{.}} 
{{end}} 

{{range .slice}} //如果想要在range...end中通路非周遊得到的value,即外部的其他值,則在前面添加$來表示
{{$.OutsideContent}}
{{end}}           
go語言text/template标準庫

舉例:

go語言text/template标準庫
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)
        }
    }
}           
go語言text/template标準庫

傳回:

go語言text/template标準庫
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           
go語言text/template标準庫

模版回車

上面的空行與模版中的回車有關,如果想要沒有輸出的空行,上面的模版應該寫成:

go語言text/template标準庫
rangeTemplate := `{{if .Kind}}{{range $i, $v := .MapContent}}
{{$i}} => {{$v}} , {{$.OutsideContent}}
{{end}}
{{else}}{{range .MapContent}}
{{.}} , {{$.OutsideContent}}
{{end}}    
{{end}}`           
go語言text/template标準庫

6.模版嵌套

{{template "name"}}
    執行名為name的模闆,提供給模闆的參數為nil,如模闆不存在輸出為""。當然首先要使用{{define "name"}}{{end}}定義好該模版
{{template "name" pipeline}}
    執行名為name的模闆,提供給模闆的參數為pipeline的值。将管道的值賦給子模闆中的"."(即"{{.}}"),即{{template "name" .}}           

1)使用{{define "name"}}...{{end}}定義模版

舉例1:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

傳回:

bogon:~ user$ go run testGo.go 
ONE TWObogon:~ user$            

2)使用template.New("name")定義模版

舉例2:

等價于上面的例子,隻是寫法不同

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

傳回:

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關聯的模闆産生輸出。

舉例:

go語言text/template标準庫
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())
    }

}           
go語言text/template标準庫

傳回:

go語言text/template标準庫
bogon:~ user$ go run testGo.go 
T1
T2
17 of wool
wool of 17
T1
T2
T1           
go語言text/template标準庫

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}}           

例子:

go語言text/template标準庫
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) }
}           
go語言text/template标準庫

傳回:

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}}

           

舉例:

go語言text/template标準庫
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) }

}           
go語言text/template标準庫

傳回:

go語言text/template标準庫
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           
go語言text/template标準庫

9.其他

func (*Template) Clone

func (t *Template) Clone() (*Template, error)           

Clone方法傳回模闆的一個副本,包括所有相關聯的模闆。模闆的底層表示樹并未拷貝,而是拷貝了命名空間,是以拷貝調用Parse方法不會修改原模闆的命名空間。Clone方法用于準備模闆的公用部分,向拷貝中加入其他關聯模闆後再進行使用。

例子:

go語言text/template标準庫
// 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)
}           
go語言text/template标準庫

這樣對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 = }}

如果把分界符改成<>,舉例:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

傳回:

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轉義等價表示字元串。

舉例:

go語言text/template标準庫
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)
    }
}           
go語言text/template标準庫

通路http://localhost:9090/

go語言text/template标準庫

通路http://localhost:9090/login

go語言text/template标準庫

如果僅傳入字元串:

go語言text/template标準庫

服務端傳回:

go語言text/template标準庫
method POST
username :  hello
password :  allen
map[]
map[]
path /favicon.ico
scheme 
[]           
go語言text/template标準庫

用戶端:

go語言text/template标準庫

當時如果username輸入的是<script>alert()</script>

go語言text/template标準庫

用戶端傳回:

go語言text/template标準庫

可見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查詢的轉義等價表示字元串