《Go語言四十二章經》第三十五章 模板
《Go語言四十二章經》第三十五章 模板
作者:李驍
Printf也可以做到輸出格式化,當然,對於簡單的例子來說足夠了,但是我們有時候還是需要複雜的輸出格式,甚至需要將格式化程式碼分離開來。這時,可以使用text/template和html/template。
Go 官方庫提供了兩個模板庫: text/template 和 html/template 。這兩個庫類似,當需要輸出html格式的程式碼時需要使用 html/template。
35.1 text/template
所謂模板引擎,則將模板和資料進行渲染的輸出格式化後的字元程式。對於Go,執行這個流程大概需要三步。
- 建立模板物件
- 載入模板
- 執行渲染模板
其中最後一步就是把載入的字元和資料進行格式化。
package main import ( "log" "os" "text/template" ) const templ = ` {{range .}}---------------------------------------- Name:{{.Name}} Price:{{.Price | printf "%4s"}} {{end}}` var report = template.Must(template.New("report").Parse(templ)) type Book struct { Namestring Price float64 } func main() { Data := []Book{{"《三國演義》", 19.82}, {"《儒林外史》", 99.09}, {"《史記》", 26.89}} if err := report.Execute(os.Stdout, Data); err != nil { log.Fatal(err) } }
程式輸出: ---------------------------------------- Name:《三國演義》 Price:%!s(float64=19.82) ---------------------------------------- Name:《儒林外史》 Price:%!s(float64=99.09) ---------------------------------------- Name:《史記》 Price:%!s(float64=26.89)
如果把模板的內容存在一個文字檔案裡tmp.txt:
{{range .}}---------------------------------------- Name:{{.Name}} Price:{{.Price | printf "%4s"}} {{end}}
我們可以這樣處理:
package main import ( "log" "os" "text/template" ) var report = template.Must(template.ParseFiles("tmp.txt")) type Book struct { Namestring Price float64 } func main() { Data := []Book{{"《三國演義》", 19.82}, {"《儒林外史》", 99.09}, {"《史記》", 26.89}} if err := report.Execute(os.Stdout, Data); err != nil { log.Fatal(err) } }
程式輸出: ---------------------------------------- Name:《三國演義》 Price:%!s(float64=19.82) ---------------------------------------- Name:《儒林外史》 Price:%!s(float64=99.09) ---------------------------------------- Name:《史記》 Price:%!s(float64=26.89)
Tmpl, err := template.ParseFiles("tmp.txt") //建立模板,自動 new("name")
ParseFiles接受一個字串,字串的內容是一個模板檔案的路徑。
ParseGlob是用正則的方式匹配多個檔案。
假設一個目錄裡有a.txt b.txt c.txt的話,用ParseFiles需要寫3行對應3個檔案,如果有更多檔案,可以用ParseGlob。
寫成template.ParseGlob("*.txt") 即可。
var report = template.Must(template.ParseFiles("tmp.txt"))
函式Must,它的作用是檢測模板是否正確,例如大括號是否匹配,註釋是否正確的關閉,變數是否正確的書寫。
35.2 html/template
和text、template類似,html/template主要在提供支援HTML的功能,所以基本使用上和上面差不多,我們來看下面程式碼:
index.html:
<!doctype html> <head> <meta charset="UTF-8"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>Go</title> </head> <body> {{ . }} </body> </html>
main.go:
package main import ( "fmt" "net/http" "text/template" ) func tHandler(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("index.html") t.Execute(w, "Hello World!") } func main() { http.HandleFunc("/", tHandler) http.ListenAndServe(":8080", nil) }
執行程式,在瀏覽器開啟:http://localhost:8080/會看到頁面顯示Hello World!
func(t *Template) ParseFiles(filenames ...string) (*Template, error) func(t *Template) ParseGlob(patternstring) (*Template, error)
從上面程式碼中我們可以看到,通過ParseFile載入了單個Html模板檔案。但最終的頁面很可能是多個模板檔案的巢狀結果。
ParseFiles也支援載入多個模板檔案,模板物件的名字則是第一個模板檔案的檔名。
ExecuteTemplate方法,用於執行指定名字的模板,下面我們根據一段程式碼來看看:
Layout.html,注意在開頭根據模板語法,定義了模板名字,define "layout"。 在結尾處,通過 {{ template "index" }}
注意:通過將模板應用於一個數據結構(即該資料結構作為模板的引數)來執行,來獲得輸出。模板執行時會遍歷結構並將指標表示為.(稱之為dot),指向執行過程中資料結構的當前位置的值。
{{template "header" .}}巢狀模板中,加入.dot 代表在該模板中也可以使用該資料結構,否則不能顯示。
{{ define "layout" }} <!doctype html> <head> <meta charset="UTF-8"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>Go</title> </head> <body> {{ . }} {{ template "index" }} </body> </html> {{ end }}
Index.html: {{ define "index" }} <div> <b>Index</b> </div> {{ end }}
通過define定義模板,還可以通過template action引入模板,類似include。
package main import ( "net/http" "text/template" ) func tHandler(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("layout.html", "index.html") t.ExecuteTemplate(w, "layout", "Hello World!") } func main() { http.HandleFunc("/", tHandler) http.ListenAndServe(":8080", nil) }
執行程式,在瀏覽器開啟:http://localhost:8080/
Hello World! Index
有關ParseGlob方法,則是通過glob萬用字元載入模板,例如 t, _ := template.ParseGlob("*.html")
35.3 模板語法
【模板標籤】 模板標籤用"{{"和"}}"括起來 【註釋】 {{/* a comment */}} 使用"{{/*"和"*/}}"來包含註釋內容 【變數】 {{.}} 此標籤輸出當前物件的值 {{.Admpub}} 表示輸出Struct物件中欄位或方法名稱為"Admpub"的值。 當"Admpub"是匿名欄位時,可以訪問其內部欄位或方法, 比如"Com":{{.Admpub.Com}} , 如果"Com"是一個方法並返回一個Struct物件,同樣也可以訪問其欄位或方法:{{.Admpub.Com.Field1}} {{.Method1 "引數值1" "引數值2"}} 呼叫方法"Method1",將後面的引數值依次傳遞給此方法,並輸出其返回值。 {{$admpub}} 此標籤用於輸出在模板中定義的名稱為"admpub"的變數。當$admpub本身是一個Struct物件時,可訪問其欄位:{{$admpub.Field1}} 在模板中定義變數:變數名稱用字母和數字組成,並帶上"$"字首,採用簡式賦值。 比如:{{$x := "OK"}} 或 {{$x := pipeline}}
【通道函式】 用法1: {{FuncName1}} 此標籤將呼叫名稱為"FuncName1"的模板函式(等同於執行"FuncName1()",不傳遞任何引數)並輸出其返回值。 用法2: {{FuncName1 "引數值1" "引數值2"}} 此標籤將呼叫FuncName1("引數值1", "引數值2"),並輸出其返回值 用法3: {{.Admpub|FuncName1}} 此標籤將呼叫名稱為"FuncName1"的模板函式(等同於執行"FuncName1(this.Admpub)",將豎線"|"左邊的".Admpub"變數值作為函式引數傳送)並輸出其返回值。 【條件判斷】 用法1: {{if pipeline}} T1 {{end}} 標籤結構:{{if ...}} ... {{end}} 用法2: {{if pipeline}} T1 {{else}} T0 {{end}} 標籤結構:{{if ...}} ... {{else}} ... {{end}} 用法3: {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} 標籤結構:{{if ...}} ... {{else if ...}} ... {{end}} 其中if後面可以是一個條件表示式(包括通道函式表示式。pipeline即通道),也可以是一個字元竄變數或布林值變數。當為字元竄變數時,如為空字串則判斷為false,否則判斷為true。
【遍歷】 用法1: {{range $k, $v := .Var}} {{$k}} => {{$v}} {{end}} range...end結構內部如要使用外部的變數,比如.Var2,需要這樣寫:$.Var2 (即:在外部變數名稱前加符號"$"即可,單獨的"$"意義等同於global) 用法2: {{range .Var}} {{.}} {{end}} 用法3: {{range pipeline}} T1 {{else}} T0 {{end}} 當沒有可遍歷的值時,將執行else部分。 【嵌入子模板】 用法1: {{template "name"}} 嵌入名稱為"name"的子模板。使用前請確保已經用{{define "name"}}子模板內容{{end}}定義好了子模板內容。 用法2: {{template "name" pipeline}} 將通道的值賦給子模板中的"."(即"{{.}}") 【子模板巢狀】 {{define "T1"}}ONE{{end}} {{define "T2"}}TWO{{end}} {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}} {{template "T3"}} 輸出: ONE TWO 【定義區域性變數】 用法1: {{with pipeline}} T1 {{end}} 通道的值將賦給該標籤內部的"."。(注:這裡的“內部”一詞是指被{{with pipeline}}...{{end}}包圍起來的部分,即T1所在位置) 用法2: {{with pipeline}} T1 {{else}} T0 {{end}} 如果通道的值為空,"."不受影響並且執行T0,否則,將通道的值賦給"."並且執行T1。 說明:{{end}}標籤是if、with、range的結束標籤。 【例子:輸出字元竄】 {{"\"output\""}} 輸出一個字元竄常量。 {{`"output"`}} 輸出一個原始字串常量 {{printf "%q" "output"}} 函式呼叫.(等同於:printf("%q", "output")。) {{"output" | printf "%q"}} 豎線"|"左邊的結果作為函式最後一個引數。(等同於:printf("%q", "output")。) {{printf "%q" (print "out" "put")}} 圓括號中表達式的整體結果作為printf函式的引數。(等同於:printf("%q", print("out", "put"))。) {{"put" | printf "%s%s" "out" | printf "%q"}} 一個更復雜的呼叫。(等同於:printf("%q", printf("%s%s", "out", "put"))。) {{"output" | printf "%s" | printf "%q"}} 等同於:printf("%q", printf("%s", "output"))。 {{with "output"}}{{printf "%q" .}}{{end}} 一個使用點號"."的with操作。(等同於:printf("%q", "output")。) {{with $x := "output" | printf "%q"}}{{$x}}{{end}} with結構,定義變數,值為執行通道函式之後的結果(等同於:$x := printf("%q", "output")。) {{with $x := "output"}}{{printf "%q" $x}}{{end}} with結構中,在其它動作中使用定義的變數 {{with $x := "output"}}{{$x | printf "%q"}}{{end}} 同上,但使用了通道。(等同於:printf("%q", "output")。)
===============【預定義的模板全域性函式】================ 【and】 {{and x y}} 表示:if x then y else x 如果x為真,返回y,否則返回x。等同於Go中的:x && y 【call】 {{call .X.Y 1 2}} 表示:dot.X.Y(1, 2) call後面的第一個引數的結果必須是一個函式(即這是一個函式型別的值),其餘引數作為該函式的引數。 該函式必須返回一個或兩個結果值,其中第二個結果值是error型別。 如果傳遞的引數與函式定義的不匹配或返回的error值不為nil,則停止執行。 【html】 轉義文字中的html標籤,如將"<"轉義為"<",">"轉義為">"等 【index】 {{index x 1 2 3}} 返回index後面的第一個引數的某個索引對應的元素值,其餘的引數為索引值 表示:x[1][2][3] x必須是一個map、slice或陣列 【js】 返回用JavaScript的escape處理後的文字 【len】 返回引數的長度值(int型別) 【not】 返回單一引數的布林否定值。 【or】 {{or x y}} 表示:if x then x else y。等同於Go中的:x || y 如果x為真返回x,否則返回y。 【print】 fmt.Sprint的別名 【printf】 fmt.Sprintf的別名 【println】 fmt.Sprintln的別名 【urlquery】 返回適合在URL查詢中嵌入到形參中的文字轉義值。(類似於PHP的urlencode)
=================【布林函式】=============== 布林函式對於任何零值返回false,非零值返回true。 這裡定義了一組二進位制比較操作符函式: 【eq】 返回表示式"arg1 == arg2"的布林值 【ne】 返回表示式"arg1 != arg2"的布林值 【lt】 返回表示式"arg1 < arg2"的布林值 【le】 返回表示式"arg1 <= arg2"的布林值 【gt】 返回表示式"arg1 > arg2"的布林值 【ge】 返回表示式"arg1 >= arg2"的布林值 對於簡單的多路相等測試,eq只接受兩個引數進行比較,後面其它的引數將分別依次與第一個引數進行比較, {{eq arg1 arg2 arg3 arg4}} 即只能作如下比較: arg1==arg2 || arg1==arg3 || arg1==arg4 ...
本書《Go語言四十二章經》內容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語言四十二章經》內容在簡書同步地址:https://www.jianshu.com/nb/29056963
雖然本書中例子都經過實際執行,但難免出現錯誤和不足之處,煩請您指出;如有建議也歡迎交流。