template源碼分析
golang 的模版使用分兩步
1,模版解析
tpl, err := template.Parse(filename)得到文件名為名字的模板,并保存在tpl變量中
template.ParseFiles(filenames)可以解析一組模板
template.ParseGlob(pattern)會(huì)根據(jù)pattern解析所有匹配的模板并保存
2,數(shù)據(jù)綁定
tpl.Execute(io.Writer, data)去執(zhí)行, 模板渲染后的內(nèi)容寫入到io.Writer中。Data是傳給模板的動(dòng)態(tài)數(shù)據(jù)。
tpl.ExecuteTemplate(io.Writer, name, data)和上面的簡(jiǎn)單模板類似,只不過(guò)傳入了一個(gè)模板的名字,指定要渲染的模板(因?yàn)閠pl可以包含多個(gè)模板
嵌套模板定義
嵌套模板定義如下:
{ {define "footer"}}<footer><p>Here is the footer</p></footer>{ {end}}
其他模板中使用:
{ {template "footer"}}模版的數(shù)據(jù)綁定也有兩種方式
1,模版變量
通過(guò).獲取變量
通過(guò){ { .FieldName }}來(lái)訪問它的字段
鏈?zhǔn)皆L問 { { .Struct.StructTwo.Field }}
2,函數(shù)變量
2.1,調(diào)用結(jié)構(gòu)體的方法
{ {if .User.HasPermission "feature-a"}}2.2 調(diào)用結(jié)構(gòu)體的函數(shù)變量(屬性是一個(gè)函數(shù))
type User struct {ID intEmail stringHasPermission func(string) bool}{ {if (call .User.HasPermission "feature-b")}}
2.3使用template.FuncMap創(chuàng)建自定義的函數(shù)
FuncMap通過(guò)map[string]interface{}將函數(shù)名映射到函數(shù)上。注意映射的函數(shù)必須只有一個(gè)返回值,或者有兩個(gè)返回值但是第二個(gè)是error類型。
testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{"hasPermission": func(user User, feature string) bool {if user.ID == 1 && feature == "feature-a" {return true}return false},}).ParseFiles("hello.gohtml")
{ { if hasPermission .User "feature-a" }}2.4第三方自定義函數(shù)
比如sprig庫(kù)
由于函數(shù)變量的方式綁定數(shù)據(jù)可以使用閉包,所以數(shù)據(jù)的綁定時(shí)機(jī)有兩個(gè)
1,使用閉包
template.New("hello.gohtml").Funcs(template.FuncMap2,Execute數(shù)據(jù)綁定
tpl.Execute(io.Writer, data)常用的兩個(gè)模版包
"text/template"template包實(shí)現(xiàn)了數(shù)據(jù)驅(qū)動(dòng)的用于生成文本輸出的模板。其實(shí)簡(jiǎn)單來(lái)說(shuō)就是將一組文本嵌入另一組文本模版中,返回一個(gè)你期望的文本
"html/template"生成HTML格式的輸出,該包提供了相同的接口,但會(huì)自動(dòng)將輸出轉(zhuǎn)化為安全的HTML格式輸出,可以抵抗一些網(wǎng)絡(luò)攻擊。 內(nèi)部其實(shí)是用了"text/template"
看下這兩個(gè)包的源碼目錄
text/template
cd /usr/local/go/src/text/templatetree.|____option.go|____examplefunc_test.go|____testdata| |____file1.tmpl| |____tmpl1.tmpl| |____tmpl2.tmpl| |____file2.tmpl|____examplefiles_test.go|____example_test.go|____exec_test.go|____link_test.go|____multi_test.go|____parse| |____lex_test.go| |____lex.go| |____parse_test.go| |____node.go| |____parse.go|____exec.go|____template.go|____helper.go|____doc.go|____funcs.go
html/template
cd /usr/local/go/src/html/templatetree.|____testdata| |____file1.tmpl| |____tmpl1.tmpl| |____fs.zip| |____tmpl2.tmpl| |____file2.tmpl|____clone_test.go|____error.go|____examplefiles_test.go|____example_test.go|____content_test.go|____escape.go|____exec_test.go|____transition_test.go|____multi_test.go|____js_test.go|____element_string.go|____urlpart_string.go|____transition.go|____css_test.go|____template_test.go|____html.go|____state_string.go|____js.go|____html_test.go|____delim_string.go|____template.go|____doc.go|____context.go|____attr_string.go|____content.go|____css.go|____url.go|____escape_test.go|____attr.go|____url_test.go|____jsctx_string.go
下面簡(jiǎn)單介紹下text/template
它的工作流程大概分為下面幾步,
通過(guò)lex 進(jìn)行詞法分析=>parse解析語(yǔ)法樹=>execute渲染生成目標(biāo)文件
首先看Template結(jié)構(gòu):
type Template struct {name string*parse.Tree*commonleftDelim stringrightDelim string}
name是這個(gè)Template的名稱,Tree是解析樹,common是另一個(gè)結(jié)構(gòu),leftDelim和rightDelim是左右兩邊的分隔符,默認(rèn)為{{和}}。
type common struct {tmpl map[string]*Template // Map from name to defined templates.option optionmuFuncs sync.RWMutex // protects parseFuncs and execFuncsparseFuncs FuncMapexecFuncs map[string]reflect.Value}
這個(gè)結(jié)構(gòu)的第一個(gè)字段tmpl是一個(gè)Template的map結(jié)構(gòu),key為template的name,value為Template。也就是說(shuō),一個(gè)common結(jié)構(gòu)
中可以包含多個(gè)Template,而Template結(jié)構(gòu)中又指向了一個(gè)common結(jié)構(gòu)。所以,common是一個(gè)模板組,在這個(gè)模板組中的(tmpl字段)所有Template都共享一個(gè)common(模板組),模板組中包含parseFuncs和execFuncs。
使用template.New()函數(shù)可以創(chuàng)建一個(gè)空的、無(wú)解析數(shù)據(jù)的模板,同時(shí)還會(huì)創(chuàng)建一個(gè)common,也就是模板組。
func New(name string) *Template {t := &Template{name: name,}t.init()return t}
其中t為模板的關(guān)聯(lián)名稱,name為模板的名稱,t.init()表示如果模板對(duì)象t還沒有common結(jié)構(gòu),就構(gòu)造一個(gè)新的common組:
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}}
新創(chuàng)建的common是空的,只有進(jìn)行模板解析(Parse(),ParseFiles()等操作)之后,才會(huì)將模板添加到common的tmpl字段(map結(jié)構(gòu))中。
在執(zhí)行了Parse()方法后,將會(huì)把模板name添加到common tmpl字段的map結(jié)構(gòu)中,其中模板name為map的key,模板為map的value。
除了template.New()函數(shù),還有一個(gè)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}
通過(guò)調(diào)用t.New()方法,可以創(chuàng)建一個(gè)新的名為name的模板對(duì)象,并將此對(duì)象加入到t模板組中。
Execute()和ExecuteTemplate()
這兩個(gè)方法都可以用來(lái)應(yīng)用已經(jīng)解析好的模板,應(yīng)用表示對(duì)需要評(píng)估的數(shù)據(jù)進(jìn)行操作,并和無(wú)需評(píng)估數(shù)據(jù)進(jìn)行合并,然后輸出>到io.Writer中:
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
兩者的區(qū)別在于Execute()是應(yīng)用整個(gè)common中已定義的模板對(duì)象,而ExecuteTemplate()可以選擇common中某個(gè)已定義的模板進(jìn)行應(yīng)用。
FuncMap和Funcs()
template內(nèi)置了一系列函數(shù),但這些函數(shù)畢竟有限,可能無(wú)法滿足特殊的需求。template允許我們定義自己的函數(shù),添加到common中,然后就可以在待解析的內(nèi)容中像使用內(nèi)置函數(shù)一樣使用自定義的函數(shù)。
下面看看模版解析的 過(guò)程
parse.Parse 函數(shù)把文本解析成map[string]*parse.Tree的樹map對(duì)象,然后把它append到當(dāng)前模板的t.temp中
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {defer t.recover(&err)t.ParseName = t.Namet.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim), treeSet)t.text = textt.parse()t.add()t.stopParse()return t, nil}
// lex creates a new scanner for the input string.func lex(name, input, left, right string) *lexer {if left == "" {left = leftDelim}if right == "" {right = rightDelim}l := &lexer{name: name,input: input,leftDelim: left,rightDelim: right,items: make(chan item),line: 1,}go l.run()return l}
// run runs the state machine for the lexer.func (l *lexer) run() {for state := lexText; state != nil; {state = state(l)}close(l.items)}
// lexText scans until an opening action delimiter, "{{".func lexText(l *lexer) stateFn {l.width = 0if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 {ldn := Pos(len(l.leftDelim))l.pos += Pos(x)trimLength := Pos(0)if strings.HasPrefix(l.input[l.pos+ldn:], leftTrimMarker) {trimLength = rightTrimLength(l.input[l.start:l.pos])}l.pos -= trimLengthif l.pos > l.start {l.emit(itemText)}l.pos += trimLengthl.ignore()return lexLeftDelim} else {l.pos = Pos(len(l.input))}// Correctly reached EOF.if l.pos > l.start {l.emit(itemText)}l.emit(itemEOF)return nil}
下面看下數(shù)據(jù)綁定的過(guò)程,其實(shí)就是遍歷語(yǔ)法樹
// Walk functions step through the major pieces of the template structure,// generating output as they go.func (s *state) walk(dot reflect.Value, node parse.Node) {s.at(node)switch node := node.(type) {case *parse.ActionNode:// Do not pop variables so they persist until next end.// Also, if the action declares variables, don't print the result.val := s.evalPipeline(dot, node.Pipe)if len(node.Pipe.Decl) == 0 {s.printValue(node, val)}case *parse.IfNode:s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)case *parse.ListNode:for _, node := range node.Nodes {s.walk(dot, node)}case *parse.RangeNode:s.walkRange(dot, node)case *parse.TemplateNode:s.walkTemplate(dot, node)case *parse.TextNode:if _, err := s.wr.Write(node.Text); err != nil {s.writeError(err)}case *parse.WithNode:s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)default:s.errorf("unknown node: %s", node)}}
推薦閱讀
