什么是模板

Go中模板处理通过包: text/templatehtml/template 完成

一个模板就是一个字符串或一个文件,里面包含了一个或多个双花括号 {{}} 包含的对象

Go中模板解析

Go中模板解析主要步骤:

  1. 创建模板对象
  2. 加载模板字串(或模板文件)
  3. 执行渲染模板

Note: 其中最后一步就是将加载的字符和数据进行格式化

基本用法

  • 模板标签: 标签用 {{}} 括起来
  • 模板注释: 注释用 {{/* a comment */}} 括起来表示注释内容

变量

Go渲染 template 的时候,接收一个 interface{} 类型的变量, 我们在模板文件或模板字串中取变量值然后渲染到模板里

模板渲染时 interface{} 接收的变量通常为:

  • struct: 在模板中用struct的字段进行渲染
  • map[string][interface{}: 在模板中使用key进行渲染

Usage:

  • {{}}: 表示模板渲染时需要被替换的字段

  • {{.}}: 表示输出当前对象的值

  • {{.FieldOrMethodName}}: 表示输出struct对象的字段或方法名称为’FieldOrMethodName’的值

    • 注:要访问的对象必须可导出的
    • 当 FieldOrMethodName 为匿名字段时, 可访问其内部字段或方法, 如 “New”: {{.FieldOrMethodName.New}}
    • 当 New 是一个方法并返回一个struct对象,同样也可以访问其字段或方法: {{.FieldOrMethodName.New.Field1}}
  • {{.Method1 "参数值1" "参数值2"}}: 调用方法“Method1”,将后面的参数值依次传递给此方法,并输出其返回值。

参数

模板参数可以是 Go 中的基本数据类型,亦可是数组切片或者一个结构体

在模板中定义变量: 变量名称由字母和数字组成,并附带 $ 前缀, 用 := 赋值

  • 比如,{{$X := “ok”}} or {{$Y := “pipeline”}}

  • {{$FieldName}}: 用于输出在模板中定义的名称为 FieldName 的值,当 FieldName 本身是一个struct时,可访问其字段

条件

Go的模板支持 bool 和 string 类型的条件判断, 语法格式如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// if
{{if .condition}}
    some content
{{end}}

// if-else 
{{if .condition}}
    some content
{{else}}
    some content
{{end}}

// if-else if
{{if .condition}}
    some content
{{else if .condition}}
    some content
{{end}}

Note: 当.condition为bool类型时,值为true表示执行;当.condition为string类型时,值非空表示执行

内置的逻辑判断函数

  • not 非

    1
    2
    
    {{if not .condition}}    
    {{end}}
    • and 与
    1
    2
    
    {{if and .condition1 .condition2}} 
    {{end}}
  • or 或

    1
    2
    
    {{if or .condition1 .condition2}} 
    {{end}}
    • eq 等于
    1
    2
    
    {{if eq .var1 .var2}} 
    {{end}}
  • ne 不等于

    1
    2
    
    {{if ne .var1 .var2}} 
    {{end}}
    • lt 小于
    1
    2
    
    {{if lt .var1 .var2}} 
    {{end}}
  • le 小于等于

    1
    2
    
    {{if le .var1 .var2}} 
    {{end}}
    • gt 大于
    1
    2
    
    {{if gt .var1 .var2}} 
    {{end}}
  • ge 大于等于

    1
    2
    
    {{if ge .var1 .var2}} 
    {{end}}

    管道

    Go模板的管道函数提供和Unix的pipe一样的功能,可用于过滤简化数据等

    函数

    Go的模板提供了自定义模板函数的功能:template包创建新模板的时候通过调用 Funcs 方法将自定义函数注册到该模板中,后续通过该模板渲染的文件均支持调用这些自定义函数

    函数集合定义

    自定义模板函数的集合定义:

    1
    
    type FuncMap map[string]interface{}

Note:

  • key: 方法名,用于模板字串或文件中调用
  • value: 函数。该函数对其参数个数无限制,但对函数返回值有两种限制
    • 只返回一个值
    • 返回两个值,且第二个值必须为 error 类型

自定义和模板函数

  1. 创建一个 FuncMap类型的 map,key 为模板函数名称, value为函数定义

  2. 将 FuncMap 注册到模板中

Note:

  • FuncMap注册必须在 Parse | ParseFiles之前
  • 代码示例参考: 函数调用

函数调用

  • {{FuncName}}

    • 调用 FuncName 模板函数(等同于执行 “FuncName()” 并输出其返回值)
  • {{FuncName "Param1" "Param2"}}

    • 调用 FuncName(Param1, Param2), 并输出其返回值
    • 例如:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      
      type Para struct {
          X int
          Y int
      }
      
      var tmpl2 = `Result is {{sum .X .Y}}`
      
      func TmplTest2() {
          funcMap := template.FuncMap{"sum": add}         // 定义 FuncMap类型对象
      
          t := template.New("Call func").Funcs(funcMap)   // 注册 Funcmap到模板
          t, _ = t.Parse(tmpl2)
      
          p := Para{
              X: 1,
              Y: 2,
          }
      
          t.Execute(os.Stdout, p)
      }
      
      func add(x, y int) int {
          return x + y
      }
  • {{.FieldName | FuncName}}

    • 将竖线 “|” 左边的 “.FieldName” 变量值作为函数参数传送, 并输出其返回值。

遍历

对于数组,切片或map,可用迭代的action,与go的迭代类似,使用range:{{range ...}} ... {{end}}

遍历slice.1

遍历 slice,且获取其索引和值

1
2
3
{{range $i, $v := .slice}}
    the value is {{$i}}, {{$v}}
{{end}}

Note:

  • 在该循环中可通过 $i$v访问遍历的值
  • range…end结构内部如要使用外部的变量,比如.Var2,需要这样写:$.Var2 ((即:在外部变量名称前加符号“$”即可,单独的“$”意义等同于global))

Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Slices struct {
    Names  []string
    OutVar string
}

var tmpl3 = `
{{range $i, $v := .Names}}
    the index is {{$i}} and the value is {{$v}}
    outside variable is {{$.OutVar}} {{/* obtain outside variable */}}
{{end}}
`

func TmplTest3() {
	s := Slices{
        Names: []string{"1", "2", "3", "4", "5", "6", "7"},
        OutVar: "0"
	}

	t := template.New("range test")
	t, _ = t.Parse(tmpl3)
	t.Execute(os.Stdout, s)
}

遍历slice.2

1
2
3
{{range .slice}}
    the value is {{.}}
{{end}}

Note: 该循环没有或取其index和value,需通过 . 访问对应的值

Code:

1
2
3
4
5
6
7
8
var tmpl = `Hello {{.Username}}
{{range .Emails}}
	an email {{.}}
{{end}}
{{range .Friends}}
	my friend name is {{.Name}}
{{end}}
`

with·上下文

{{with ...}} ... {{end}}

with: 操作当前对象的值,类似上下文概念,其含义为创建一个封闭的作用域,在其范围内,可用 .action处理变量,而与外面的 . 无关,只与为with参数有关:

1
2
3
{{with arg}}
    {{/* 此时的 . 就是arg */}}
{{end}}

Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Friend struct {
	Fname string
}

type Person struct {
	UserName string
	Emails   []string
	Friends  []*Friend
}

var tmpl = `hello {{.UserName}}!
{{range .Emails}}
	an email {{.}}
{{end}}
{{with .Friends}} 
{{range .}}
	my friend name is {{.Fname}}
{{end}}
{{else}} 
    this is else {{.}}
{{end}}
`

Note: with语句也可有 else,else中的点则和 with 外面的 . 作用一样

Must操作

模板包包含一个函数: Must,其作用为检测模板是否正确(例如:大括号是否匹配,注释是否正确关闭,变量是否是正确的书写)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
	tOk := template.New("first")
	template.Must(tOk.Parse(" some static text /* and a comment */"))
	fmt.Println("The first one parsed OK.")

	template.Must(template.New("second").Parse("some static text {{ .Name }}"))
	fmt.Println("The second one parsed OK.")

	fmt.Println("The next one ought to fail.")
	tErr := template.New("check parse error with Must")
	template.Must(tErr.Parse(" some static text {{ .Name }"))
}

