golang中的標準庫template
html/template包實現了資料驅動的模板,用於生成可對抗程式碼注入的安全HTML輸出。它提供了和text/template包相同的介面,Go語言中輸出HTML的場景都應使用text/template包。
模板
在基於MVC的Web架構中,我們通常需要在後端渲染一些資料到HTML檔案中,從而實現動態的網頁效果
模板示例
通過將模板應用於一個數據結構(即該資料結構作為模板的引數)來執行,來獲得輸出。模板中的註釋引用資料介面的元素(一般如結構體的欄位或者字典的鍵)來控制執行過程和獲取需要呈現的值。模板執行時會遍歷結構並將指標表示為’.‘(稱之為”dot”)指向執行過程中資料結構的當前位置的值。
用作模板的輸入文字必須是utf-8編碼的文字。”Action”—資料運算和控制單位—由”“界定;在Action之外的所有文字都不做修改的拷貝到輸出中。Action內部不能有換行,但註釋可以有換行。
HTML檔案程式碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
<p>Hello {{.}}</p>
</body>
</html>
我們的HTTP server端程式碼如下:
func main() { http.HandleFunc("/", SayHello) err := http.ListenAndServe(":8000", nil) if err != nil { fmt.Println(err.Error()) return } } func SayHello(w http.ResponseWriter, r *http.Request) { // 解析指定檔案,生成模板物件 tmpl, err := template.ParseFiles("./templates/index.html") if err != nil { fmt.Println("create template failed, err: ", err) return } // 利用給定資料渲染模板,並將結果寫入w tmpl.Execute(w, "sankuan") }
模板語法
{{.}}
模板語法都包含在{{和}}中間,其中{{.}}中的點表示當前物件。
當我們傳入一個結構體物件時,我們可以根據.來訪問結構體的對應欄位。例如:
func main() { http.HandleFunc("/", SayHello) if err := http.ListenAndServe(":8000", nil); err != nil { fmt.Println(err.Error()) return } } func SayHello(w http.ResponseWriter, r *http.Request) { // 解析模板檔案,生成模板物件 tmpl, err := template.ParseFiles("./templates/index.html") if err != nil { fmt.Println(err.Error()) return } // 將資料渲染到模板,並寫入到w user := User{"張三", 18, true} tmpl.Execute(w, &user) } type User struct { Name string Age uint8 Gender bool }
HTML檔案程式碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
<p>Name: {{.Name}}</p>
<p>Age: {{.Age}}</p>
<p>Gender: {{.Gender}}</p>
</body>
</html>
同理,當我們傳入的變數是map時,也可以在模板檔案中通過.根據key來取值。
註釋
<p>Gender: {{/*.Gender*/}}</p>
註釋,執行時會忽略。可以多行。註釋不能巢狀,並且必須緊貼分界符始止。
pipeline
pipeline是指產生資料的操作。比如{{.}}、{{.Name}}等。Go的模板語法中支援使用管道符號|連結多個命令,用法和unix下的管道類似:|前面的命令會將運算結果(或返回值)傳遞給後一個命令的最後一個位置。
注意 : 並不是只有使用了|才是pipeline。Go的模板語法中,pipeline的概念是傳遞資料,只要能產生資料的,都是pipeline。
html中定義變數
Action裡可以初始化一個變數來捕獲管道的執行結果。初始化語法如下:
<body>
<p>Name: {{.Name}}</p>
<p>Age: {{.Age}}</p>
<p>Gender: {{.Gender}}</p> {{$score := 88}}
<p>分數: {{$score}}</p>
</body>
if判斷
{{if lt .Age 18}}未成年{{else}}已經成年{{end}}
golang的模板也支援if的條件判斷,當前支援最簡單的bool型別和字串型別的判斷
{{if .condition}} {{end}}
當.condition為bool型別的時候,則為true表示執行,當.condition為string型別的時候,則非空表示執行。
當然也支援else , else if巢狀
{{if .condition1}} {{else if .contition2}} {{end}}
假設我們需要邏輯判斷,比如與或、大小不等於等判斷的時候,我們需要一些內建的模板函式來做這些工作,目前常用的一些內建模板函式有:
not 非
{{if not .condition}}
{{end}}
and 與
{{if and .condition1 .condition2}}
{{end}}
or 或
{{if or .condition1 .condition2}}
{{end}}
eq 等於
{{if eq .var1 .var2}}
{{end}}
ne 不等於
{{if ne .var1 .var2}}
{{end}}
lt 小於 (less than)
{{if lt .var1 .var2}}
{{end}}
le 小於等於
{{if le .var1 .var2}}
{{end}}
gt 大於
{{if gt .var1 .var2}}
{{end}}
ge 大於等於
{{if ge .var1 .var2}}
{{end}}
迴圈
main.go
func main() {
http.HandleFunc("/", SayHello)
if err := http.ListenAndServe(":8000", nil); err != nil {
fmt.Println(err.Error())
return
}
}
func SayHello(w http.ResponseWriter, r *http.Request) {
// 解析模板檔案,生成模板物件
tmpl, err := template.ParseFiles("./templates/index.html")
if err != nil {
fmt.Println(err.Error())
return
}
// 將資料渲染到模板,並寫入到w
user := User{"哈哈", 88, false, []int{11, 22, 33}, map[string]interface{}{
"name": "定時傳送放得開了",
"age": 998,
}}
tmpl.Execute(w, &user)
}
type User struct {
Name string
Age uint8
Gender bool
Score []int
Height map[string]interface{}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
{{ .Name }}
{{ .Score }}
{{range .Score}}
{{.}}
{{end}}
{{range $i, $v := .Score}}
{{$i}}---{{$v}}
{{end}}
{{range .Height}}
{{.}}
{{end}}
{{range $k, $v := .Height}}
{{$k}}---{{$v}}
{{end}}
</body>
</html>
模板的巢狀
在編寫模板的時候,我們常常將公用的模板進行整合,比如每一個頁面都有導航欄和頁尾,我們常常將其編寫為一個單獨的模組,讓所有的頁面進行匯入,這樣就不用重複的編寫了。
任何網頁都有一個主模板,然後我們可以在主模板內嵌入子模板來實現模組共享。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
<!--定義子模板-->
{{define "navbar"}}
<h1>啊哈哈哈</h1>
<p>嘿嘿</p>
{{end}}
<!--使用子模板-->
{{template "navbar" .}}
</body>
</html>
模板中使用函式
golang的模板其實功能很有限,很多複雜的邏輯無法直接使用模板語法來表達,所以只能使用模板函式來繞過。
首先,template包建立新的模板的時候,支援.Funcs方法來將自定義的函式集合匯入到該模板中,後續通過該模板渲染的檔案均支援直接呼叫這些函式。
該函式集合的定義為:
type FuncMap map[string]interface{}
key為方法的名字,value則為函式。這裡函式的引數個數沒有限制,但是對於返回值有所限制。有兩種選擇,一種是隻有一個返回值,還有一種是有兩個返回值,但是第二個返回值必須是error型別的。這兩種函式的區別是第二個函式在模板中被呼叫的時候,假設模板函式的第二個引數的返回不為空,則該渲染步驟將會被打斷並報錯。
在模板檔案內,呼叫方法也非常的簡單:
{{funcname .arg1 .arg2}}
假設我們定義了一個函式
func add(left int, right int) int
則在模板檔案內,通過呼叫
{{add 1 2}}
就可以獲得取值: 3
這個結果,golang的預定義函式沒有add,所以有點兒麻煩。
main.py
func main() {
http.HandleFunc("/", SayHello)
if err := http.ListenAndServe(":8000", nil); err != nil {
fmt.Println(err.Error())
return
}
}
func SayHello(w http.ResponseWriter, r *http.Request) {
// 解析模板檔案,生成模板物件
tmpl := template.New("./templates/index.html")
tmpl.Funcs(template.FuncMap{
"add": Add,
})
tmpl, err := tmpl.ParseFiles("./templates/index.html")
if err != nil {
fmt.Println(err.Error())
return
}
// 將資料渲染到模板,並寫入到w
user := User{"哈哈", 88, false, []int{11, 22, 33}, map[string]interface{}{
"name": "定時傳送放得開了",
"age": 998,
}}
if err := tmpl.ExecuteTemplate(w, "index.html", user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
//tmpl.Execute(w, &user)
}
type User struct {
Name string
Age uint8
Gender bool
Score []int
Height map[string]interface{}
}
func Add(a, b int) int {
return a + b
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
哈哈哈哈
{{add 11 22}}
</body>
</html>
預定義函式
執行模板時,函式從兩個函式字典中查詢:首先是模板函式字典,然後是全域性函式字典。一般不在模板內定義函式,而是使用Funcs方法新增函式到模板裡。
預定義的全域性函式如下:
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,模板執行會中斷並返回給呼叫模板執行者該錯誤;
比較函式
布林函式會將任何型別的零值視為假,其餘視為真。
下面是定義為函式的二元比較運算的集合:
eq 如果arg1 == arg2則返回真
ne 如果arg1 != arg2則返回真
lt 如果arg1 < arg2則返回真
le 如果arg1 <= arg2則返回真
gt 如果arg1 > arg2則返回真
ge 如果arg1 >= arg2則返回真
為了簡化多引數相等檢測,eq(只有eq)可以接受2個或更多個引數,它會將第一個引數和其餘引數依次比較,返回下式的結果:
{{eq arg1 arg2 arg3}}
比較函式只適用於基本型別(或重定義的基本型別,如”type Celsius float32”)。但是,整數和浮點數不能互相比較。