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")
这里的 t 和 tt 都指向同一个模板对象,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