嵌套模板

子模板定义

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

子模板调用

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

代码测试

Code: 定义三个模板文件,header.tmpl、content.tmpl、footer.tmpl测试嵌套模板

1
2
3
4
5
6
7
8
// header.tmpl
{{define "header"}}
<html>
<head>
    <title> 演示信息 </title>
</head>
<body>
{{end}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// content.tmpl
{{define "content"}}
{{template "header"}}
<h1> 演示嵌套 </h1>
<u1>
    <li> 嵌套使用 define 定义子模板</li>
    <li> 调用使用 template "footer" </li>
</u1>
{{template "footer"}}
{{end}
1
2
3
4
5
// footer.tmpl
{{define "footer"}}
</body>
</html>
{{end}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Embedded template
func EmbeddedTmpl() {
	path := "/home/work/code/Go_Path/src/instance.golang.com/tmpl/tmpl"
	t, err := template.ParseFiles(path+"/header.tmpl", path+"/content.tmpl", path+"/footer.tmpl")
	if err != nil {
		logrus.Error(err)
	}

	t.ExecuteTemplate(os.Stdout, "header", nil)  // 渲染指定模板 header
	t.ExecuteTemplate(os.Stdout, "content", nil) // 渲染指定模板 content
	t.ExecuteTemplate(os.Stdout, "footer", nil)  // 渲染指定模板 footer
}

Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html>
<head>
    <title> 演示信息 </title>
</head>
<body>


<html>
<head>
    <title> 演示信息 </title>
</head>
<body>

<h1> 演示嵌套 </h1>
<u1>
    <li> 嵌套使用 define 定义子模板</li>
    <li> 调用使用 template "footer" </li>
</u1>

</body>
</html>



</body>
</html>

Note:

  • template.ParseFiles 可把所有嵌套模板解析到我们的模板对象
  • 每一个 {{define “template_name”}} 定义的文件都是一个独立的模板文件,与其他模板文件并行存在; 解析后的模板文件在内部存储类似一种map结构,即key-表示模板名称,value-模板内容
  • ExecuteTemplate()用来渲染定义好的子模板文件

子模板访问父模板变量

使用 {{template "content" .}} 可将当前变量传送给子模板

模板相关函数详解

Parse & ParseFiles & ParseBlob

函数定义:

1
2
3
4
5
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
func (t *Template) Parse(text string) (*Template, error)
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
func (t *Template) ParseGlob(pattern string) (*Template, error)

Note:

  • Parse 用来解析一个字符串,字符串代表模版的内容,并且嵌套的模版也会和这个模版进行关联。
  • ParseFiles 用来解析一组命名的文件模版,当模版定义在不同的文件中的时候,使用这个方法可以产生一个可执行的模版,模版执行的时候不会出错。
  • ParseGlob 与ParseFiles类似,它使用filepath.Glob模式匹配的方式遍历文件,将这些文件生成模版
  • 两个函数还可以作为 *Template的方法使用。作为函数使用的时候,它返回模版的名字是第一个文件的名字,模版以第一个文件作为base模版。同时后面的文件也会生成模版作为这个模版的关联模板,你可以通过Lookup方法查找到这个模版,因为每个模版都保存着它的关联模版:

代码示例

Execute & ExecuteTemplate

函数定义

1
2
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

Note:

  • Execute 直接接收data的值渲染模板
  • 使用 ExecuteTemplate 可以选择渲染 t 关联的模版作为渲染的主模版; name-指定主模版

See Also

Thanks to the authors 🙂