Go Template 简介

模板可以理解为事先定义好的 HTML 文档文件,模板渲染的作用机制可以简单理解为文本替换操作(使用相应的数据去替换 HTML 文档中事先准备好的标记),模板用于显示和数据分离(前后端分离),模板技术本质是模板引擎利用模板文件和数据生成 HTML 文档。

text/templatehtml/template
.tmpl.tplUTF8{{}}..FieldName{{}}

模板引擎的两个步骤:

(1)模板文件列表或者字符串进行语法分析,创建模板结构 。

(2)模板引擎结合模板结构和传入动态数据,执行生成 HTML ,传递给 ResponseWriter 接口。

template*Template
func Must(t *Template, err error) *Template
func New(name string) *Template
func ParseFS(fs fs.FS, patterns ...string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)
func (t *Template) Clone() (*Template, error)
func (t *Template) DefinedTemplates() string
func (t *Template) Delims(left, right string) *Template
func (t *Template) Execute(wr io.Writer, data any) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error
func (t *Template) Funcs(funcMap FuncMap) *Template
func (t *Template) Lookup(name string) *Template
func (t *Template) Name() string
func (t *Template) New(name string) *Template
func (t *Template) Option(opt ...string) *Template
func (t *Template) Parse(text string) (*Template, error)
func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error)
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
func (t *Template) ParseGlob(pattern string) (*Template, error)
func (t *Template) Templates() []*Template
Must()*Template

例如创建模板对象使用如下的方式:

t := template.New("abc")
tt,err := t.Parse("xxx")

这里的 ttt 都指向同一个模板对象,t 称为模板的关联名称(即创建了一个模板关联到变量 t 上),但 t 不是模板的名称,因为 Template 中有一个未导出的 name 字段才是模板的名称。

Name()

模板的结构

Template 与 common 的结构

Template
type Template struct {
        name string
        *parse.Tree
        *common
        leftDelim  string
        rightDelim string
}
{{}}
common
type common struct {
        tmpl   map[string]*Template // Map from name to defined templates.
        option option
        muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
        parseFuncs FuncMap
        execFuncs  map[string]reflect.Value
}
tmplTemplatecommoncommon
commontmplcommonparseFuncsexecFuncsparseFuncsexecFuncs

模板函数和方法

New() 函数和 init() 方法

template.New()common
func New(name string) *Template {
        t := &Template{
                name: name,
        }
        t.init()
        return t
}
t.init()commoncommon
func (t *Template) init() {
        if t.common == nil {
                c := new(common)
                c.tmpl = make(map[string]*Template)
                c.parseFuncs = make(FuncMap)
                c.execFuncs = make(map[string]reflect.Value)
                t.common = c
        }
}
template.New()commoncommonParse()ParseFiles()commontmpl
tmpl := template.New("mytmpl")
template.New()tmpltemplateinit()commoninit()commoncommoncommon
Parse()
package main

import (
	"html/template"
	"fmt"
)

func main() {
        t1 := template.New("test1")
        tmpl,_ := t1.Parse(
        `{{define "T1"}}ONE{{end}}
         {{define "T2"}}TWO{{end}}
         {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
         {{template "T3"}}
        `)
        fmt.Println(t1)
        fmt.Println(tmpl)
        fmt.Println(t1.Lookup("test1"))  	// 使用关联名称t1检索 test1 模板
        fmt.Println(t1.Lookup("T1"))
        fmt.Println(tmpl.Lookup("T2")) 		// 使用关联名称tmpl检索 T2 模板
        fmt.Println(tmpl.Lookup("T3"))
}

执行程序输出如下的结果:

&{<nil> 0xc00007c080 0xc000104120 0xc00005c180}
&{<nil> 0xc00007c080 0xc000104120 0xc00005c180}
&{<nil> 0xc00007c080 0xc000104120 0xc00005c180}
&{<nil> 0xc00007c200 0xc000104240 0xc00005c180}
&{<nil> 0xc00007c240 0xc000104360 0xc00005c180}
&{<nil> 0xc00007c280 0xc000104480 0xc00005c180}
parseTreecommoncommon

该程序执行过程说明:

template.New()commonParse()Parse()Parse()Parse()Parse()commontmplcommontmpl

