英文:
Rendering template.HTML directly into templates
问题
我最近更换了数据存储,并且由于兼容性问题,不得不将一个结构体字段从template.HTML
更改为string
。这个字段名为RenderedDesc
,包含经过russross/blackfriday处理的渲染后的HTML。
以前,我可以将整个结构体直接传递到模板中,并在模板中调用{{ .RenderedDesc }}
。
由于现在它是一个字符串,我添加了一个过滤器,在模板渲染时将其转换回原始HTML:
templates.go
func RenderUnsafe(s string) template.HTML {
return template.HTML(s)
}
template.FuncMap{
...
"unsafe": RenderUnsafe,
}
_content.tmpl
...
<div class="detail">
{{ .RenderedDesc | unsafe }}
</div>
...
有没有更好的方法在不使用模板级过滤器的情况下实现这一点?除非重新编写我的DB驱动的编组逻辑(不可行),否则看起来这是存储字符串但渲染原始HTML的最简单方法。
英文:
I've recently swapped out datastores and as a side-effect have had to change a struct field from template.HTML
to string
to be compatible with the marshaller/DB driver. This field, RenderedDesc
, contains rendered HTML as passed through russross/blackfriday.
Previously I could just pass the whole struct into the template "as is" and call {{ .RenderedDesc }}
in the template.
Because it's now a string, I've added a filter to convert it back on template render:
templates.go
func RenderUnsafe(s string) template.HTML {
return template.HTML(s)
}
template.FuncMap{
...
"unsafe": RenderUnsafe,
}
_content.tmpl
...
<div class="detail">
{{ .RenderedDesc | unsafe }}
</div>
...
Is there a better way to achieve this without having to use a filter at the template level? Short of re-writing marshalling logic from my DB driver (not on the cards) it looks like this is the simplest way to "store" strings but render raw HTML.
答案1
得分: 4
IMHO,做这个的正确方法是使用过滤器,就像你已经在做的那样。有更多实现相同效果的方法,其中之一是使用标签并将结构体转换为map[string]interface{}
。因为可以以与结构体相同的方式访问映射字段,所以你的模板将保持不变。
以下是代码示例(playground):
package main
import (
"html/template"
"os"
"reflect"
)
var templates = template.Must(template.New("tmp").Parse(`
<html>
<head>
</head>
<body>
<h1>Hello</h1>
<div class="content">
Usafe Content = {{.Content}}
Safe Content = {{.Safe}}
Bool = {{.Bool}}
Num = {{.Num}}
Nested.Num = {{.Nested.Num}}
Nested.Bool = {{.Nested.Bool}}
</div>
</body>
</html>
`))
func asUnsafeMap(any interface{}) map[string]interface{} {
v := reflect.ValueOf(any)
if v.Kind() != reflect.Struct {
panic("asUnsafeMap invoked with a non struct parameter")
}
m := map[string]interface{}{}
for i := 0; i < v.NumField(); i++ {
value := v.Field(i)
if !value.CanInterface() {
continue
}
ftype := v.Type().Field(i)
if ftype.Tag.Get("unsafe") == "html" {
m[ftype.Name] = template.HTML(value.String())
} else {
m[ftype.Name] = value.Interface()
}
}
return m
}
func main() {
templates.ExecuteTemplate(os.Stdout, "tmp", asUnsafeMap(struct {
Content string `unsafe:"html"`
Safe string
Bool bool
Num int
Nested struct {
Num int
Bool bool
}
}{
Content: "<h2>Lol</h2>",
Safe: "<h2>Lol</h2>",
Bool: true,
Num: 10,
Nested: struct {
Num int
Bool bool
}{
Num: 9,
Bool: true,
},
}))
}
输出结果:
<html>
<head>
</head>
<body>
<h1>Hello</h1>
<div class="content">
Usafe Content = <h2>Lol</h2>
Safe Content = <h2>Lol</h2>
Bool = true
Num = 10
Nested.Num = 9
Nested.Bool = true
</div>
</body>
</html>
注意:上述代码不能处理嵌套结构,但很容易添加对它们的支持。此外,每个标记为不安全的字段都将被视为字符串。
英文:
IMHO, the right way to do this is using a filter, like you are already doing. There are more ways to achieve the same, one of them is using tags and converting the struct in to a map[string]Interface{}
. Because map fields can be reached in the same way that structs, your templates will remain unmodified.
Show me the code (playground):
package main
import (
"html/template"
"os"
"reflect"
)
var templates = template.Must(template.New("tmp").Parse(`
<html>
<head>
</head>
<body>
<h1>Hello</h1>
<div class="content">
Usafe Content = {{.Content}}
Safe Content = {{.Safe}}
Bool = {{.Bool}}
Num = {{.Num}}
Nested.Num = {{.Nested.Num}}
Nested.Bool = {{.Nested.Bool}}
</div>
</body>
</html>
`))
func asUnsafeMap(any interface{}) map[string]interface{} {
v := reflect.ValueOf(any)
if v.Kind() != reflect.Struct {
panic("asUnsafeMap invoked with a non struct parameter")
}
m := map[string]interface{}{}
for i := 0; i < v.NumField(); i++ {
value := v.Field(i)
if !value.CanInterface() {
continue
}
ftype := v.Type().Field(i)
if ftype.Tag.Get("unsafe") == "html" {
m[ftype.Name] = template.HTML(value.String())
} else {
m[ftype.Name] = value.Interface()
}
}
return m
}
func main() {
templates.ExecuteTemplate(os.Stdout, "tmp", asUnsafeMap(struct {
Content string `unsafe:"html"`
Safe string
Bool bool
Num int
Nested struct {
Num int
Bool bool
}
}{
Content: "<h2>Lol</h2>",
Safe: "<h2>Lol</h2>",
Bool: true,
Num: 10,
Nested: struct {
Num int
Bool bool
}{
Num: 9,
Bool: true,
},
}))
}
Output:
<html>
<head>
</head>
<body>
<h1>Hello</h1>
<div class="content">
Usafe Content = <h2>Lol</h2>
Safe Content = &lt;h2&gt;Lol&lt;/h2&gt;
Bool = true
Num = 10
Nested.Num = 9
Nested.Bool = true
</div>
</body>
</html>
Note: the previous code doesn't work with nested structures, but it will be easy to add support for them. Also, every field tagged as unsafe will be treated as string.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论