1. 程式人生 > >go模板

go模板

文章目錄

go模板

介紹

template包用於生成文字輸出的模板,要生成HTML輸出,需要用到html/template,和text/template具有相同的介面,但會自動將輸出轉化為安全的HTML格式輸出,可以抵抗一些網路攻擊。模板標籤用"{{“和”}}"括起來。

通過將資料結構作為模板的引數來生成文字。模板中通過引用資料結構的元素(通常是結構的欄位或對映中的鍵)來控制執行過程和需要顯示的值。模板執行時會遍歷結構並將指標表示為’.’(稱之為"dot")指向執行過程中資料結構的當前位置的值。

模板的輸入文字格式必須是UTF-8編碼。 “Action” - 資料運算或控制結構 - 由“{{”和“}}”分隔,在Action之外的所有文字都不做修改的拷貝到輸出中。Action內部不能有換行,但註釋可以有換行。

一旦解析,模板可以安全地並行執行,但是如果並行執行共享Writer,則輸出可以是交錯的。

這是一個簡單的例子,打印出“17件由羊毛製成”。

type Inventory struct {
	Material string
	Count    uint
}
sweaters := Inventory{"wool", 17}

tmpl, err := template.
New("test").Parse("{{.Count}} items are made of {{.Material}}") if err != nil { panic(err) } err = tmpl.Execute(os.Stdout, sweaters) if err != nil { panic(err) }

空格

預設情況下,執行模板時,將逐字複製actions之間的所有文字。 例如,執行程式時,上例中的字串" items are made of "出現在標準輸出上。但是,為了幫助格式化模板原始碼,如果操作的左分隔符(預設為“{{”)後面緊跟一個減號和ASCII空格字元(“{{-

”),則所有尾隨空格都會緊接在文字之前。 同樣,如果右邊分隔符(“}}”)前面有空格和減號(“-}}”),則去除文字中緊隨其後的空格。在這些修剪標記中,必須存在ASCII空格字元; “{{-3}}”解析為數字-3

例如,模板

"{{23 }} < {{ 45}}"
"{{23 -}} < {{- 45}}"

輸出

"23 < 45"
"23<45"

註釋

{{/* a comment */}}

執行時會忽略。可以多行,註釋不能巢狀,並且必須緊貼分界符始止,就像這裡表示的一樣。

package main

import (
	"html/template"
	"os"
)

func main()  {
	//建立一個模板
	t := template.New("test")
	//註釋
	t, _ = t.Parse(`{{/*我是註釋*/}}`)
	t.Execute(os.Stdout, nil)
}

輸出

{{pipeline}}
pipeline的值的預設文字表示會被拷貝到輸出裡。

package main

import (
	"html/template"
	"os"
)

func main()  {
	//建立一個模板
	t := template.New("test")
	//顯示的值
	d := "this is a"
	t, _ = t.Parse(`{{.}}{{" test!"}}`)
	t.Execute(os.Stdout, d)

	//output: this is a test!
}

變數

點號用於輸出當前物件的值,$用於輸出模板中定義變數的值。

Action裡可以初始化一個變數來捕獲管道的執行結果。初始化語法如下:

$variable := pipeline

其中$variable是變數的名字。宣告變數的action不會產生任何輸出。

賦值先前宣告的變數,語法如下:

$variable = pipeline

如果"range" action初始化了1個變數,該變數設定為迭代器的每一個成員元素,如果初始化了逗號分隔的2個變數:

range $index, $element := pipeline

這時, i n d e x index和 element分別設定為陣列/切片的索引或者字典的鍵,以及對應的成員元素。注意這和go range從句只有一個引數時設定為索引/鍵不同!

一個變數的作用域只到宣告它的控制結構(“if”、“with”、“range”)的"end"為止,如果不是在控制結構裡宣告會直到模板結束為止。子模板的呼叫不會從呼叫它的位置(作用域)繼承變數。

模板開始執行時,$會設定為傳遞給Execute方法的引數,就是說,dot的初始值。