注意:虽然 test1、T1、T2、T3 都关联在 t1 上,但 t1 只能代表 test1 (所以上图中只有 test1 下面标注了t1),因为 t1 是一个 Template 类型。可以认为 test1、T1、T2、T3 这 4 个模板共享一个组,但 T1、T2、T3 都是对外部不可见的,只能通过特殊方法的查询找到它们。

template*TemplateParse()

New() 方法

template.New()Template.New()
func (t *Template) New(name string) *Template {
        t.init()
        nt := &Template{
                name:       name,
                common:     t.common,
                leftDelim:  t.leftDelim,
                rightDelim: t.rightDelim,
        }
        return nt
}

对该方法进一步说明:

t.init()commoncommon
New()t.New()New()New()commonNew()common
New()Parse()commonNew()common

例如编写如下程序:

t1 := template.New("test1")
t1 = t1.Parse(...)
t2 := t1.New("test2")
t2 = t2.Parse(...)
t3 := t1.New("test3")
Parse()
package main

import (
		"html/template"
		"os"
)
func main(){
		t1 := template.New("test1")
		t2 := t1.New("test2")
		t1, _ = t1.Parse(
		`{{define "T1"}}Hi{{end}}
		{{define "T2"}}cqupthao{{end}}
		{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{template "T3"}}
		`)
		t2, _ = t2.Parse(
		`{{define "T4"}}Hello{{end}}
		{{define "T2"}}CQUPT!{{end}}
		{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
		{{template "T3"}}
		`)
        _ = t1.Execute(os.Stdout, "a")
        _ = t2.Execute(os.Stdout, "a")
}

执行程序输出如下结果:

 Hello CQUPT!
 Hello CQUPT!
commont1.Parse()t2.Parse()common
t1.Lookup("T2")t2.Lookup("T2")t2.Parse()t1.Execute()

Parse() 方法

Parse(string)Parse()New()
commontemplateinit()commonParse()commonExecute()ExecuteTemplate()Lookup()DefinedTemplates()Parse()commoncommon

ParseFiles() 方法/函数和 ParseGlob() 方法/函数

Parse()ParseFiles()ParseGlob()ParseFiles()ParseGlob()
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
func (t *Template) ParseGlob(pattern string) (*Template, error)
ParseFiles()ParseFiles()
a.cnfb.cnfc.cnf
package main

import (
		"html/template"
		"fmt"
)

func main() {
        t1,err := template.ParseFiles("a.cnf","b.cnf","c.cnf")
        if err != nil {
                panic(err)
        }
        // t1.Parse("")
        fmt.Println(t1.DefinedTemplates())
        fmt.Println()
        fmt.Println(t1)
        fmt.Println(t1.Lookup("a.cnf"))
        fmt.Println(t1.Lookup("b.cnf"))
        fmt.Println(t1.Lookup("c.cnf"))
}

执行程序输出的结果如下:

; defined templates are: "a.cnf", "b.cnf", "c.cnf"

&{<nil> 0xc00007c080 0xc000104120 0xc00005c1e0}
&{<nil> 0xc00007c080 0xc000104120 0xc00005c1e0}
&{<nil> 0xc00007c0c0 0xc000104240 0xc00005c1e0}
&{<nil> 0xc00007c100 0xc000104360 0xc00005c1e0}
a.cnftemplate.New()commont.ParseFiles()t.ParseGlob()New()Parse()common

将上面的注释行取消掉,执行程序的结果将如下:

; defined templates are: "b.cnf", "c.cnf", "a.cnf"

&{<nil> 0xc00007c080 0xc000104120 0xc00005c1e0}
&{<nil> 0xc00007c080 0xc000104120 0xc00005c1e0}
&{<nil> 0xc00007c0c0 0xc000104240 0xc00005c1e0}
&{<nil> 0xc000182000 0xc00018a000 0xc00005c1e0}
parseFiles()
func parseFiles(t *Template, filenames ...string) (*Template, error) {
        if len(filenames) == 0 {
                // Not really a problem, but be consistent.
                return nil, fmt.Errorf("template: no files named in call to ParseFiles")
        }
        for _, filename := range filenames {
                b, err := ioutil.ReadFile(filename)
                if err != nil {
                        return nil, err
                }
                s := string(b)

                // name为文件名的basename部分
                name := filepath.Base(filename)

                var tmpl *Template
                if t == nil {
                        t = New(name)
                }
                // 如果调用t.Parsefiles(),则t.Name不为空
                // name也就不等于t.Name
                // 于是新New(name)一个模板对象给tmpl
                if name == t.Name() {
                        tmpl = t
                } else {
                        tmpl = t.New(name)
                }
                // 解析tmpl。如果选中了上面的else分支,则和t无关
                _, err = tmpl.Parse(s)
                if err != nil {
                        return nil, err
                }
        }
        return t, nil
}

