LoginSignup
2
0

More than 3 years have passed since last update.

Golang入門. 5 -html/template.Executeを追う-

Posted at

はじめに

Golang入門.4 -template.ParseFilesを追う-の続きです。

今回のテーマ

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

概念

document
スクリーンショット 2020-04-18 16.59.43.png

コード

 documentによると、特定のデータをwriterに出力しているようです。また必要に応じてエラーを返しています。内部処理が重要そうなので、実際に処理を見ていきます。

html/template/template.go
// 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)
}

 別の関数を読んでいるようです。そちらを見てみます。

text/template/exec.go
// 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関数にwriterdataを渡しているようです。今回はServeHTTP関数で受け取ったhttp.ResponseWriterwriterとして渡しています。また、dataは独自に定義したPage構造体を渡しています。次にTemplate.execute関数を確認します。

text/template/exec.go
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構造体を確認します。

text/template/exec.go
// 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関数を確認します。

text/template/exec.go
// 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点です。

  1. s.at(node)によってs.nodewalk関数の引数として定義したTemplate.Rootを代入。
  2. s.nodetypeに応じて分岐。

 この時注意することが一つあります。それは、switchが複数回行われていることです。s.nodeにはtext/template/parse.Nodeの配列であるNodesが格納されています。これらには条件に応じてファイルの中身が分割して格納されています。switchにおいてはNodesから順にtext/template/parse.Nodeを取り出して実行しています。なお、今回の場合は

  • {{}}というテンプレートを用いていない場合(case *parse.TextNode)
  • テンプレートを用いた場合(case *parse.ActionNode)

でその後の動作が変わります。前者の場合はs.wr.Writeであるhttp.ResponseWriterWrite関数で中身を出力します。
 一方後者の場合は、s.evalPipelineによってevalを実行してテンプレートにかかれてある処理を実行した後のファイルの中身を取得します。その後、s.printValue関数で出力を行なっています。最後にs.printValue関数を確認します。

text/template/exec.go
// 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関数によって出力していることがわかりました。

2
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
2
0