Go语言 使用动态名称调用其他模板

kiayqfof  于 2022-12-16  发布在  Go
关注(0)|答案(6)|浏览(158)

我没有看到一种方法来调用模板(文本或html)与动态名称。例如:
这是可行的:

{{template "Blah" .}}

模板调用中出现“意外“$BlahVar”错误:

{{$BlahVar := "Blah"}}
{{template $BlahVar .}}


我试图解决的总体问题是,我需要根据配置文件有条件地呈现模板--所以我事先不知道模板的名称。显然,我可以在FuncMap中放置一个函数,它只执行单独的模板解析和调用,并返回结果,但我想知道是否有更好的方法。

xnifntxz

xnifntxz1#

作为对这一点的说明和后续行动,我最终结束了这个问题的两个主要答案:1)尽量避免这种情况。在某些情况下,简单的if语句就能很好地工作。2)我可以使用FuncMap中的一个函数来实现这一点,这个函数只进行单独的渲染。这不是世界上最好的事情,但它确实有效并解决了问题。下面是一个完整的独立演示,展示了这个想法:

package main

import (
    "bytes"
    "html/template"
    "os"
)

func main() {

    var err error

    // our main template here calls a sub template
    tpl := template.New("main")

    // provide a func in the FuncMap which can access tpl to be able to look up templates
    tpl.Funcs(map[string]interface{}{
        "CallTemplate": func(name string, data interface{}) (ret template.HTML, err error) {
            buf := bytes.NewBuffer([]byte{})
            err = tpl.ExecuteTemplate(buf, name, data)
            ret = template.HTML(buf.String())
            return
        },
    })

    // this is the main template
    _, err = tpl.Parse(`

{{$Name := "examplesubtpl"}}

from main template

{{CallTemplate $Name .}}

`)
    if err != nil {
        panic(err)
    }

    // whatever code to dynamically figure out what templates to load

    // a stub just to demonstrate
    _, err = tpl.New("examplesubtpl").Parse(`

this is from examplesubtpl - see, it worked!

`)
    if err != nil {
        panic(err)
    }

    err = tpl.Execute(os.Stdout, map[string]interface{}{})
    if err != nil {
        panic(err)
    }

}
3htmauhk

3htmauhk2#

另一种方法,虽然可能不是一个更好的方法,将有单独的模板文件,所有提供相同命名的模板。例如,假设你有一个网页的共享布局:

<html>
  ...
  <body>
    {{template "body" .}}
  </body>
</html>

在每个页面中执行以下操作:

{{define "body"}}
  This will be in the body
{{end}}

然后在代码中合并它们:

func compileTemplate(layout, name string) (*template.Template, error) {
    tpl := template.New(name)
    tpl, err := tpl.ParseFiles(
        "views/layouts/"+layout+".htm",
        "views/"+name+".htm",
    )
    if err != nil {
        return nil, err
    }
    return tpl, nil
}
k4emjkb1

k4emjkb13#

一个与我共事的天才开发人员想出了一个不同的方法,那就是对Template示例进行后处理,以找到任何未定义的模板包含,并在文件系统中查找匹配的文件,然后解析找到的每个文件;然后再渲染。
这将为您提供如下设置:
浏览次数/index.html:

{{template "/includes/page-wrapper.html" .}}

{{define "body"}}
<div>Page guts go here</div>
{{end}}

{{define "head_section"}}
<title>Title Tag</title>
{{end}}

包含/页面 Package .html:

<html>
<head>
{{block "head_section" .}}{{end}}
<head>
<body>

{{template "body" .}}

</body>
</html>

ServeHTTP()方法在“views”目录中查找文件,加载并解析它,然后调用TmplIncludeAll()(如下所示)。
最后,我将这个基本概念改编为几个函数,如下所示:t是解析后但渲染前的模板,fs是“views”和“includes”所在的目录(参见上文)。

func TmplIncludeAll(fs http.FileSystem, t *template.Template) error {

    tlist := t.Templates()
    for _, et := range tlist {
        if et != nil && et.Tree != nil && et.Tree.Root != nil {
            err := TmplIncludeNode(fs, et, et.Tree.Root)
            if err != nil {
                return err
            }
        }
    }

    return nil
}

