はじめに
Golang入門.3 -html/templateを使う-の続きです。
今回のテーマ
html/template
の関数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.
コード
documentによると、Template
構造体を返しているようです。まず定義を確認します。
// Template is a specialized Template from "text/template" that produces a safe
// HTML document fragment.
type Template struct {
// Sticky error if escaping fails, or escapeOK if succeeded.
escapeErr error
// We could embed the text/template field, but it's safer not to because
// we need to keep our version of the name space and the underlying
// template's in sync.
text *template.Template
// The underlying template's parse tree, updated to be HTML-safe.
Tree *parse.Tree
*nameSpace // common to all associated templates
}
まず、text/template
のTemplate
構造体をfield
に持っています。また、HTML
のデータ構造に合わせた処理を行うために必要なものを組み合わせています。構造体についての概要を掴めたので実際に処理を見ていきます。
// 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 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 err := t.checkCanParse(); err != nil {
return nil, err
}
if len(filenames) == 0 {
// Not really a problem, but be consistent.
return nil, fmt.Errorf("html/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
}
例外処理を行なった後に、与えられたファイル一覧をイテレートしています。各ファイルについて、
- 中身をbyte形式で取得(b)
- byteからstringに変換(s)
- ファイルの名前を取得(name)
- (最初のファイルについて処理している時のみ)
Template
構造体の生成(t) -
Template
構造体の名前空間を構築(t.nameSpace)
した後に生成したTemplate
構造体を返しています。
最後に、途中で登場しているtmpl
について説明します。tmpl
は現在処理を行なっているファイル情報を格納したTemplate
構造体の変数です。
if name == t.Name() {
tmpl = t
} else {
tmpl = t.New(name)
}
という部分に書いてあるように、一番最初に作ったTemplate
構造体と同一のname
を有する場合はtmpl
とt
は同一です。しかし、2番目以降のファイルについてはt
を生成せずにtmpl
のみを生成します。最終的には全てのファイルについての情報を返す必要があります。例えば全てのファイルについての名前空間をt.nameSpace
に格納する必要があります。そのためにはtmpl.nameSpace
の情報をt.nameSpace
に共有する必要があります。共有のために必要な処理がt.New
関数に書いてあると予想し、確認します。
// New allocates a new HTML 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.
//
// If a template with the given name already exists, the new HTML template
// will replace it. The existing template will be reset and disassociated with
// t.
func (t *Template) New(name string) *Template {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
return t.new(name)
}
ここではmutex
を用いた制御をt.new
関数に付与しています。どうやら実際の処理はt.new
関数に記されているようです。
// new is the implementation of New, without the lock.
func (t *Template) new(name string) *Template {
tmpl := &Template{
nil,
t.text.New(name),
nil,
t.nameSpace,
}
if existing, ok := tmpl.set[name]; ok {
emptyTmpl := New(existing.Name())
*existing = *emptyTmpl
}
tmpl.set[name] = tmpl
return tmpl
}
tmpl.text
とtmpl.nameSpace
がt.New
およびt.nameSpace
として定義されているところがポイントです。これによりtmpl
のfield
を更新するだけで自然にt
に必要な情報が追加されます。そして、全てのファイルについてtmpl
を生成、更新することでt
に必要な情報が集約されていきます。