package main

import (
	"os"
	"text/template"
)
type User struct {
	Id       string
	UserName string
	Age      int
	Ct       Contact
}

type Contact struct {
	Name string
	Tel  int
}

func main()  {
	//模板函式
	t := template.New("test1")
	t, _ = t.Parse(`
			{{.Id}} {{.UserName}} {{.Age}} {{.Ct.Tel}}
			{{$a := .Ct}} {{$a.tel}}
                     `)
	t.Execute(os.Stdout, User{"1234", "wyy", 22,Contact{"mama",195566985}})

	//output:
	//1234 wyy 22 195566985
}

變數的引用

{{.}}

此標籤輸出當前物件的值

{{.Admpub}}

表示輸出Struct物件中欄位或方法名稱為“Admpub”的值。

當“Admpub”是匿名欄位時,可以訪問其內部欄位或方法,比如“Com”:{{.Admpub.Com}} ,

如果“Com”是一個方法並返回一個Struct物件,同樣也可以訪問其欄位或方法:{{.Admpub.Com.Field1}}

{{.Method1 "parm1" "parm2"}}
呼叫方法“Method1”,將後面的引數值依次傳遞給此方法,並輸出其返回值。

{{$admpub}}
此標籤用於輸出在模板中定義的名稱為“admpub”的變數。當KaTeX parse error: Expected '}', got 'EOF' at end of input: …ct物件時,可訪問其欄位:{{admpub.Field1}}

Arguments表示一個簡單的值,由下面的某一條表示的值。

  • go語法的布林值、字串、字元、整數、浮點數、虛數、複數,視為無型別字面常數,字串不能跨行
  • 關鍵字nil,代表一個go的無型別的nil值
  • 字元’.’(句點,用時不加單引號),代表dot的值
  • 變數名,以美元符號起始加上(可為空的)字母和數字構成的字串,如:$piOver2和$;
    執行結果為變數的值,變數參見下面的介紹
  • 結構體資料的欄位名,以句點起始,如:.Field;
    執行結果為欄位的值,支援鏈式呼叫:.Field1.Field2;
    欄位也可以在變數上使用(包括鏈式呼叫):$x.Field1.Field2;
  • 字典型別資料的鍵名;以句點起始,如:.Key;
    執行結果是該鍵在字典中對應的成員元素的值;
    鍵也可以和欄位配合做鏈式呼叫,深度不限:.Field1.Key1.Field2.Key2;
    雖然鍵也必須是字母和數字構成的標識字串,但不需要以大寫字母起始;
    鍵也可以用於變數(包括鏈式呼叫):$x.key1.key2;
  • 資料的無引數方法名,以句點為起始,如:.Method;
    執行結果為dot呼叫該方法的返回值,dot.Method();
    該方法必須有1到2個返回值,如果有2個則後一個必須是error介面型別;
    如果有2個返回值的方法返回的error非nil,模板執行會中斷並返回給呼叫模板執行者該錯誤;
    方法可和欄位、鍵配合做鏈式呼叫,深度不限:.Field1.Key1.Method1.Field2.Key2.Method2;
    方法也可以在變數上使用(包括鏈式呼叫):$x.Method1.Field;
  • 無引數的函式名,如:fun;
    執行結果是呼叫該函式的返回值fun();對返回值的要求和方法一樣;函式和函式名細節參見後面。
  • 上面某一條的例項加上括弧(用於分組)
    執行結果可以訪問其欄位或者鍵對應的值:
    print (.F1 arg1) (.F2 arg2)
    (.StructValuedMethod “arg”).Field

Arguments可以是任何型別:如果是指標,在必要時會自動錶示為指標指向的值;如果執行結果生成了一個函式型別的值,如結構體的函式型別欄位(返回一個函式指標),該函式不會自動呼叫,但可以在if等action裡視為真。如果要呼叫它,使用call函式,參見下面。

package main

