Get Started

初识模板

模板是go编程里一项重要的技术,模板是进行生成代码(类似于其他语言的宏编程),web页面后端渲染,文件批量生成与替换等应用的高效技术.

模板语法

Text and Space

  
    
      模板采用`{{Action}}`包含用户的Action,用户通过Action可以得到生成的动态数据,而编写模板时一般为了美观,会进行空格,换行,然而这样往往会导致生成的数据里也包含了不必要的数据,例如下面代码(包含了我们主要用于美观代码而使用的换行)
      

//解释 {{- -}}的用处
package main

import (
   "html/template"
   "os"
)
type Record struct {
   Author	string
   Title	string
}
func main() {
   //我们想得到Author:Title这样的输出
   //显然下面的方法输出不符合要求(包含了用于代码美观的空格)
   /*templateStr := `
      {{.Author}}:
      {{.Title}}
   `*/
   //fix it: 方法一
   //(省略多余的空格,换行,但对于复杂的代码可能会影响代码美观)
   /*
      templateStr := `{{.Author}}:{{.Title}}`
   */
   // fix it: 方法二
   // 使用了go提供的 `{{- }}`, `{{ -}}`, `{{- -}}`用于trim左右空白
   /*
      templateStr := `
         {{- .Author }}:
         {{- .Title -}}
      `
   */
   templ,err := template.New("template").Parse(templateStr)
   if err != nil {
      panic(err)
   }
   templ.Execute(os.Stdout,Record {
      Author: "Marco Epsilon",
      Title: "Learninng for Go Template",
   })
}
`{{- -}}`主要用于trim左右的空白,我们可以很方便的使用其处理用于代码美化和用户数据造成的歧义性,关于是否将Action之间的空白都当成是代码美化,用`{{- -}}`消除歧义, `{{print '\n'}}`生成真正的空白,还是综合运用,这就见仁见智了.

控制语句

通过go内置的Action我们可以在模板内进行逻辑判断,循环
// template中的 if else range with
package main

import (
	"os"
	"html/template"
)
type Author struct {
	Name			string
	Communication	[]string
	IsWoman			bool
	Address			string
}
func main() {
	//if 语句
	/*
		{{if pipeline}}
			//Your Code
		{{end}}
		{{if pipeline}}
			//Your Code
		{{else}}
			//Your Another Code
		{{end}}
		{{if pipeline}}
			//Your Code
		{{else if pipeline}}
			//Your Other Code
		{{end}}
	*/
	//range 语句
	// range语句用于遍历 map slice array
	/*
		{{range pipeline}}
			Your Code
		{{end}}
		{{range $index,$element := pipeline}}
			Your Code
		{{end}}
	*/
	//with 语句
	//with语句用于判断pipeline是否是nil
	/*
		{{with pipeline}}
			Your Code
		{{end}}
		{{with pipeline}}
			Your Code
		{{else}}
			Your Another Code
		{{end}}
	*/
	templateStr := `
		{{- "Author Name:"}} {{ .Name }}
		{{- if .IsWoman }}
		{{- print "\n" -}}
		{{- "Sex: 女"}}
		{{- else}}
		{{- print "\n" -}}
		{{- "Sex: 男"}}
		{{- end }}
		{{- range .Communication -}}
			{{- print "\n" -}}
			{{- "Contact by:"}} {{.}}
		{{- end}}
		{{- with .Address -}}
		{{- print "\n" -}}
		{{- "He offer address"}}: {{.}}
		{{- end -}}
	`
	templ,err := template.New("template").Parse(templateStr)
	if err != nil {
		panic(err)
	}
	templ.Execute(os.Stdout,Author {
		Name: "Marco Epsilon",
		Communication: []string {"Github","Email","Wechat"},
		IsWoman: false,
		//Address: "Earth",
	})
}

变量(Variables)

Go Template允许我们在其中定义变量,变量以`$varName`标识,变量可以`{{$varName = Value}}`进行赋值,变量拥有作用域,一般在控制语句`{{end}}`后或模板的结束,子模板不会继承调用点的变量,模板调用时 `$`被设置为执行时传入的参数
// example for definition of variables
package main

import (
	"os"
	"html/template"
)
type Todos struct {
	Titles	[]string
}
func main() {
	templateStr := `
		{{- range $index,$element := $.Titles -}}
			{{$index}}:{{$element}}
			{{- print "\n" -}}
		{{- end -}}
	`
	templ,err := template.New("template").Parse(templateStr)
	if err != nil {
		panic(err)
	}
	templ.Execute(os.Stdout,Todos {
		Titles: []string {
			"Get Up!",
			"Eat Breakfast",
			"Do Sports",
			"Eat Dinner",
		},
	})
}

函数(Functions)

Go Template提供了大量内置函数,当然也支持自定义函数
内置函数

格式化类

print

fmt.Sprint的别名

printf

fmt.Sprintf的别名

println

fmt.Sprintln的别名

逻辑类

eq

`eq arg1 arg2` 返回 `args2 == args2` 的布尔值

ne

`ne args1 args2` 返回 `args1 != args2` 的布尔值

gt

`gt args1 args2` 返回 `args1 > args2` 的布尔值

ge

`ge args1 args2` 返回 `args1 >= args2` 的布尔值

lt

`lt args1 args2` 返回 `args1 < args2`的布尔值

le

`le args1 args2` 返回 `args1 <= args2` 的布尔值
注意:上述双目操作函数并未像大部分编程语言一样实现短路求值,args1和args2类型和内存布局不必完全相同,比较时会按代数值比较,而不是位模式

