Go 语言的模板引擎也是介于无逻辑模板引擎和嵌入逻辑模板引擎之间的一种模板引擎。在 Web 应用里面,模板引擎通常由处理器负责触发。
Go 的模板都是文本文档(其中 Web 应用的模板通常都是 HTML),它们都嵌入了一些称为动作(action)的指令。从模板引擎的角度来说,模板就是嵌入了动作的文本(这些文本通常包含在模板文件里面),而模板引擎则通过分析并执行这些文本来生成出另外一些文本。Go 语言拥有通用模板引擎库text/template,它可以处理任意格式的文本,除此之外,Go 语言还拥有专门为 HTML 格式而设的模板引擎库html/template。模板中的动作默认使用两个大括号{{和}}包围,接下来我们将会了解和简单使用模版引擎的基本语法。

ParseFiles

ParseFiles是一个独立的(standalone)函数,它可以对模板文件进行语法分析,并创建出一个经过语法分析的模板结构以供Execute方法执行。当用户调用ParseFiles方法的时候,Go 会创建一个新的模板,并将用户给定的模板文件的名字用作这个新模板的名字,代码如下所示。

t, _ := template.ParseFiles("tmpl.html")
# 等同于
t := template.New("tmpl.html")
t, _ := t.ParseFiles("tmpl.html")

ParseFiles可以接受一个或多个文件名作为参数,代码如下所示。

t, _ := template.ParseFiles("t1.html", "t2.html")

当向ParseFiles传入单个文件时,ParseFiles返回的是一个模板;而在向ParseFiles传入多个文件时,ParseFiles返回的则是一个模板集合。

ParseGlob

ParseGlob会对匹配给定模式的所有文件进行语法分析,如下所示。

# 匹配后缀名为html的文件
t, _ := template.ParseGlob("*.html")

Must

Must函数可以包裹起一个函数,被包裹的函数会返回一个指向模板的指针和一个错误,如果这个错误不是nil,那么Must函数将产生一个 panic。(在 Go 里面,panic 会导致正常的执行流程被终止:如果 panic 是在函数内部产生的,那么函数会将这个 panic 返回给它的调用者。panic 会一直向调用栈的上方传递,直至main函数为止,并且程序也会因此而崩溃。),如下所示。

t := template.Must(template.ParseFiles("tmpl.html"))

Execute

当ParseFiles对模板文件进行语法分析之后,我们将路由函数的数据与参数w(http.ResponseWriter)传入Execute方法,由Execute生成相应的HTML网页,使用方法如下。

# 使用ParseFiles函数对模版文件tmpl.html进行语法分析,生成对象t
t, _ := template.ParseFiles("tmpl.html")
# 由对象t调用Execute方法
# 参数w为路由函数参数http.ResponseWriter,Hello World!模版变量,由路由函数生成并传入模版文件
t.Execute(w, "Hello World!")

ExecuteTemplate

我们知道ParseFiles可以传入多个模版文件,如果在ParseFiles方法中传入多个模版文件,它将会生成模板集合。当对模板集合调用Execute方法的时候,Execute方法只会执行模板集合中的第一个模板。如果想要执行的不是模板集合中的第一个模板而是其他模板,就需要使用ExecuteTemplate方法,如下所示。

t, _ := template.ParseFiles("t1.html", "t2.html")
# Execute只会执行模版文件t1.html
t.Execute(w, "Hello World!")
# 如果只执行t2.html,则调用ExecuteTemplate
t.ExecuteTemplate(w, "t2.html", "Hello World!")

动作

Go 模板的动作就是一些嵌入在模板里面的命令,这些命令在模板中使用两个大括号{{和}}进行包围。Go 拥有一套非常丰富的动作集合,它们不仅功能强大,而且还非常灵活多变。本节将讨论以下几种主要的动作:

  • 条件动作
  • 迭代动作
  • 设置动作
  • 包含动作

条件动作

条件动作是根据判条件选择相应的内容,如下所示。

{{ if arg }}
 some content
{{ end }}

或者
{{ if arg }}
 some content
{{ else }}
 other content
{{ end }}

迭代动作

迭代动作可以对数组、切片、映射或者通道进行迭代,而在迭代循环的内部,点(.)则会被设置为当前被迭代的元素,如下所示。

{{ range array }}
 Dot is set to the element {{ . }}
{{ end }}

# 还可以添加使用else
{{ range . }}
 <li>{{ . }}</li>
{{ else }}
 <li> Nothing to show </li>
{{ end}}

设置动作

设置动作允许用户在指定的范围之内为点(.)设置值。在{{ with arg }}和{{ end }}之间的点将被设置为参数arg的值,如下所示。

{{ with "world"}}
 Dot is set to {{ . }}
{{ end }}

比如在{{ with “world” }}之前的点的值设置为hello,而在{{ with “world” }}和{{ end }}之间的点则会被设置成world;但是,在语句{{ end }}执行完毕之后,点的值又会重新被设置成hello。

包含动作

包含动作允许用户在一个模板里面包含另一个模板,从而构建出嵌套的模板。包含动作的格式为{{ template “name” }},其中name参数为被包含模板的名字。比如在t1.html中嵌套t2.html,实现代码如下。

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=9">
  <title>Go Web Programming</title>
 </head>
 <body>
  <div> This is t1.html before</div>
  <div>This is the value of the dot in t1.html - [{{ . }}]</div>
  <hr/>
  {{ template "t2.html"  . }}
  <hr/>
  <div> This is t1.html after</div>
 </body>
</html>

# t2.html代码如下
<div style="background-color: yellow;">
 This is t2.html<br/>
 This is the value of the dot in t2.html - [{{ . }}]
</div>

由于t1.html中嵌套t2.html,因此路由的处理函数需要传入相应的模版文件,如下所示。

func process(w http.ResponseWriter, r *http.Request) {
  t, _ := template.ParseFiles("t1.html", "t2.html")
  t.Execute(w, "Hello World!")
}

在执行嵌套模板时,我们必须对涉及的所有模板文件都进行语法分析。牢记这一点是非常重要的,忘记对必要的模板文件进行语法分析将导致程序出现不正确的结果。
ParseFiles在进行语法分析时,用户给定的第一个模板文件将成为主模板(main template),当用户对模板集合调用Execute方法时,主模板将被执行。
从模版文件t1.html看到,{{ template “t2.html” . }}是嵌套模版文件t2.html,实心点(.)是将模版文件t1.html的变量传入模版文件t2.html,这是模版嵌套之间的数据变量传递。