0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Golang入門.4 -template.ParseFilesを追う-

Last updated at Posted at 2020-04-17

はじめに

Golang入門.3 -html/templateを使う-の続きです。

今回のテーマ

 html/templateの関数ParseFilesの実装を追います。

概念

document

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/templateTemplate構造体を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
}

 例外処理を行なった後に、与えられたファイル一覧をイテレートしています。各ファイルについて、

  1. 中身をbyte形式で取得(b)
  2. byteからstringに変換(s)
  3. ファイルの名前を取得(name)
  4. (最初のファイルについて処理している時のみ)Template構造体の生成(t)
  5. Template構造体の名前空間を構築(t.nameSpace)

した後に生成したTemplate構造体を返しています。
 最後に、途中で登場しているtmplについて説明します。tmplは現在処理を行なっているファイル情報を格納したTemplate構造体の変数です。

		if name == t.Name() {
			tmpl = t
		} else {
			tmpl = t.New(name)
		}

という部分に書いてあるように、一番最初に作ったTemplate構造体と同一のnameを有する場合はtmpltは同一です。しかし、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.texttmpl.nameSpacet.Newおよびt.nameSpaceとして定義されているところがポイントです。これによりtmplfieldを更新するだけで自然にtに必要な情報が追加されます。そして、全てのファイルについてtmplを生成、更新することでtに必要な情報が集約されていきます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?