转义类

html

html实现对参数文本中特殊的字符,如`<`,`>`等进行转义,该函数仅在使用text/template时可用,在使用html/template时使用会有一些异常

js

js Function可以对文本中特殊js字符转义,防止XSS注入攻击

urlquery

urlquery Function可以对文本中url请求参数进行转义,在使用时html/template不能使用这个函数,否则会造成异常
注意:html Function,urlquery Function在使用html/template时不可用

辅助类

index

返回第一个参数索引随后元素的值如`{{index arr 1 2 3}}`返回`arr[1] arr[2] arr[3]`,第一个参数只能是array,slice,map中的一种

len

返回参数的长度

call

调用第一个参数(必须为Function),并将随后的参数作为Function的参数,Function必须返回一个值或第二个值为error类型,如果不满足上述条件,则模板会Execute异常

and

`{{ and x y}}`行为类似于`if x then y else x`,所有参数都会被求值

not

`{{not booleanValue}}`对booleanValue进行取反

or

`{{or x y}}`行为类似于`if x then x else y`,所有参数都会被求值

自定义函数
虽然go template提供了很多内置函数,但是随着业务的复杂性提高,我们可以在template中使用自定义函数满足我们的需求,我们执行
	go doc template.FuncMap
查看输出会发现template.FuncMap只是`map[string]interface{}`的别名
	type FuncMap map[string]interface{}
所以使用自定义函数至少看起来像这样
package main

import (
	"text/template"
	"os"
)

func add(x int,y int) (int) {
	return x + y;
}
func main() {
	templateStr := `
	{{- "add 1 2 result is: " -}}
	{{- add 1 2 -}}
	`
	selfFuncs := template.FuncMap {
		"add": add,
	}
	templ,err := template.New("funcExample").Funcs(selfFuncs).Parse(templateStr)
	if err != nil {
		panic("template occur error")
	}
	templ.Execute(os.Stdout,nil)
}

模板的复用 DRY (Don’t Repeat Yourself)

模板名称
每个模板都有个名称,当调用`template.PasreFiles()`时生成的每个模板名称默认为传入的文件名,当然我们也可以通过调用`template.New()`来指定模板的名称,模板的名称可以使得我们可以唯一标识生成的模板,通过`template.ExecuteTemplate()`我们可以执行指定名称的模板
关联模板
为了重复利用代码,可以通过模板的名字,来指定调用某种模板或重复利用模板,我们可以在模板中定义内嵌模板,而在之后使用它,内嵌模板必须被定义在模板顶层
// 模板的复用 Part 1
package main

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


func main() {
	templateStr := `
		{{- define "TemplateA" -}}
			Execute TemplateA
		{{- end -}}
		{{- define "TemplateB" -}}
			Execute TemplateB
		{{- end -}}
		{{- define "TemplateMain" -}}
			Execute TemplateMain
			{{- print "\n" -}}
			{{- template "TemplateA" -}}
			{{- print "\n" -}}
			{{- template "TemplateB" -}}
		{{- end -}}
		{{template "TemplateMain" -}}
	`
	templ := template.Must(template.New("main").Parse(templateStr))
	//templ.Execute(os.Stdout,nil)
	// 观察指定不同名字模板的输出
	templ.ExecuteTemplate(os.Stdout,"main",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateMain",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateA",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateB",nil)
}
上述代码还可以用`block Action`改写
// 模板复用 Part 2
package main

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

func main() {
	templateStr := `
		{{- block "TemplateMain" . -}}
			Exectute TemplateMain
			{{- print "\n" -}}
			{{- block "TemplateA" . -}}
				Execute TemplateA
			{{- end -}}
			{{- print "\n" -}}
			{{- block "TemplateB" . -}}
				Execute TemplateB
			{{- end -}}
		{{- end -}}
	`
	templ := template.Must(template.New("main").Parse(templateStr))
	//templ.Execute(os.Stdout,nil)
	templ.ExecuteTemplate(os.Stdout,"main",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateMain",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateA",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateB",nil)
}
注意:( *template).Parse()可重复调用,模板的body不会被覆盖,内嵌模板和模板主体只有在后来调用时存在相同名称模板时相应模板才会被覆盖,对于( *template).ParseFiles()多个参数也是如此
// 模板复用 Part 3
package main

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

func main() {
	templateStr := `
		{{- block "TemplateMain" . -}}
			Exectute TemplateMain
			{{- print "\n" -}}
			{{- block "TemplateA" . -}}
				Execute TemplateA
			{{- end -}}
			{{- print "\n" -}}
			{{- block "TemplateB" . -}}
				Execute TemplateB
			{{- end -}}
		{{- end -}}
	`
	newTemplateAStr := `
		{{- define "TemplateA" -}}
			Execute new TemplateA
		{{- end -}}
	`
	newMainTemplateStr := `
		{{- define "main" -}}
			Execute New main Template
		{{- end -}}
	`
	templ := template.Must(template.New("main").Parse(templateStr))
	templ = template.Must(templ.Parse(newTemplateAStr))
	templ = template.Must(templ.Parse(newMainTemplateStr))
	//templ.Execute(os.Stdout,nil)
	templ.ExecuteTemplate(os.Stdout,"main",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateMain",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateA",nil)
	fmt.Println()
	templ.ExecuteTemplate(os.Stdout,"TemplateB",nil)
}
我不想改变这残酷的世界,只想不被这世界改变。