Go --- html/template模板包的使用
這里說的是go 語言中自帶的包html/template里的一些基本操作
當然還有text/template,也是個模板包,但是這個并不是網頁安全的,如果 往模板上傳的是標簽,它是會將標簽轉成html格式的,這一般來說是不允許的。
在本博客中所有的例子都是經過go build,打包成可執行文件執行的,如果使用goland啟動標志其中會導致找不到包而報錯。如果過真的想用啟動標志啟動,建議改下解析文件模板的路徑。
例子碼云地址:go-templateLearn
目錄
- 注釋
- 變量的使用
- 判斷與清楚空白符操作
- 判斷
- 去空白字符
- 循環、with和與預定義函數的使用
- 循環
- with
- 預定義函數
- 自定義函數
- 嵌套
- 填空 block
- 安全測試
- 修改默認標識符
- 一個簡單的表單傳值的例子
## 簡單使用
要使用模板需要分三個步驟,分別是定義、解析和渲染,下面咱一步一步來
定義
創建一個.tmpl或是.tpl文件,在goland中第一次創建這種類型的文件他會讓你選用什么文件的格式去提示這類文件,這時候選擇 go template files。
-
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>測試</title> </head> <body>{{ . }}<br>{{ .name}}<br>{{ .sex}} </body> </html>其中{{ }} 是模板里的標識符,該標識符可能會與Vue中某些代碼沖突,后面會提供修改默認標識符的方法,在表示符中的 . 代表著數據,這個數據是從后臺傳來的。
解析
t, err := template.ParseFiles("./test.tmpl")將剛才定義的模板文件解析到程序中,上方調用的方法是解析多個文件
解析方法
// 解析多個文件 func ParseFiles(filenames ...string) (*Template, error) {return parseFiles(nil, readFileOS, filenames...) } // 通過正則表達式解析多個文件 func ParseGlob(pattern string) (*Template, error) {return parseGlob(nil, pattern) } // 類似于上面兩種方法,但是是從文件系統fs讀取,而不是主機操作系統的文件系統。 func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {return parseFS(nil, fs, patterns) }渲染
mp := map[string]interface{}{"name": "張三","sex": "男",}err = t.Execute(w, mp)將數據渲染到剛解析的模板中
// 可以看出這個data是個空接口類型,也就意味著是什么值都可以傳的 func (t *Template) Execute(wr io.Writer, data interface{}) error {if err := t.escape(); err != nil {return err}return t.text.Execute(wr, data) }另外一種渲染方法,指定模板文件進行渲染,適用于有好多解析文件在一起時使用
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {tmpl, err := t.lookupAndEscapeTemplate(name)if err != nil {return err}return tmpl.text.Execute(wr, data) }-
main.go
package mainimport ("fmt""html/template""net/http" )func sayHello(w http.ResponseWriter,r *http.Request) {t, err := template.ParseFiles("./test.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}mp := map[string]interface{}{"name": "張三","sex": "男",}err = t.Execute(w, mp)if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }
注釋
注釋使用的符號為{{/* */}},支持多行注釋,如
{{/*注釋內容 */}}變量的使用
在模板中使用變量是利用 $ 符號
賦值
{{ $obj := 數據 }}聲明變量之后就可以在模板文件中使用了。
例子:
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>變量使用</title> </head> <body>MSG :{{ . }}<br>姓名 :{{ .Name }}<br>性別 : {{ .Sex }}<br> {{/* 使用變量 */}}<div>{{ $num := "123" }}年歲 :{{ $num }}</div> </body> </html>main.go
package mainimport ("fmt""html/template""net/http" )func sayHello(w http.ResponseWriter,r *http.Request) {t, err := template.ParseFiles("./test.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}err = t.Execute(w, struct {Name stringSex string}{Name: "張三",Sex: "女",})if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }? 這里要注意一點,就是當傳給模板的數據為結構體時,根據go語言的特性,屬性名小寫的屬性外界將獲取不到值。
判斷與清楚空白符操作
判斷
在模板中的判斷語句寫法同go語言類似
{{ if [比較函數] 變量 [比較對象] }}如果為真要執行的語句 {{ end }}如果是只有變量的話,就判斷變量是否存在值,如果存在就執行。
也可以使用 if … else … 語句,或if … else if … 語句,如
{{ if [比較函數] 變量 [比較對象] }}{{ else }}{{ end }} {{ if [比較函數] 變量 [比較對象] }}{{ else if [比較函數] 變量 [比較對象]}}{{ end }}去空白字符
使用
清楚變量左右兩側的空白符號,當然也可以只清除一側,只需要將不需要清除的一側的 - 舍去 {{- -}}例子:
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>判斷與清楚空白符操作</title> </head> <body>MSG :{{ . }}<br>姓名 :{{ .Name }}<br>性別 : {{ .Sex }}<br>{{ $num := .Age }}年齡 :{{ $num }} {{/* 判斷使用 注意,要先寫條件之后跟上比較對象*/}} {{/* 比較函數 eq == eq可以進行多個值比較 如 eq n1 n2 n3 ,就會拿n1 分別跟n2,n3比較 ne != lt < le <= gt > ge >= */}}<div>{{ if lt $num 18 }}好好吃飯{{ else if ge $num 18 }}別在熬夜了{{end}}</div> {{/* {{- -}} 取出空白符的符號使用*/}}{{ $num }} = {{ $num }}<br> {{/* 取出空白符,讓左側或右側能與其他的文本貼貼*/}}{{ $num -}} = {{- $num }} </body> </html>main.go
package mainimport ("fmt""html/template""net/http""os""path/filepath" )func sayHello(w http.ResponseWriter,r *http.Request) {// ./ 代表項目路徑t, err := template.ParseFiles("./test.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}err = t.Execute(w, struct {Name stringSex stringAge int}{Name: " 張三 ",Sex: "女",Age: 20,})if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)root := filepath.Dir(os.Args[0])fmt.Println(root)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }循環、with和與預定義函數的使用
循環
使用range關鍵字進行遍歷
這個變量只能是數組、切片、map或者通道 {{ range 變量 }}{{ end }} {{ range $index,$valuse = 變量 }}{{ end }}在range中也可以使用else語句,如果所遍歷的這個變量長度為0,則執行else語句
{{ range 變量 }}{{ else }}{{ end }}with
with的作用為重新定義 . 所代表的數據,這個重新定義有一個范圍,只有在范圍內 . 才代表with重新定義的那個數據
{{ with 變量 }} 在這中間 . 都將被替換為變量的數據 {{ end }}當賦值的變量為空時,可以使用 else 語句來檢測 .有沒有被重新賦值
{{ with 變量 }}{{ else }} 如果變量為空則執行這里的語句 {{ end }}預定義函數
模板中的預定義函數有:
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。這個函數在html/template中不可用。 urlquery以適合嵌入到網址查詢中的形式返回其參數的文本表示的轉義值。這個函數在html/template中不可用。 js返回與其參數的文本表示形式等效的轉義JavaScript。 call執行結果是調用第一個參數的返回值,該參數必須是函數類型,其余參數作為調用該函數的參數;如"call .X.Y 1 2"等價于go語言里的dot.X.Y(1, 2);其中Y是函數類型的字段或者字典的值,或者其他類似情況;call的第一個參數的執行結果必須是函數類型的值(和預定義函數如print明顯不同);該函數類型值必須有1到2個返回值,如果有2個則后一個必須是error接口類型;如果有2個返回值的方法返回的error非nil,模板執行會中斷并返回給調用模板執行者該錯誤;例子:
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>循環,with和預定義函數的使用</title> </head> <body> {{/* 循環 */}}{{ range $index,$v1 := . }}下標:{{ $index }}姓名:{{ $v1 }}<br>{{ end }} <hr> {{/* with 更改點的值*/}}開始時:{{ . }}<br>{{ with "斗地主研討會"}}轉換后:{{ . }}{{ end }} <hr> {{/* 預定義函數 */}} {{/* 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。這個函數在html/template中不可用。 urlquery以適合嵌入到網址查詢中的形式返回其參數的文本表示的轉義值。這個函數在html/template中不可用。 js返回與其參數的文本表示形式等效的轉義JavaScript。 call執行結果是調用第一個參數的返回值,該參數必須是函數類型,其余參數作為調用該函數的參數;如"call .X.Y 1 2"等價于go語言里的dot.X.Y(1, 2);其中Y是函數類型的字段或者字典的值,或者其他類似情況;call的第一個參數的執行結果必須是函數類型的值(和預定義函數如print明顯不同);該函數類型值必須有1到2個返回值,如果有2個則后一個必須是error接口類型;如果有2個返回值的方法返回的error非nil,模板執行會中斷并返回給調用模板執行者該錯誤; */}}研討會人數:{{ len . }} </body> </html>main.go
package mainimport ("fmt""html/template""net/http""os""path/filepath" )func sayHello(w http.ResponseWriter,r *http.Request) {// ./ 代表項目路徑t, err := template.ParseFiles("./test.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}strings := []string{"張安","潘鳳","李翔"}err = t.Execute(w,strings)if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)root := filepath.Dir(os.Args[0])fmt.Println(root)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }自定義函數
若是覺得模板中的預定義函數不夠用,這時候就需要定義自己的函數了。
我們一般不在模板中定義自己的函數,而是在渲染模板之前給定自己所定義的函數,這將使用了Fancs方法。
scold := func(a string) string {return a + "TNND"} // 這個New就是用給的這個名字重新分配一個模板 // 調用Funcs方法前要先調用New方法t, err := template.New("test").Funcs(template.FuncMap{"scold" : scold}).ParseFiles("./test.tmpl")Funcs方法:
// 可以看出,在Funcs中要傳入一個 map[string]interface{} , // 其中是string代表的自定義的方法在模板中叫啥名 // 第二個空接口穿的應該是自定義的函數 func (t *Template) Funcs(funcMap FuncMap) *Template {t.text.Funcs(template.FuncMap(funcMap))return t } type FuncMap map[string]interface{}在模板中使用自定義函數時,要用到管道的方法
{{ a | b }} 這種寫法的意思是將 a 的輸出通過管道再作為 b 的輸入,最后呈現出來的是 b 的輸出例子:
test.tmpl
{{ define "test" }} <!DOCTYPE html> <html lang="zh-CN"> <head><title>自定義函數</title> </head><body>{{/*{{ a | b }}這種寫法的意思是將 a 的輸出通過管道再作為 b 的輸入,最后呈現出來的是 b 的輸出*/}}{{ . | scold }} </body> </html> {{end}}main.go
package mainimport ("fmt""html/template""net/http" )func sayHello(w http.ResponseWriter,r *http.Request) {scold := func(a string) string {return a + "TNND"}t, err := template.New("test").Funcs(template.FuncMap{"scold" : scold}).ParseFiles("./test.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}err = t.Execute(w, "為什么不喝,")if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }嵌套
就是在一個模板中嵌套另外的一個模板。
需要使用的語法為
在需要嵌套的地方 {{ template 模板名 . }} 其中,這個 . 是在模板中傳遞數據用的例子:
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>嵌套</title> </head> <body>ul :<br>{{ template "ul.tmpl" . }}<hr>ol :<br>{{ template "ol.tmpl" . }} </body> </html>ol.tmpl
<ol><li>{{ .name }}</li><li>{{ .sex }}</li> </ol>ul.tmpl
<ul><li>{{ .name }}</li><li>{{ .sex }}</li> </ul>main.go
package mainimport ("fmt""html/template""net/http" )func nest(w http.ResponseWriter,r *http.Request) {// 不能寫成template.ParseFiles("./ul.tmpl","./test.tmpl","./ol.tmpl")// 因為是test.tmpl是主模板,ul.tmpl和ol.tmpl需要等主模板解析完之后在解析t, err := template.ParseFiles("./test.tmpl","./ul.tmpl","./ol.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}mp := map[string]interface{}{"name": "張大炮","sex": "女",}err = t.Execute(w, mp)if err != nil {fmt.Printf("execute file failed err := %v",err)} }func main() {http.HandleFunc("/",nest)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }填空 block
相當于繼承,一個網頁項目會有一些公共的頁面信息,為了不每個頁面都將這些信息寫上一邊,就要有根模板,這時候其他的頁面就需要繼承根模板。
使用block
將數據傳遞給該模板名的模板 {{ block 模板名 數據}}{{ end }}在使用時一定要引入根模板
用 . 來接受根模板傳來的所有數據 {{ template 跟模板名 .}}例子:
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>填空</title> </head> <body><div><p style="text-align: center">{{ block "content" .}}{{ end }}</p></div> </body> </html>content.tmpl
{{ template "test.tmpl" .}}{{/* 一個項目里面肯定要有好些個功能,如果每一個功能都有一些相同作用的文件, 這樣就很難保證這些文件不會出現重復的問題, 有兩個解決方案, 1. 在每一個模板上方定義一個名字 2. 創建一個包,包里分層放,不同層代表著不同的功能 然后使用template.ParseGlob() 正則解析 */}} {{ define "content" }}{{ . }} {{end}}main.go
package mainimport ("fmt""html/template""net/http" )func sayHello(w http.ResponseWriter,r *http.Request) {t, err := template.ParseFiles("./test.tmpl","./content.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}err = t.ExecuteTemplate(w,"content", "這都是大棚的瓜")if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }安全測試
就像開頭所說,html/template這個包傳遞給的模板的數據會進行安全處理。
測試:
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>安全測試</title> </head> <body> {{/*并不會出現執行標簽的現象*/}}彈窗:{{ .msg }}<br>鏈接1:{{ .a }}<br>鏈接2:{{ .a | trust }}<br> </body> </html>main.go
package mainimport ("fmt""html/template""net/http" )func sayHello(w http.ResponseWriter,r *http.Request) {t, err := template.New("test.tmpl").Funcs(template.FuncMap{"trust": func(s string) template.HTML {return template.HTML(s)},}).ParseFiles("./test.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}m := map[string]interface{}{"msg":"<script>alert('張三攛掇著李四去王五家打了趙六')</script>","a": "<a href='https://blog.csdn.net/weixin_52025712'>本人博客</a>",}err = t.Execute(w, m)if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }修改默認標識符
模板中默認標識符 {{ }} 可能會與其他語言中的表示符沖突,這時候我們就要重新定義默認標識符,需要使用Delims方法
// 修改默認標識符為 [ ] t, err := template.New("test.tmpl").Delims("[","]").ParseFiles("./test.tmpl")Delims:
// left和rigth分別代表標識符左右兩個部分 func (t *Template) Delims(left, right string) *Template {t.text.Delims(left, right)return t }例子:
test.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>修改默認標識符</title> </head> <body>[ . ] </body> </html>main.go
package mainimport ("fmt""html/template""net/http" )func sayHello(w http.ResponseWriter,r *http.Request) {t, err := template.New("test.tmpl").Delims("[","]").ParseFiles("./test.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}err = t.Execute(w, "hello")if err != nil {fmt.Printf("execute file failed err := %v",err)}}func main() {http.HandleFunc("/",sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }一個簡單的表單傳值的例子
get.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>表單傳值</title> </head> <body><form action="/" method="post"><label><input type="text" name="name"></label><br><label><input type="text" name="age"></label><br><label><input type="radio" name="sex" value="男"></label> 男<br><label><input type="radio" name="sex" value="女"></label> 女<br><button type="submit">提交</button> </form></body> </html>post.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head><title>接受表單傳過來的值</title> </head> <body>姓名:{{ .name }}<br>性別:{{ .sex }}<br>年齡:{{ .age }} </body> </html>main.go
package mainimport ("fmt""html/template""net/http" )func form(w http.ResponseWriter,r *http.Request) {switch r.Method {case "GET":t, err := template.ParseFiles("./get.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}err = t.Execute(w, nil)if err != nil {fmt.Printf("execute file failed err := %v",err)}case "POST":t, err := template.ParseFiles("./post.tmpl")if err != nil {fmt.Printf("parse file failed err := %v",err)}name := r.FormValue("name")//fmt.Printf("name = %s\n",name)sex := r.FormValue("sex")//fmt.Printf("sex = %s\n",sex)age := r.FormValue("age")//fmt.Printf("age = %s\n",age)m := map[string]interface{}{"name":name,"sex":sex,"age":age,}err = t.Execute(w, m)if err != nil {fmt.Printf("execute file failed err := %v",err)}}}func main() {// 配置路由http.HandleFunc("/",form)// 啟動err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Printf("listen address failed err = %v",err)} }參考文章:
李文周的博客
xyz098的簡書
希望大家能多去支持一下上面的兩位大佬
最后再向大家推薦一首今天在單曲循環的歌遇見 西安話版 吳昊晨
總結
以上是生活随笔為你收集整理的Go --- html/template模板包的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何防止关联,求一个小号多开工具?
- 下一篇: Python3,1行代码,制作GUI图形