在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一、模板与渲染在一些前后端不分离的Web架构中,我们通常需要在后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。 我们这里说的模板可以理解为事先定义好的HTML文档文件,模板渲染的作用机制可以简单理解为文本替换操作–使用相应的数据去替换HTML文档中事先准备好的标记。 很多编程语言的Web框架中都使用各种模板引擎,比如Python语言中Flask框架中使用的jinja2模板引擎。 二、Go语言的模板引擎Go语言内置了文本模板引擎
三、模板引擎的使用Go语言模板引擎的使用可以分为三部分:定义模板文件、解析模板文件和模板渲染. 1. 定义模板文件其中,定义模板文件时需要我们按照相关语法规则去编写,后文会详细介绍。 2. 解析模板文件上面定义好了模板文件之后,可以使用下面的常用方法去解析模板文件,得到模板对象: func (t *Template) Parse(src string) (*Template, error) func ParseFiles(filenames ...string) (*Template, error) func ParseGlob(pattern string) (*Template, error) 当然,你也可以使用 渲染模板简单来说就是使用数据去填充模板,当然实际上可能会复杂很多。 func (t *Template) Execute(wr io.Writer, data interface{}) error func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error 4. 基本示例4.1 定义模板文件我们按照Go模板语法定义一个 <!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Hello</title> </head> <body> <p>Hello {{ . }}</p> </body> </html> 4.2 解析和渲染模板文件然后我们创建一个 package main import ( "html/template" "log" "net/http" ) func sayHello(w http.ResponseWriter, r *http.Request) { // 解析模板 t,err := template.ParseFiles("./hello.tmpl") if err!=nil { log.Println("Parse template failed, err%v", err) return } // 渲染模板 name := "飞哥哥" err = t.Execute(w, name) if err!=nil { log.Println("render template failed, err%v", err) return } } func main() { http.HandleFunc("/", sayHello) err := http.ListenAndServe(":9000", nil) if err!=nil { log.Println("http server start failed,err:%v",err) } } 将上面的 四、模板语法1. {{.}}
2. 注释{{/* a comment */}} 注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。 3. pipeline
注意:并不是只有使用了 4. 变量我们还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下: $obj := {{.}} 其中 5. 移除空格有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用 例如: {{- .Name -}} 注意: 6. 条件判断Go模板语法中的条件判断有以下几种: {{if pipeline}} T1 {{end}} {{if pipeline}} T1 {{else}} T0 {{end}} {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} 7. rangeGo的模板语法中使用 {{range pipeline}} T1 {{end}} 如果pipeline的值其长度为0,不会有任何输出 {{range pipeline}} T1 {{else}} T0 {{end}} 如果pipeline的值其长度为0,则会执行T0。 8. with{{with pipeline}} T1 {{end}} 如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。 {{with pipeline}} T1 {{else}} T0 {{end}} 如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。 9. 预定义函数执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用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。 这个函数在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,模板执行会中断并返回给调用模板执行者该错误; 10. 比较函数布尔函数会将任何类型的零值视为假,其余视为真。 下面是定义为函数的二元比较运算的集合: 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”)。但是,整数和浮点数不能互相比较。 11. 自定义函数Go的模板支持自定义函数。 func sayHello(w http.ResponseWriter, r *http.Request) { htmlByte, err := ioutil.ReadFile("./hello.tmpl") if err != nil { fmt.Println("read html failed, err:", err) return } // 自定义一个夸人的模板函数 kua := func(arg string) (string, error) { return arg + "真帅", nil } // 采用链式操作在Parse之前调用Funcs添加自定义的kua函数 tmpl, err := template.New("hello").Funcs(template.FuncMap{"kua": kua}).Parse(string(htmlByte)) if err != nil { fmt.Println("create template failed, err:", err) return } user := UserInfo{ Name: "小王子", Gender: "男", Age: 18, } // 使用user渲染模板,并将结果写入w tmpl.Execute(w, user) } 我们可以在模板文件 {{kua .Name}} 12. 嵌套template我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过 举个例子: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>tmpl test</title> </head> <body> <h1>测试嵌套template语法</h1> <hr> {{template "ul.tmpl"}} <hr> {{template "ol.tmpl"}} </body> </html> {{ define "ol.tmpl"}} <ol> <li>吃饭</li> <li>睡觉</li> <li>打豆豆</li> </ol> {{end}}
<ul> <li>注释</li> <li>日志</li> <li>测试</li> </ul> 我们注册一个 http.HandleFunc("/tmpl", tmplDemo)
func tmplDemo(w http.ResponseWriter, r *http.Request) { tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl") if err != nil { fmt.Println("create template failed, err:", err) return } user := UserInfo{ Name: "小王子", Gender: "男", Age: 18, } tmpl.Execute(w, user) } 注意:在解析模板时,被嵌套的模板一定要在后面解析,例如上面的示例中 13. block{{block "name" pipeline}} T1 {{end}}
定义一个根模板 <!DOCTYPE html> <html lang="zh-CN"> <head> <title>Go Templates</title> </head> <body> <div class="container-fluid"> {{block "content" . }}{{end}} </div> </body> </html> 然后定义一个 {{template "base.tmpl" .}} {{define "content"}} <div>Hello world!</div> {{end}} 然后使用 func index(w http.ResponseWriter, r *http.Request){ tmpl, err := template.ParseGlob("templates/*.tmpl") if err != nil { fmt.Println("create template failed, err:", err) return } err = tmpl.ExecuteTemplate(w, "index.tmpl", nil) if err != nil { fmt.Println("render template failed, err:", err) return } } 如果我们的模板名称冲突了,例如不同业务线下都定义了一个
14. 修改默认的标识符Go标准库的模板引擎使用的花括号 template.New("index3.tmpl").Delims("{[","]}").ParseFiles("templates/index3.tmpl") <h1>hello {[ . ]}</h1> 五、text/template与html/tempalte的区别
例如,我定义下面的模板文件: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Hello</title> </head> <body> {{.}} </body> </html> 这个时候传入一段JS代码并使用 但是在某些场景下,我们如果相信用户输入的内容,不想转义的话,可以自行编写一个safe函数,手动返回一个 func xss(w http.ResponseWriter, r *http.Request){ tmpl,err := template.New("xss.tmpl").Funcs(template.FuncMap{ "safe": func(s string)template.HTML { return template.HTML(s) }, }).ParseFiles("./xss.tmpl") if err != nil { fmt.Println("create template failed, err:", err) return } jsStr := `<script>alert('嘿嘿嘿')</script>` err = tmpl.Execute(w, jsStr) if err != nil { fmt.Println(err) } } 这样我们只需要在模板文件不需要转义的内容后面使用我们定义好的safe函数就可以了。
|
请发表评论