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.
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 = <h2>Lol</h2>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With