Go Template 实践

Written by with ♥ on in Go

tempalte 包的官方定义是:Package template implements data-driven templates for generating textual output.大概意思是 template 实现数据驱动的文本输出。简单说就是我们先定义好模板,根据模板输出不同的文本。

使用

// New 创建一个新的 Template 实例,命名为 test(内部 name 字段)
tmpl, err := template.New("test").Parse("My name is {{ . }}")
tmpl.Execute(os.Stdout, "J") // 输出 My name is J

常规操作是调用 New 函数初始化 Template 实例,使用 Parse、ParseFile、ParseGlob 解析函数解析模板,然后使用 Execute 或 ExecuteTemplate 函数传入数据渲染模块。

函数

New()

// New allocates a new, undefined template with the given name.
func New(name string) *Template {
	t := &Template{
		name: name,
	}
	t.init()
	return t
}

New 函数创建 Template 类型实例,并调用 init 函数初始化 common 字段。

Must()

func Must(t *Template, err error) *Template {
	if err != nil {
		panic(err)
	}
	return t
}

工具函数,如果报错直接 panic,var t = template.Must(template.New("name").Parse("text"))

Parse 系列函数

Parsexxx 系列函数都放在源码中的 helper.go 文件中,这些函数都是帮助函数,尤其是 Template 类型方法上的 Parsexx 函数和直接调用的区别非常小。

ParseFiles()

// ParseFiles creates a new Template and parses the template definitions from
// the named files. The returned template's name will have the base name and
// parsed contents of the first file. There must be at least one file.
// If an error occurs, parsing stops and the returned *Template is nil.
//
// When parsing multiple files with the same name in different directories,
// the last one mentioned will be the one that results.
// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
// named "foo", while "a/foo" is unavailable.
func ParseFiles(filenames ...string) (*Template, error) {
   return parseFiles(nil, filenames...)
}

ParseFiles 创建一个新的 Template 实例,内部调用 parseFiles 非导出函数,

parseFiles()

// parseFiles is the helper for the method and function. If the argument
// template is nil, it is created from the first file.
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 := filepath.Base(filename)
      // First template becomes return value if not already defined,
      // and we use that one for subsequent New calls to associate
      // all the templates together. Also, if this file has the same name
      // as t, this file becomes the contents of t, so
      //  t, err := New(name).Funcs(xxx).ParseFiles(name)
      // works. Otherwise we create a new template associated with t.
      var tmpl *Template
      if t == nil {
         t = New(name)
      }
      if name == t.Name() {
         tmpl = t
      } else {
         tmpl = t.New(name)
      }
      _, err = tmpl.Parse(s)
      if err != nil {
         return nil, err
      }
   }
   return t, nil
}

parseFiles() 是抽象的辅助函数,解析一个或多个模板文件,如果第一个参数 t 为 nil,会从第一个文件创建 Template 实例 t,实例的 name 值为 filepath.Base(filename) 取出的文件名。并将后续的模板文件关联到实例 t 上。

ParseGlob()

Template 类型

// Template is the representation of a parsed template. The *parse.Tree
// field is exported only for use by html/template and should be treated
// as unexported by all other clients.
// Template 结构体代表解析模板,*parse.Tree 字段只在 html/template 包内使用,且应该被处理为非导出的
type Template struct {
	name string // 模板名
	*parse.Tree // 解析树
	*common // 内部通用结构
	leftDelim  string // 左分隔符
	rightDelim string // 右分隔符
}

下面关注 common 结构。

New()

// New allocates a new, undefined template associated with the given one and with the same
// delimiters. The association, which is transitive, allows one template to
// invoke another with a {{template}} action.
func (t *Template) New(name string) *Template {
   t.init()
   nt := &Template{
      name:       name,
      common:     t.common,
      leftDelim:  t.leftDelim,
      rightDelim: t.rightDelim,
   }
   return nt
}

初始化一个新的 Template 实例,指向同一个 common 实例,用于创建模板组中的新模板。

init()

// init guarantees that t has a valid common structure.
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
	}
}

初始化 common 字段,创建一个空的模板组。

Parse()

// Parse parses text as a template body for t.
// Named template definitions ({{define ...}} or {{block ...}} statements) in text
// define additional templates associated with t and are removed from the
// definition of t itself.
//
// Templates can be redefined in successive calls to Parse.
// A template definition with a body containing only white space and comments
// is considered empty and will not replace an existing template's body.
// This allows using Parse to add new named template definitions without
// overwriting the main template body.
func (t *Template) Parse(text string) (*Template, error) {
	t.init()
	t.muFuncs.RLock()
	trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
	t.muFuncs.RUnlock()
	if err != nil {
		return nil, err
	}
	// Add the newly parsed trees, including the one for t, into our common structure.
	for name, tree := range trees {
		if _, err := t.AddParseTree(name, tree); err != nil {
			return nil, err
		}
	}
	return t, nil
}

解析字符串参数模板,加入到 common 的 tmpl 中。

ParseFiles()

func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
   t.init()
   return parseFiles(t, filenames...)
}

与直接调用 ParseFiles 的区别是 parseFiles 的第一个参数不为 nil,解析的所有文件模板都会关联到当前 Template 实例。

ParseGlob()

与 ParseFiles 类似。

Execute()

// If data is a reflect.Value, the template applies to the concrete
// value that the reflect.Value holds, as in fmt.Print.
func (t *Template) Execute(wr io.Writer, data interface{}) error {
   return t.execute(wr, data)
}

ExecuteTemplate()

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
   var tmpl *Template
   if t.common != nil {
      tmpl = t.tmpl[name]
   }
   if tmpl == nil {
      return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
   }
   return tmpl.Execute(wr, data)
}

选出某个定义好的模板,使用 Execute 函数执行。

common

// common holds the information shared by related templates.
// common 保留在多个相关联模块中共享的信息,可以看到共享的信息有函数
type common struct {
	tmpl   map[string]*Template // Map from name to defined templates.
	option option
	// We use two maps, one for parsing and one for execution.
	// This separation makes the API cleaner since it doesn't
	// expose reflection to the client.
	muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
	parseFuncs FuncMap
	execFuncs  map[string]reflect.Value
}

tmpl 是保存多个 templates 的 map,用于保存多个相关联的模板实例,这一组模板都指向同一个 common 实例,共享相同的数据。

Action

Function

内置函数

自定义函数

内置的函数不一定完全满足我们的需求,可以添加自定义函数,自定义函数最多 2 个返回值,如果有第二个返回值必须为 error,如果 error 不为 nil,会自动停止执行。

例子

参考资料