func TmplIncludeNode(fs http.FileSystem, t *template.Template, node parse.Node) error {

    if node == nil {
        return nil
    }

    switch node := node.(type) {

    case *parse.TemplateNode:
        if node == nil {
            return nil
        }

        // if template is already defined, do nothing
        tlist := t.Templates()
        for _, et := range tlist {
            if node.Name == et.Name() {
                return nil
            }
        }

        t2 := t.New(node.Name)

        f, err := fs.Open(node.Name)
        if err != nil {
            return err
        }
        defer f.Close()

        b, err := ioutil.ReadAll(f)
        if err != nil {
            return err
        }

        _, err = t2.Parse(string(b))
        if err != nil {
            return err
        }

        // start over again, will stop recursing when there are no more templates to include
        return TmplIncludeAll(fs, t)

    case *parse.ListNode:

        if node == nil {
            return nil
        }

        for _, node := range node.Nodes {
            err := TmplIncludeNode(fs, t, node)
            if err != nil {
                return err
            }
        }

    case *parse.IfNode:
        if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
            return err
        }
        if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
            return err
        }

    case *parse.RangeNode:
        if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
            return err
        }
        if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
            return err
        }

    case *parse.WithNode:
        if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
            return err
        }
        if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
            return err
        }

    }

    return nil
}

这是我最喜欢的方法,我已经用了一段时间了。它的优点是只有一个模板渲染,错误消息很好很干净,Go语言的模板标记可读性很强。如果html/template.Template能让它更容易实现,那就太好了,但总体来说,它是一个很好的解决方案。

ghhkc1vu

ghhkc1vu4#

使用**htmltemplate.HTML()**将解析后的模板(“email/test”)插入到另一个模板(“email/main”)中

htmlTplEngine := htmltemplate.New("htmlTplEngine")

_, htmlTplEngineErr := htmlTplEngine.ParseGlob("views/email/*.html")
if nil != htmlTplEngineErr {
    log.Panic(htmlTplEngineErr.Error())
}

var contentBuffer bytes.Buffer
if err := htmlTplEngine.ExecuteTemplate(&contentBuffer, "email/test", params); err != nil {
    return "", "", errors.Wrap(err, "execute content html")
}

var templateBuf bytes.Buffer
if err := htmlTplEngine.ExecuteTemplate(
    &templateBuf,
    "email/main",
    map[string]interface{}{
        "Content": htmltemplate.HTML(contentBuffer.String()),
        "Lang":    language,
    },
); err != nil {
    return "", "", errors.Wrap(err, "execute html template")
}

在“电子邮件/主页”上

{{define "email/main"}}

My email/test template: {{.Content}}

{{end}}
jfewjypa

jfewjypa5#

面对同样的问题,而使用杜松子酒,最简单的解决方案是:

router.GET("/myurl", func(ctx *gin.Context) {
    /*
       --- do anything here --
    */
    template1 := "template1.html"
    template2 := "template2.html"

    ctx.HTML(200, template1, nil)
    ctx.HTML(200, template1, nil)
})

基本上我把html内容分割成单独的文件,然后单独调用它们。只要响应代码相同(例如:200),则不会触发任何问题。

hmtdttj4

hmtdttj46#

如果模板变量针对一组已知的备选项进行选择,那么我最终会手动或自动生成一个后缀来在模板之间进行选择。

{{define "foo [en]"}}
    English version
{{end}}

{{define "foo [cy]"}}
    Welsh version
{{end}}

{{define "foo"}}
    {{if eq .Lang "en"}}
        {{template "foo [en]"}}
    {{else if eq .Lang "cy"}}
        {{template "foo [cy]"}}
    {{else}}
        {{error "missing or invalid .Lang"}}
    {{end}}
{{end}}

我在其他地方使用的另一种方法是,在执行模板之前,解析一个动态生成的小模板,如下所示:

clone, _ := rootTemplate.Clone() // error checking elided
clone.Parse(`{{define "body"}}{{template "`+name+`" .}}{{end}}`)
clone.ExectueTemplate(...)

相关问题