2、template

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

模板引擎

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

声明模板,放到web包下

<!DOCTYPE html>
<html lang="en">
  <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>
    <!-- 注意,此处使用的构造体实例属性必须是public的也就是首字母大写 -->
    <p>Id: {{.Id}}</p>
    <p>Name: {{.Name}}</p>
    <p>Age: {{.Age}}</p>
  </body>
</html>

搭建web服务

package main

import (
	"fmt"
	"net/http"
	"text/template"
)

type Stu struct {
	Id   string
	Name string
	Age  int
}

func main() {
	http.HandleFunc("/", indexHandler)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println(err)
	}
}

// indexHandler 首页操作
func indexHandler(resp http.ResponseWriter, req *http.Request) {
	//解析文件,获取模板实例
	t, err := template.ParseFiles("web/index.html")
	if err != nil {
		fmt.Println(err)
	}
	s := Stu{"A123456", "Lucy", 18}
	//将模板写出,并绑定实例对象,解析模板
	t.Execute(resp, s)
}

语法

对象

{{.}}:处于顶级作用域时,表示Execute函数第二个参数传入的实例对象,或者任何数据

如果是在{{range}} {{.}} {{end}}中,代表range迭代出的元素

注释

{{/* a comment */}}

pipeline

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

{{"output" | printf "%q"}}

条件判断

{{if pipeline}} T1 {{end}}

{{if pipeline}} T1 {{else}} T0 {{end}}

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

range

{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为0,不会有任何输出

{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为0,则会执行T0。 

去除空白

template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,对于要解析的内容,不要随意缩进、随意换行

可以在{{符号的后面加上短横线并保留一个或多个空格- 来去除它前面的空白(包括换行符、制表符、空格等),即{{- xxxx

}}的前面加上一个或多个空格以及一个短横线-来去除它后面的空白,即xxxx -}}

{{23}} < {{45}}        -> 23 < 45
{{23}} < {{- 45}}      ->  23 <45
{{23 -}} < {{45}}      ->  23< 45
{{23 -}} < {{- 45}}    ->  23<45

变量

  1. 变量有作用域,只要出现end,则当前层次的作用域结束。内层可以访问外层变量,但外层不能访问内层变量
  2. 有一个特殊变量$,它代表模板的最顶级作用域对象(通俗地理解,是以模板为全局作用域的全局变量),在Execute()执行的时候进行赋值,且一直不变
  3. 变量不可在模板之间继承
// 未定义过的变量
$var := pipeline

// 已定义过的变量
$var = pipeline

with

{{with "xx"}}{{println .}}{{end}}

上面将输出xx,因为.已经设置为xx

内置函数

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,模板执行会中断并返回给调用模板执行者该错误;

嵌套模板

开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成headercontentfooter三个部分。

声明模板的语法

{{define "子模板名称"}}内容{{end}}

调用模板的方法,pipeline为给模板传递的参数,可以不写则为nil

{{template "子模板名称" pipeline}}

例如,我们可以写三个html文件,header.htmlcontent.htmlfooter.html

header.html

{{define "header"}}
<div>
    header
    {{.}}
</div>
{{end}}

footer.html

{{define "footer"}}
<div>
    footer
</div>
{{end}}

content.html

<!DOCTYPE html>
<html lang="en">
  <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>template</title>
  </head>
  <body>
    {{template "header" .}}
      <div>
          content
      </div>
    {{template "footer"}}
  </body>
</html>
package main

import (
    "fmt"
    "os"
    "text/template"
)

func main() {
    // 解析文件,获取模板实例
    s1, _ := template.ParseFiles("header.html", "content.html", "footer.html")
    // 分别执行渲染三个模板
    s1.ExecuteTemplate(os.Stdout, "header", nil)
    s1.ExecuteTemplate(os.Stdout, "content", nil)
    s1.ExecuteTemplate(os.Stdout, "footer", nil)
    s1.Execute(os.Stdout, nil)
}