1. 程式人生 > 其它 >golang中的標準庫template

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”)。但是,整數和浮點數不能互相比較。