import (
	"os"
	"text/template"
)
type User struct {
	Id       string
	UserName string
	Age      int
}

func main()  {
	//模板函式
	t := template.New("test1")
	t, _ = t.Parse(`
		{{.Id}} {{.UserName}} {{.Age}}
                `)
	t.Execute(os.Stdout, User{"1234", "wyy", 22})

	//output:
	//1234 wyy 22
}

管道

Pipeline是一個(可能是鏈狀的)command序列。Command可以是一個簡單值(argument)或者對函式或者方法的(可以有多個引數的)呼叫:

Argument
    執行結果是argument的執行結果
.Method [Argument...]
    方法可以獨立呼叫或者位於鏈式呼叫的末端,不同於鏈式呼叫中間的方法,可以使用引數;
    執行結果是使用給出的引數呼叫該方法的返回值:dot.Method(Argument1, etc.);
functionName [Argument...]
    執行結果是使用給定的引數呼叫函式名指定的函式的返回值:function(Argument1, etc.)

pipeline通常是將一個command序列分割開,再使用管道符’|'連線起來(但不使用管道符的command序列也可以視為一個管道)。在一個鏈式的pipeline裡,每個command的結果都作為下一個command的最後一個引數。pipeline最後一個command的輸出作為整個管道執行的結果。

command的輸出可以是1到2個值,如果是2個後一個必須是error介面型別。如果error型別返回值非nil,模板執行會中止並將該錯誤返回給執行模板的呼叫者。

{{FuncName1}}
此標籤將呼叫名稱為“FuncName1”的模板函式(等同於執行“FuncName1()”,不傳遞任何引數)並輸出其返回值。

{{FuncName1 "parm1" "parm2"}}
此標籤將呼叫“FuncName1(“引數值1”, “引數值2”)”,並輸出其返回值

{{.Admpub|FuncName1}}
此標籤將呼叫名稱為“FuncName1”的模板函式(等同於執行“FuncName1(this.Admpub)”,將豎線“|”左邊的“.Admpub”變數值作為函式引數傳送)並輸出其返回值。

package main

import (
	"os"
	"text/template"
)
type User struct {
	Id       string
	UserName string
	Age      int
}

//記得函式名首字母大寫
func (u User)Test(msg string) string {
	return msg
}

//這個函式是註冊到模板,可以不用大寫
func test1(msg string) string {
	return msg + " ==>"
}

func main()  {
	//模板函式
	t1 := template.New("test1")
	//註冊模板函式
	t1.Funcs(template.FuncMap{"test1": test1})
	// {{函式名}}輸出函式返回值
	// {{函式名 引數1 引數2}}
	// {{.欄位名|函式名}} 以欄位的值作為函式的引數
	t1, _ = t1.Parse(`
                      {{.UserName|test1}}
		      {{ "2018-01-02 15:04:05" | .Test}}
                     `)
	t1.Execute(os.Stdout, User{"1234", "wyy", 22})

	//output:
	//wyy ==>
	//2018-01-02 15:04:05
}

條件

如果pipeline的值為empty,不產生輸出,否則輸出T1執行結果。不改變dot的值。Empty值包括false、0、任意nil指標或者nil介面,任意長度為0的陣列、切片、字典、字串。

{{if pipeline}} T1 {{end}}

如果pipeline的值為empty,輸出T0執行結果,否則輸出T1執行結果。不改變dot的值。

{{if pipeline}} T1 {{else}} T0 {{end}}

用於簡化if-else,else action可以直接包含另一個if

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

等價於:

