はじめに
Golang入門.4 -template.ParseFilesを追う-の続きです。
今回のテーマ
html/template
の関数Execute
の実装を追います。
概念
コード
documentによると、特定のデータをwriter
に出力しているようです。また必要に応じてエラーを返しています。内部処理が重要そうなので、実際に処理を見ていきます。
// Execute applies a parsed template to the specified data object,
// writing the output to wr.
// If an error occurs executing the template or writing its output,
// execution stops, but partial results may already have been written to
// the output writer.
// A template may be executed safely in parallel, although if parallel
// executions share a Writer the output may be interleaved.
func (t *Template) Execute(wr io.Writer, data interface{}) error {
if err := t.escape(); err != nil {
return err
}
return t.text.Execute(wr, data)
}
別の関数を読んでいるようです。そちらを見てみます。
// Execute applies a parsed template to the specified data object,
// and writes the output to wr.
// If an error occurs executing the template or writing its output,
// execution stops, but partial results may already have been written to
// the output writer.
// A template may be executed safely in parallel, although if parallel
// executions share a Writer the output may be interleaved.
//
// 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)
}
どうやらTemplate
構造体のexecute
関数にwriter
とdata
を渡しているようです。今回はServeHTTP
関数で受け取ったhttp.ResponseWriter
をwriter
として渡しています。また、data
は独自に定義したPage
構造体を渡しています。次にTemplate.execute
関数を確認します。
func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
defer errRecover(&err)
value, ok := data.(reflect.Value)
if !ok {
value = reflect.ValueOf(data)
}
state := &state{
tmpl: t,
wr: wr,
vars: []variable{{"$", value}},
}
if t.Tree == nil || t.Root == nil {
state.errorf("%q is an incomplete or empty template", t.Name())
}
state.walk(value, t.Root)
return
}
state.walk
関数で実際の処理を行なっているようです。先にstate
構造体を確認します。
// state represents the state of an execution. It's not part of the
// template so that multiple executions of the same template
// can execute in parallel.
type state struct {
tmpl *Template
wr io.Writer
node parse.Node // current node, for errors
vars []variable // push-down stack of variable values.
depth int // the height of the stack of executing templates.
}
次に、state.walk
関数を確認します。
// 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)
}
}
長く書かれていますが、今回関係あるのは以下の2点です。
-
s.at(node)
によってs.node
にwalk
関数の引数として定義したTemplate.Root
を代入。 -
s.node
のtype
に応じて分岐。
この時注意することが一つあります。それは、switch
が複数回行われていることです。s.node
にはtext/template/parse.Node
の配列であるNodes
が格納されています。これらには条件に応じてファイルの中身が分割して格納されています。switch
においてはNodes
から順にtext/template/parse.Node
を取り出して実行しています。なお、今回の場合は
- {{}}というテンプレートを用いていない場合(
case *parse.TextNode
) - テンプレートを用いた場合(
case *parse.ActionNode
)
でその後の動作が変わります。前者の場合はs.wr.Write
であるhttp.ResponseWriter
のWrite
関数で中身を出力します。
一方後者の場合は、s.evalPipeline
によってeval
を実行してテンプレートにかかれてある処理を実行した後のファイルの中身を取得します。その後、s.printValue
関数で出力を行なっています。最後にs.printValue
関数を確認します。
// printValue writes the textual representation of the value to the output of
// the template.
func (s *state) printValue(n parse.Node, v reflect.Value) {
s.at(n)
iface, ok := printableValue(v)
if !ok {
s.errorf("can't print %s of type %s", n, v.Type())
}
_, err := fmt.Fprint(s.wr, iface)
if err != nil {
s.writeError(err)
}
}
fmt.Fprintf
関数にs.wr (http.ResponseWriter)
を渡しています。以前確認したように、fmt.Fprintf
関数では最終的に引数のhttp.Response.Write
関数を呼び出しています。
終わりに
以前からテンプレートによるhtmlファイルの生成がどのような仕組みで実行されているのか気になっていました。今回、eval
関数と組み合わせることで実現していることがわかりました。また、html/template
においても出力にはhttp.ResponseWriter.Write
関数によって出力していることがわかりました。