Execute() 方法和 ExecuteTemplate() 方法

io.Writer
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
Execute()commonExecuteTemplate()common
package main

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

func main() {
        t1 := template.New("test1")
        t1, _ = t1.Parse(
        `{{define "T1"}}ONE{{end}}
        {{- define "T2"}}TWO{{end}}
        {{- define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
        {- template "T3"}}
        `)
        _ = t1.Execute(os.Stdout,"")
        fmt.Println()
        fmt.Println("-------------")
        _ = t1.ExecuteTemplate(os.Stdout, "T2", "")
}

执行程序输出的结果如下:

ONE TWO
-------------
TWO

Lookup() 方法、DefinedTemplates() 方法和 Templates() 方法

Lookup()DefinedTemplates()Templates()
Lookup()DefinedTemplates()Templates()DefinedTemplates()[]*Template
commonLookup()nilcommonDefinedTemplates()Templates()

例如编写如下程序:

package main

import (
	"html/template"
	"fmt"
)
func main() {
        t1 := template.New("test1")
        t2 := t1.New("test2")
        t1, _ = t1.Parse(
        `{{define "T1"}}ONE{{end}}
        {{define "T2"}}TWO{{end}}
        {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
        {{template "T3"}}
        `)
        t2, _ = t2.Parse(
        `{{define "T4"}}ONE{{end}}
        {{define "T2"}}TWOO{{end}}
        {{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
        {{template "T3"}}
        `)
        fmt.Println(t1.DefinedTemplates())
        fmt.Println(t2.DefinedTemplates())
        fmt.Println(t2.Templates())
}

执行程序输出如下的结果:

; defined templates are: "T1", "T2", "T3", "test1", "T4", "test2"
; defined templates are: "test2", "T1", "T2", "T3", "test1", "T4"
[0xc0000a05a0 0xc0000a05d0 0xc0000a0840 0xc0000a0870 0xc0000a08a0 0xc0000a0b40]

从程序运行的结果可知,虽然返回的顺序虽然不一致,但包含的 template name 是完全一致的。


Clone() 方法

Clone()common
t1 := template.New("test1")
t1 = t1.Parse(...)

t2 := t1.New("test2")
t2 = t2.Parse(...)

t3, err := t1.Clone()
if err != nil {
        panic(err)
}
common{{define "Tx"}}...{{end}}
package main

import (
	"html/template"
	"fmt"
)

func main() {
        t1 := template.New("test1")
        t2 := t1.New("test2")
        t1, _ = t1.Parse(
        `{{define "T1"}}ONE{{end}}
		{{define "T2"}}TWO{{end}}
		{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{template "T3"}}
		`)
        t2, _ = t2.Parse(
        `{{define "T4"}}ONE{{end}}
		{{define "T2"}}TWOO{{end}}
		{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
		{{template "T3"}}
		`)
        t3, err := t1.Clone()
        if err != nil {
                panic(err)
        }

    	// 结果完全一致
        fmt.Println(t1.Lookup("T4"))
        fmt.Println(t3.Lookup("T4"))
    
   		// 修改 t3
        t3,_ = t3.Parse(`{{define "T4"}}one{{end}}`)
    	// 结果将不一致
        fmt.Println(t1.Lookup("T4"))
        fmt.Println(t3.Lookup("T4"))
}

执行程序输出的结果如下:

&{<nil> 0xc00007c500 0xc0001046c0 0xc00005c180}
&{<nil> 0xc00007c700 0xc000104a20 0xc00005c2a0}
&{<nil> 0xc00007c500 0xc0001046c0 0xc00005c180}
&{<nil> 0xc00007c940 0xc000105200 0xc00005c2a0}
common

Must() 函数

template
t1 := template.New("ttt")
t1,err := t1.Parse(...)
if err != nil {
    	panic(err)
}
Must()
func Must(t *Template, err error) *Template {
        if err != nil {
                panic(err)
        }
        return t
}
*Template,errMust()*Template
var t = template.Must(template.New("name").Parse("text"))

FuncMap 与 Funcs() 函数

