3

HTML/テンプレートをラップしようとしているので、レンダリングしたいデータに加えて、テンプレートに特定のデータ (セッション データなど) が含まれていることが保証されます。しかし、私の現在のアプローチには...欠陥があります。以下に簡単な例を示します。

package main

import "fmt"
import "os"
import "html/template"

func main() {
    // Passing nil directly to Execute doesn't render anything for missing struct fields
    fmt.Print("Directly rendering nil\n")
    tmpl, err := template.New("master").Parse("Foo is: {{.Data.Foo}}")
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    err = tmpl.Execute(os.Stdout, nil)
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    // Wrapping templates works as long as I supply data...
    fmt.Print("\nRendering Foo\n")
    render(struct {
        Foo string
    }{
        "foo",
    })

    // ...but this breaks.
    fmt.Print("\nRendering nil\n")
    render(nil)
}

func render(data interface{}) {
    allData := struct {
        Session string
        Data    interface{}
    }{
        "sessionData",
        data,
    }

    // Hardcoded template for the example - this could be any arbitrary template
    tmpl, err := template.New("master").Parse("Foo is: {{.Data.Foo}}")
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    err = tmpl.Execute(os.Stdout, allData)
    if err != nil {
        fmt.Printf(err.Error())
        return
    }
}

次の出力が得られます。

Directly rendering nil
Foo is: 
Rendering Foo
Foo is: foo
Rendering nil
Foo is: template: master:1:15: executing "master" at <.Data.Foo>: nil pointer evaluating interface {}.Foo

そもそも何が起こっているのかよくわかりません.html/templateが渡されたものを処理nilできるのに、nilポインターをどうするか分からないのはなぜですか?

第二に、この問題にアプローチするより良い方法はありますか?

4

1 に答える 1

3

あなたの最善の策は、型をマップまたは構造体にするか、nil を使用しないことによって、常に Data をマップまたは構造体にすることinterface{}です。

package main

import "fmt"
import "os"
import "text/template"

func main() {
    tmpl, err := template.New("master").Parse("{{if .Data.Foo}}Foo is: {{.Data.Foo}}{{else}}Foo is empty{{end}}")
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    err = tmpl.Execute(os.Stdout, nil)
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    fmt.Println("")

    err = tmpl.Execute(os.Stdout, struct {
        Session string
        Data    map[string]string
    }{
        "sessionData",
        nil,
    })
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    fmt.Println("")

    err = tmpl.Execute(os.Stdout, struct {
        Session string
        Data    interface{}
    }{
        "sessionData",
        map[string]string{},
    })
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    fmt.Println("")
}

再生: http://play.golang.org/p/9GkAp6ysvD

なぜこのように機能するのかについては、少し複雑です。コードを確認する必要があります: https://golang.org/src/text/template/exec.go?s=4647:4717#L521

execute が nil で呼び出されるとreflect.ValueOf(nil)、無効な値が返されるため、evalField はゼロ値を返し、空の文字列になります。

ただし、execute が有効な構造体で呼び出されると、最初の Reflect.ValueOf は有効な値を返します。この.Dataコマンドは、Execute に渡した構造体全体に対して evalField を呼び出し、evalField は FieldByIndex/FieldByName を呼び出して "Data" フィールドを取得します。これは無効な値を返しません。

次に.Fooが評価されたときに、Data がインターフェイスまたはポインターの場合、間接関数はそれを最後までたどり、nil であることが判明すると、このエラーで失敗します。

Data がマップの場合、間接関数は何も実行せず、失敗しません。

これは、text/template パッケージのバグである可能性があります。

于 2016-03-11T16:25:31.483 に答える