1. Template

    html/template包实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用text/template包。

    在基于MVC的Web架构中,我们通常需要在后端渲染一些数据到HTML文件中,从而实现动态的网页效果。

    通过将模板应用于一个数据结构(即该数据结构作为模板的参数)来执行,来获得输出。模板中的注释引用数据接口的元素(一般如结构体的字段或者字典的键)来控制执行过程和获取需要呈现的值。模板执行时会遍历结构并将指针表示为’.‘(称之为”dot”)指向运行过程中数据结构的当前位置的值。

    用作模板的输入文本必须是utf-8编码的文本。”Action”—数据运算和控制单位—由”“界定;在Action之外的所有文本都不做修改的拷贝到输出中。Action内部不能有换行,但注释可以有换行。

    HTML文件代码如下:

    我们的HTTP server端代码如下:

    1. func sayHello(w http.ResponseWriter, r *http.Request) {
    2. // 解析指定文件生成模板对象
    3. tmpl, err := template.ParseFiles("./hello.html")
    4. if err != nil {
    5. fmt.Println("create template failed, err:", err)
    6. return
    7. }
    8. // 利用给定数据渲染模板,并将结果写入w
    9. tmpl.Execute(w, "5lmh.com")
    10. }
    11. func main() {
    12. http.HandleFunc("/", sayHello)
    13. err := http.ListenAndServe(":9090", nil)
    14. if err != nil {
    15. fmt.Println("HTTP server failed,err:", err)
    16. return
    17. }
    18. }

    1.1.2. 模板语法

    {{.}}

    模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象。

    当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如:

    1. // main.go
    2. type UserInfo struct {
    3. Name string
    4. Gender string
    5. Age int
    6. }
    7. func sayHello(w http.ResponseWriter, r *http.Request) {
    8. // 解析指定文件生成模板对象
    9. tmpl, err := template.ParseFiles("./hello.html")
    10. if err != nil {
    11. fmt.Println("create template failed, err:", err)
    12. return
    13. }
    14. // 利用给定数据渲染模板,并将结果写入w
    15. user := UserInfo{
    16. Name: "枯藤",
    17. Gender: "男",
    18. Age: 18,
    19. }
    20. }

    HTML文件代码如下:

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
    7. <title>Hello</title>
    8. <body>
    9. <p>Hello {{.Name}}</p>
    10. <p>性别:{{.Gender}}</p>
    11. <p>年龄:{{.Name}}</p>
    12. </body>
    13. </html>

    注释

    1. {{/* a comment */}}
    2. 注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。

    pipeline

    pipeline是指产生数据的操作。比如{{.}}{{.Name}}等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。

    注意 : 并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。

    变量

    Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下:

    1. $variable := pipeline

    其中$variable是变量的名字。声明变量的action不会产生任何输出。

    Go模板语法中的条件判断有以下几种:

    range

    Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。

    1. {{range pipeline}} T1 {{end}}
    2. 如果pipeline的值其长度为0,不会有任何输出
    3. {{range pipeline}} T1 {{else}} T0 {{end}}
    4. 如果pipeline的值其长度为0,则会执行T0

    with

    1. {{with pipeline}} T1 {{end}}
    2. 如果pipelineempty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot
    3. {{with pipeline}} T1 {{else}} T0 {{end}}
    4. 如果pipelineempty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1

    1.1.4. 预定义函数

    执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。

    预定义的全局函数如下:

    1. and
    2. 函数返回它的第一个empty参数或者最后一个参数;
    3. 就是说"and x y"等价于"if x then y else x";所有参数都会执行;
    4. or
    5. 返回第一个非empty参数或者最后一个参数;
    6. 亦即"or x y"等价于"if x then x else y";所有参数都会执行;
    7. not
    8. 返回它的单个参数的布尔值的否定
    9. len
    10. 返回它的参数的整数类型长度
    11. index
    12. 执行结果为第一个参数以剩下的参数为索引/键指向的值;
    13. "index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
    14. print
    15. fmt.Sprint
    16. printf
    17. fmt.Sprintf
    18. println
    19. fmt.Sprintln
    20. html
    21. 返回其参数文本表示的HTML逸码等价表示。
    22. urlquery
    23. 返回其参数文本表示的可嵌入URL查询的逸码等价表示。
    24. js
    25. 返回其参数文本表示的JavaScript逸码等价表示。
    26. call
    27. 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    28. 其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    29. call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    30. 该函数类型值必须有12个返回值,如果有2个则后一个必须是error接口类型;

    布尔函数会将任何类型的零值视为假,其余视为真。

    1. eq 如果arg1 == arg2则返回真
    2. ne 如果arg1 != arg2则返回真
    3. lt 如果arg1 < arg2则返回真
    4. le 如果arg1 <= arg2则返回真
    5. gt 如果arg1 > arg2则返回真
    6. ge 如果arg1 >= arg2则返回真

    为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:

    1. {{eq arg1 arg2 arg3}}

    比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。但是,整数和浮点数不能互相比较。

    1.1.6. 自定义函数

    Go的模板支持自定义函数。

    我们可以在模板文件hello.html中使用我们自定义的kua函数了。

    1. {{kua .Name}}

    我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。

    举个例子: t.html文件内容如下:

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
    7. <title>tmpl test</title>
    8. </head>
    9. <body>
    10. <h1>测试嵌套template语法</h1>
    11. <hr>
    12. {{template "ul.html"}}
    13. <hr>
    14. {{template "ol.html"}}
    15. </body>
    16. </html>
    17. {{ define "ol.html"}}
    18. <h1>这是ol.html</h1>
    19. <ol>
    20. <li>吃饭</li>
    21. <li>睡觉</li>
    22. <li>打豆豆</li>
    23. </ol>
    24. {{end}}

    ul.html文件内容如下:

    1. <ul>
    2. <li>注释</li>
    3. <li>日志</li>
    4. <li>测试</li>
    5. </ul>

    我们注册一个templDemo路由处理函数.

    1. http.HandleFunc("/tmpl", tmplDemo)

    tmplDemo函数的具体内容如下:

    1. func tmplDemo(w http.ResponseWriter, r *http.Request) {
    2. tmpl, err := template.ParseFiles("./t.html", "./ul.html")
    3. if err != nil {
    4. fmt.Println("create template failed, err:", err)
    5. return
    6. }
    7. user := UserInfo{
    8. Name: "枯藤",
    9. Gender: "男",
    10. Age: 18,
    11. }