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
這時, 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明顯不同);
該函式型別值必須有1到2個返回值,如果有2個則後一個必須是error介面型別;
如果有2個返回值的方法返回的error非nil,模板執行會中斷並返回給呼叫模板執行者該錯誤;
html
轉義文字中的html標籤,如將"<"轉義為"<",">"轉義為">"等。
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標籤
//<br>
//返回Contact索引為qq的值
//656965586
//回用js的escape處理後的文字
//?a=123&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