common
commonFuncMapcommon
type common struct {
        tmpl   map[string]*Template
        option option
        muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
        parseFuncs FuncMap
        execFuncs  map[string]reflect.Value
}
FuncMap
type FuncMap map[string]interface{}

它是一个 map 结构,key 为模板中可以使用的函数名,value 为函数对象(函数),函数必须只有 1 个值或 2 个值,如果有两个值,第二个值必须是 error 类型的,当执行函数时 err 不为空,则执行自动停止。

{{str . "aaa"}}

假如定义一个将字符串转换为大写的函数,编写程序如下:

import "strings"
func upper(str string) string {
        return strings.ToUpper(str)
}

将其添加到 FuncMap 结构中并将此函数命名为 “strupper” ,以后在待解析的内容中就可以调用 “strupper” 函数,编写程序如下:

funcMap := template.FuncMap{
        "strupper": upper,
}

或者直接将匿名函数放在 FuncMap 内部:

funcMap := template.FuncMap{
        "strupper": func(str string) string { return strings.ToUpper(str) },
}
commoncommonFuncs()commoncommonFuncs()
func (t *Template) Funcs(funcMap FuncMap) *Template

具体的实现,参考如下程序代码:

funcMap := template.FuncMap{
        "strupper": func(str string) string { return strings.ToUpper(str) },
}
t1 := template.New("test")
t1 = t1.Funcs(funcMap)
commonFuncs()common

完整的程序代码如下:

package main

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

func main() {
        funcMap := template.FuncMap{
                "strupper": upper,
        }
        t1 := template.New("test1")
        tmpl, err := t1.Funcs(funcMap).Parse(`{{strupper .}}`)
        if err != nil {
                panic(err)
        }
        _ = tmpl.Execute(os.Stdout, "Welcome to CQUPT!\n")
}

func upper(str string) string {
        return strings.ToUpper(str)
}

执行程序输出的结果如下:

WELCOME TO CQUPT!
{{strupper .}}..

模板处理过程

模板处理流程(构建模板对象 New() --> 解析数据Parse() --> 应用合并 Execute() )主要是如下两个阶段 :

parseexecuteio.Writer

例如编写如下程序:

package main

import (
  	 	"os"
   		"text/template"
)
var templateStr = `Welcome to CQUPT!`

func main() {
   		// 创建 template 对象
   		t := template.New("demo")
   		// 解析模板内容
   		parse, err := t.Parse(templateStr)
   		if err != nil {
      			panic(err)
   		}
   		// 执行解析
   		data := map[string]string{"Name": "World"}
   		err = parse.Execute(os.Stdout, data)
   		if err != nil {
      			panic(err)
   		}
}

执行程序输出的结果如下:

Welcome to CQUPT!

程序执行过程分析:

template.New()
type Template struct {
   		name string
   		*parse.Tree
   		*common
   		leftDelim  string
   		rightDelim string
}
  • Parse 阶段。
*parse.Treehtml/templatecommon{{define block}}parse.Tree
Rootparse.TreeTextNodeActionNode.Nameparse.Tree
template.Templateparse.NodeListparse.Node
  • Execute 阶段。
templateExecute()

func (t *Template) Execute(wr io.Writer, data any) error {
   		return t.execute(wr, data)
}

func (t *Template) execute(wr io.Writer, data any) (err error) {
   		defer errRecover(&err)
   		// 使用反射解析传入的参数
   		value, ok := data.(reflect.Value)
   		...
   		state := &state{
      			tmpl: t,
     	 		wr:   wr,
      			vars: []variable{{"$", value}},
   		}
   		...
   		// 遍历节点
   		state.walk(value, t.Root)
   		return
}
Root
func (s *state) walk(dot reflect.Value, node parse.Node) {
   		s.at(node)
   		switch node := node.(type) {
   				case *parse.ActionNode:
      					val := s.evalPipeline(dot, node.Pipe)
      					if len(node.Pipe.Decl) == 0 {
         						s.printValue(node, val)
      					}
   				...
   				case *parse.ListNode:
       					// 循环遍历节点, 递归 
      					for _, node := range node.Nodes {
         						s.walk(dot, node)
      					}
   				...
   				case *parse.TextNode:
      					if _, err := s.wr.Write(node.Text); err != nil {
         						s.writeError(err)
      					}
   				...
		}
		...
}
ActionNode()TextNode()
ActionNode
evalPipeline()
printValue()
TextNodeWrite()io.Writer
parse.Nodeio.Writer