{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
package main

import (
	"html/template"
	"os"
)

func comp(a int) bool {
	if a == 3 {
		return true
	}
	return false
}

func main()  {
	t := template.New("test")
	t.Funcs(template.FuncMap{"comp": comp})

	t, _ = t.Parse(`{{if 1}}
                          	true
                         {{else}}
				false
                      	 {{end}}

                         {{$a := 4}}
                         {{if $a|comp}}
                             $a=3
                         {{else}}
                             $a!=3
                         {{end}}`)
	t.Execute(os.Stdout, nil)

	//output: true
	//        $a!=3
}

遍歷

pipeline的值必須是陣列、切片、字典或者通道。如果pipeline的值其長度為0,不會有任何輸出;否則dot依次設為陣列、切片、字典或者通道的每一個成員元素並執行T1;如果pipeline的值為字典,且鍵可排序的基本型別,元素也會按鍵的順序排序。

{{range pipeline}} T1 {{end}}

pipeline的值必須是陣列、切片、字典或者通道。如果pipeline的值其長度為0,不改變dot的值並執行T0;否則會修改dot並執行T1。

{{range pipeline}} T1 {{else}} T0 {{end}}
package main

import (
	"text/template"
	"os"
)
type User struct {
	Contact  map[string]string
	Num      int
}

func main()  {
	t := template.New("test")
	t, _ = t.Parse(`
                        {{range $k, $v := .Contact}}
                        {{$k}} {{$v}}
                     	{{end}}

			{{range .Contact}}
    			{{.}}
			{{end}}

			{{range .Contact}}
			{{else}}
			{{/* 當 .Pages 為空 或者 長度為 0 時會執行這裡 */}}
			{{end}}
                       `)
	info := make(map[string]string)
	info["qq"] = "656965586"
	info["tel"] = "15399265339"
	t.Execute(os.Stdout, User{info,100})

	//output:
	//qq  656965586
	//tel 15399265339

	//656965586
	//15399265339
}

定義區域性變數

with 用於重定向 pipeline,建立一個封閉的作用域,在其範圍內,可以使用.action,而與外面的.無關,只與with的引數有關。如果pipeline為empty不產生輸出,否則將dot設為pipeline的值並執行T1。不修改外面的dot。

{{with pipeline}} T1 {{end}}

如果pipeline為empty,不改變dot並執行T0,否則dot設為pipeline的值並執行T1。

{{with pipeline}} T1 {{else}} T0 {{end}}
package main

import (
	"text/template"
	"os"
)
type User struct {
	Contact  map[string]string
	Num      int
}

func main()  {
	t := template.New("test")
	t, _ = t.Parse(`
			{{with "hello"}}{{printf "%q" .}}{{end}}

            		{{with .Contact}}
                        {{range $k, $v := .}}
			{{$k}} {{$v}}
                        {{end}}
                      	{{end}}
                       `)
	info := make(map[string]string)
	info["qq"] = "656965586"
	info["tel"] = "15399265339"
	t.Execute(os.Stdout, User{info,100})

	//output:
	//"hello"
	//qq 656965586
	//tel 15399265339
}

嵌入子模板

每一個模板在建立時都要用一個字串命名。同時,每一個模板都會和0或多個模板關聯,並可以使用它們的名字呼叫這些模板;這種關聯可以傳遞,並形成一個模板的名字空間。

一個模板可以通過模板呼叫例項化另一個模板;參見上面的"template" action。name必須是包含模板呼叫的模板相關聯的模板的名字。

執行名為name的模板,提供給模板的引數為nil,如模板不存在輸出為""。

嵌入名稱為“name”的子模板。使用前,請確保已經用“{{define “name”}}子模板內容{{end}}”定義好了子模板內容。

{{template "name"}}

執行名為name的模板,提供給模板的引數為pipeline的值。將管道的值賦給子模板中的“.”(即“{{.}}”)

{{template "name" pipeline}}

典型用法是定義一組根模板,然後通過重新定義其中的block模板進行自定義。

{{block "name" pipeline}} T1 {{end}}

等價於

{{define "name"}} T1 {{end}}
{{template "name" pipeline}}

當解析模板時,可以定義另一個模板,該模板會和當前解析的模板相關聯。模板必須定義在當前模板的最頂層,就像go程式裡的全域性變數一樣。

這種定義模板的語法是將每一個子模板的宣告放在"define"和"end" action內部。

define action使用給出的字串常數給模板命名,舉例如下:

`{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}`

它定義了兩個模板T1和T2,第三個模板T3在執行時呼叫這兩個模板;最後該模板呼叫了T3。輸出結果是:

ONE TWO

採用這種方法,一個模板只能從屬於一個關聯。如果需要讓一個模板可以被多個關聯查詢到;模板定義必須多次解析以建立不同的*Template 值,或者必須使用Clone或AddParseTree方法進行拷貝。

可能需要多次呼叫Parse函式以集合多個相關的模板;參見ParseFiles和ParseGlob函式和方法,它們提供了簡便的途徑去解析儲存在檔案中的存在關聯的模板。

一個模板可以直接呼叫或者通過ExecuteTemplate方法呼叫指定名字的相關聯的模板;我們可以這樣呼叫模板:

err := tmpl.Execute(os.Stdout, "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}

或顯式的指定模板的名字來呼叫:

err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}
package main

import (
	"os"
	"text/template"
)
type User struct {
	Id       string
	UserName string
	Age      int
}

func test() string {
	return "test"
}

func main()  {
	//巢狀模板
	t := template.New("test");
	t.Funcs(template.FuncMap{"test": test});
	// {{define "模板名"}}模板內容{{end}} 定義模板
	// {{template "模板名"}} 引入模板
	// {{template "模板名" 函式}} 將函式中的值賦給模板中的{{.}}
	t, _ = t.Parse(`
		  {{/*下面三句模板定義不會有輸出*/}}
                  {{define "tp1"}} 我是模板1 {{end}}
                  {{define "tp2"}} 我是模板2 {{.}} {{end}}
                  {{define "tp3"}} {{- template "tp1"}} {{template "tp2"}} {{end}}
                      
  		  {{/*下面三句會有輸出*/}}
		  {{template "tp1"}}
                  {{template "tp2" test}}
                  {{template "tp3" test}}
                     `);
	t.Execute(os.Stdout, nil);
	//output:
	//我是模板1
	//我是模板2 test
	//我是模板1   我是模板2 <no value>
}

預定義的模板全域性函式

執行模板時,函式從兩個函式字典中查詢:首先是模板函式字典,然後是全域性函式字典。一般不在模板內定義函式,而是使用Funcs方法新增函式到模板裡。

預定義的全域性函式如下:

and
    函式返回它的第一個empty引數或者最後一個引數;
    就是說"and x y"等價於"if x then y else x";所有引數都會執行;
call
    執行結果是呼叫第一個引數的返回值,該引數必須是函式型別,其餘引數作為呼叫該函式的引數;
    如"call .X.Y 1 2"等價於go語言裡的dot.X.Y(1, 2);
    其中Y是函式型別的欄位或者字典的值,或者其他類似情況;
    call的第一個引數的執行結果必須是函式型別的值(和預定義函式如print明顯不同);
    該函式型別值必須有12個返回值,如果有2個則後一個必須是error介面型別;
    如果有2個返回值的方法返回的errornil,模板執行會中斷並返回給呼叫模板執行者該錯誤;
html
    轉義文字中的html標籤,如將"<"轉義為"&lt;"">"轉義為"&gt;"等。
index
    執行結果為第一個引數以剩下的引數為索引/鍵指向的值;
    如"index x 1 2 3"返回x[1][2][3]的值;每個被索引的主體必須是陣列、切片或者字典。
js
    返回用JavaScript的escape處理後的文字。
len
    返回它的引數的整數型別長度
not
    返回它的單個引數的布林值的否定
or
    返回第一個非empty引數或者最後一個引數;
    亦即"or x y"等價於"if x then x else y";所有引數都會執行;
print
    即fmt.Sprint
printf
    即fmt.Sprintf
println
    即fmt.Sprintln
urlquery
    返回適合在URL查詢中嵌入到形參中的文字轉義值。(類似於PHP的urlencode)


布林函式會將任何型別的零值視為假,其餘視為真。

下面是定義為函式的二元比較運算的集合:

eq      如果arg1 == arg2則返回真
ne      如果arg1 != arg2則返回真
lt      如果arg1 < arg2則返回真
le      如果arg1 <= arg2則返回真
gt      如果arg1 > arg2則返回真
ge      如果arg1 >= arg2則返回真

為了簡化多引數相等檢測,eq(只有eq)可以接受2個或更多個引數,和go的||不一樣,不做惰性運算,所有引數都會執行,它會將第一個引數和其餘引數依次比較,

{{eq arg1 arg2 arg3 arg4}}

即只能作如下比較:

arg1==arg2 || arg1==arg3 || arg1==arg4 ...

比較函式只適用於基本型別(或重定義的基本型別,如"type Celsius float32")。它們實現了go語言規則的值的比較,但具體的型別和大小會忽略掉,因此任意型別有符號整數值都可以互相比較;任意型別無符號整數值都可以互相比較;等等。但是,整數和浮點數不能互相比較。


package main

import (
	"text/template"
	"os"
)
type User struct {
	Contact  map[string]string
}

func sum() func(nums ...int) (int, error) {
	return func(nums ...int) (int, error) {
		sum := 0
		for _, v := range nums {
			sum += v
		}
		return sum, nil
	};
}

func main()  {
	//內建的模板函式
	t := template.New("test")
	t.Funcs(template.FuncMap{"sum": sum})
	t, _ = t.Parse(`
                        //如果3為真,返回4,否則返回3
                        {{and 3 4}}
 
                        //call後第一個引數的返回值必須是一個函式
                        {{call sum 1 3 5 7}}
 
                        //轉義文字中的html標籤
                        {{"<br>"|html}}
 
                        //返回Contact索引為qq的值
                        {{index .Contact "qq"}}
 
                        //返回用js的escape處理後的文字
                        {{"?a=123&b=你好"|js}}
 
                        //返回引數的長度值
                        {{"hello"|len}}
 
                        //返回單一引數的布林否定值
                        {{not 0}}
 
                        //如果3為真,返回3,否則返回4
                        {{or 3 4}}
 
                        //fmt.Sprint的別名
                        {{"你好"|print "世界"}}
 
                        //fmt.Sprintf的別名
                        {{"你好"|printf "%d %s" 123}}
 
                        //fmt.Sprintln的別名
                        {{"你好"|println "世界"}}
 
                        //url中get引數轉義
                        {{"?q=關鍵字&p=1"|urlquery}}
 
                        //等於
                        {{if eq 1 1}}1=1{{end}}
 
                        //不等於
                        {{if ne 1 2}}1!=1{{end}}
 
                        //小於
                        {{if lt 1 3}}1<3{{end}}
 
                        //小於等於
                        {{if le 3 3}}3<=3{{end}}
 
                        //大於
                        {{if gt 3 1}}3>1{{end}}
 
                        //大於等於
                        {{if ge 3 3}}3>=3{{end}}
                       `)
	info := make(map[string]string)
	info["qq"] = "656965586"
	info["tel"] = "15399265339"
	t.Execute(os.Stdout, User{info})

	//output:
	//如果3為真,返回4,否則返回3
	//4

	//call後第一個引數的返回值必須是一個函式
	//16

	//轉義文字中的html標籤
	//&lt;br&gt;

	//返回Contact索引為qq的值
	//656965586

	//回用js的escape處理後的文字
	//?a=123&amp;b=你好

	//返回引數的長度值
	//5

	//返回單一引數的布林否定值
	//true

	//如果3為真,返回3,否則返回4
	//3

	//fmt.Sprint的別名
	//世界你好

	//fmt.Sprintf的別名
	//123 你好

	//fmt.Sprintln的別名
	//世界 你好


	//url中get引數轉義
	//%3Fq%3D%E5%85%B3%E9%94%AE%E5%AD%97%26p%3D1

	//等於
	//1=1

	//不等於
	//1!=1
    
	//小於
	//1<3