Go 官方库提供了两个模板库: text/template 和 html/template。这两个库类似,只不过 html/template对html格式做了特别的处理,当需要输出html格式的代码时需要使用html/template。
简单使用
package main
import (
"os"
"text/template"
)
type Student struct {
Name string
}
func main() {
tom := Student{"Tom"}
tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, tom)
if err != nil {
panic(err)
}
}
<h1> It is day number {{.Name}} of the month </h1>
package main
import (
"os"
"text/template"
)
type Student struct {
Name string
}
func main() {
tom := Student{"Tom"}
// name := "Tom"
muban := "hello, {{.Name}}"
tmpl, err := template.New("test").Parse(muban)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, tom)
if err != nil {
panic(err)
}
}
模板变量
- 字符
模板变量可以是boolean, string, character, integer, floating-point, imaginary 或者 complex constant。传给模板这样的数据就可以通过点号.来访问:
{ { . }}
如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。
如果字段还是复杂类型,可以链式访问 { { .Struct.StructTwo.Field }}。
- 模板中的变量
传给模板的数据可以存在模板中的变量中,在整个模板中都能访问。 比如 { {$number := .}}, 我们使用$number作为变量,保存传入的数据,可以使用{ {$number}}来访问变量。
{ {$number := .}}
<h1> It is day number { {$number}} of the month </h1>
var tpl *template.Template
tpl = template.Must(template.ParseFiles("templateName"))
err := tpl.ExecuteTemplate(os.Stdout, "templateName", 23)
上面的例子我们把23传给模板,模板的变量$number的值是23,可以在模板中使用。
模板动作
- if/else 语句:使用if检查数据,如果不满足可以执行else。空值是是false, 0、nil、空字符串或者长度为0的字符串都是false。
<h1>Hello, { {if .Name}} { {.Name}} { {else}} Anonymous { {end}}!</h1>
- 移除空格:往模板中增加不同的值的时候可能会增加一定数量的空格。我们既可以改变我们的模板以便更好的处理它,忽略/最小化这种效果,或者我们还可以使用减号-:
<h1>Hello, { {if .Name}} { {.Name}} { {- else}} Anonymous { {- end}}!</h1>
- range
package main
import (
"os"
"text/template"
)
type Item struct {
Name string
Price int
}
type ViewData struct {
Name string
Items []Item
}
func main() {
tom := ViewData{
Name: "viewData",
Items: []Item{
{
Name: "0",
Price: 0,
},
{
Name: "1",
Price: 1,
},
},
}
tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, tom)
if err != nil {
panic(err)
}
}
{{range .Items}}
<div class="item">
<h3 class="name">{{.Name}}</h3>
<span class="price">${{.Price}}</span>
</div>
{{end}}
模板函数
- 获取索引值: 如果传给模板的数据是map、slice、数组,那么我们就可以使用它的索引值。我们使用{ {index x number}}来访问x的第number个元素, index是关键字。比如{ {index names 2}}等价于names[2]。{ {index names 2 3 4}} 等价于 names[2][3][4](三维数组)。
package main
import (
"os"
"text/template"
)
type person struct {
Name string
FavNums []int
}
func main() {
tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, &person{"Curtis", []int{7, 11, 94}})
if err != nil {
panic(err)
}
}
<body>
<h1> {{- index .FavNums 2}}</h1>
</body>
- and 函数: and函数返回bool值,通过返回第一个空值或者最后一个值。and x y逻辑上相当于if x then y else x
package main
import (
"os"
"text/template"
)
type User struct {
Admin bool
}
type ViewData struct {
*User
}
func main() {
tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, &ViewData{
User: &User{Admin: false},
})
if err != nil {
panic(err)
}
}
{{if and .User .User.Admin}}
You are an admin user!
{{else}}
Access denied!
{{end}}
- or 函数: 类似and
- not 函数: 返回相反的值
{ { if not .Authenticated}}
Access Denied!
{ { end }}
模板比较函数
- 比较: 数据的类型只能是基本类型和命名的基本类型,比如type Temp float3,格式是{ { function arg1 arg2 }}。
- eq: arg1 == arg2
- ne: arg1 != arg2
- lt: arg1 < arg2
- le: arg1 <= arg2
- gt: arg1 > arg2
- ge: arg1 >= arg2
eq函数比较特殊,可以拿多个参数和第一个参数进行比较。{ { eq arg1 arg2 arg3 arg4}}逻辑是arg1==arg2 || arg1==arg3 || arg1==arg4。
嵌套模板和布局
- 嵌套模板: 嵌套模板可以用做跨模板的公共部分代码,比如 header或者 footer。使用嵌套模板我们就可以避免一点小小的改动就需要修改每个模板。嵌套模板定义如下:
{ {define "footer"}}
<footer>
<p>Here is the footer</p>
</footer>
{ {end}}
{ {template "footer"}}
- 模板之间传递变量
// Define a nested template called header
{{define "header"}}
<h1>{ {.}}</h1>
{{end}}
// Call template and pass a name parameter
{{range .Items}}
<div class="item">
{{template "header" .Name}}
<span class="price">${ {.Price}}</span>
</div>
{{end}}
- 创建布局
Glob模式通过通配符匹配一组文件名。template.ParseGlob(pattern string)会匹配所有符合模式的模板。template.ParseFiles(files...)也可以用来解析一组文件。
模板默认情况下会使用配置的参数文件名的base name作为模板名。这意味着views/layouts/hello.gohtml的文件名是hello.gohtml,如果模板中有{ {define “templateName”}}的话,那么templateName会用作这个模板的名字。
模板可以通过t.ExecuteTemplate(w, "templateName", nil)来执行, t是一个类型为Template的对象,w的类型是io.Writer,比如http.ResponseWriter,然后是要执行的模板的名称,以及要传入的数据:
var LayoutDir string = "views/layouts"
var bootstrap *template.Template
func main() {
var err error
bootstrap, err = template.ParseGlob(LayoutDir + "/*.gohtml")
if err != nil {
panic(err)
}
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
bootstrap.ExecuteTemplate(w, "bootstrap", nil)
}
所有的.gohtml文件都被解析,然后当访问/的时候,bootstrap会被执行。
views/layouts/bootstrap.gohtml定义如下:
{{define "bootstrap"}}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Go Templates</title>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<h1>Filler header</h1>
<p>Filler paragraph</p>
</div>
<!-- jquery & Bootstrap JS -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"
</script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">
</script>
</body>
</html>
{{end}}
模板调用函数
- 函数变量(调用结构体的方法)
package main
import (
"os"
"text/template"
)
type User struct {
ID int
Email string
}
func (u User) HasPermission(feature string) bool {
if feature == "feature-a" {
return true
} else {
return false
}
}
func main() {
tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, User{})
if err != nil {
panic(err)
}
}
type User struct {
ID int
Email string
}
func (u User) HasPermission(feature string) bool {
if feature == "feature-a" {
return true
} else {
return false
}
}
{{ if .HasPermission "feature-a" }}
<div class="feature">
<h3>Feature A</h3>
<p>Some other stuff here...</p>
</div>
{{ else }}
<div class="feature disabled">
<h3>Feature A</h3>
<p>To enable Feature A please upgrade your plan</p>
</div>
{{ end }}
- 函数变量(内部函数): 使用 call 调用函数
package main
import (
"os"
"text/template"
)
type User struct {
ID int
Email string
HasPermission func(string) bool
}
func main() {
tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, User{
HasPermission: func(s string) bool {
if s == "a" {
return true
}
return false
},
})
if err != nil {
panic(err)
}
}
{{ if call .HasPermission "feature-c" }}
<div class="feature">
<h3>Feature A</h3>
<p>Some other stuff here...</p>
</div>
{{ else }}
<div class="feature disabled">
<h3>Feature A</h3>
<p>To enable Feature A please upgrade your plan</p>
</div>
{{ end }}
- 自定义函数
另外一种方式是使用template.FuncMap创建自定义的函数,它创建一个全局的函数,可以在整个应用中使用。FuncMap通过map[string]interface{}将函数名映射到函数上。注意映射的函数必须只有一个返回值,或者有两个返回值但是第二个是error类型。
package main
import (
"os"
"text/template"
)
type User struct {
ID int
Email string
HasPermission func(string) bool
}
func main() {
tmpl, err := template.New("hello.tpl").Funcs(
template.FuncMap{
"hasPermission": func(s string) bool {
if s == "a" {
return true
}
return false
},
}).ParseFiles("hello.tpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, nil)
if err != nil {
panic(err)
}
}
{{ if hasPermission "feature-a" }}
<div class="feature">
<h3>Feature A</h3>
<p>Some other stuff here...</p>
</div>
{{ else }}
<div class="feature disabled">
<h3>Feature A</h3>
<p>To enable Feature A please upgrade your plan</p>
</div>
{{ end }}
- 第三方自定义函数
除了官方的预定义的函数外,一些第三方也定义了一些函数,你可以使用这些库,避免重复造轮子。
比如sprig库,定义了很多的函数:
- String Functions: trim, wrap, randAlpha, plural, etc.
- String List Functions: splitList, sortAlpha, etc.
- Math Functions: add, max, mul, etc.
- Integer Slice Functions: until, untilStep
- Date Functions: now, date, etc.
- Defaults Functions: default, empty, coalesce, toJson, toPrettyJson, toRawJson, ternary
- Encoding Functions: b64enc, b64dec, etc.
- Lists and List Functions: list, first, uniq, etc.
- Dictionaries and Dict Functions: get, set, dict, hasKey, pluck, deepCopy, etc.
- Type Conversion Functions: atoi, int64, toString, etc.
- File Path Functions: base, dir, ext, clean, isAbs
- Flow Control Functions: fail
- Advanced Functions
- UUID Functions: uuidv4
- OS Functions: env, expandenv
- Version Comparison Functions: semver, semverCompare
- Reflection: typeOf, kindIs, typeIsLike, etc.
- Cryptographic and Security Functions: derivePassword, sha256sum, genPrivateKey, etc.