将template.HTML直接渲染到模板中。

huangapple go评论91阅读模式
英文:

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{
		...
		&quot;unsafe&quot;: RenderUnsafe,
	}

_content.tmpl

...
&lt;div class=&quot;detail&quot;&gt;

	{{ .RenderedDesc | unsafe }}

&lt;/div&gt;
...

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  = &lt;h2&gt;Lol&lt;/h2&gt;
			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 (
&quot;html/template&quot;
&quot;os&quot;
&quot;reflect&quot;
)
var templates = template.Must(template.New(&quot;tmp&quot;).Parse(`
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Hello&lt;/h1&gt;
&lt;div class=&quot;content&quot;&gt;
Usafe Content = {{.Content}}
Safe Content  = {{.Safe}}
Bool          = {{.Bool}}
Num           = {{.Num}}
Nested.Num    = {{.Nested.Num}}
Nested.Bool   = {{.Nested.Bool}}
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
`))
func asUnsafeMap(any interface{}) map[string]interface{} {
v := reflect.ValueOf(any)
if v.Kind() != reflect.Struct {
panic(&quot;asUnsafeMap invoked with a non struct parameter&quot;)
}
m := map[string]interface{}{}
for i := 0; i &lt; v.NumField(); i++ {
value := v.Field(i)
if !value.CanInterface() {
continue
}
ftype := v.Type().Field(i)
if ftype.Tag.Get(&quot;unsafe&quot;) == &quot;html&quot; {
m[ftype.Name] = template.HTML(value.String())
} else {
m[ftype.Name] = value.Interface()
}
}
return m
}
func main() {
templates.ExecuteTemplate(os.Stdout, &quot;tmp&quot;, asUnsafeMap(struct {
Content string `unsafe:&quot;html&quot;`
Safe    string
Bool    bool
Num     int
Nested  struct {
Num  int
Bool bool
}
}{
Content: &quot;&lt;h2&gt;Lol&lt;/h2&gt;&quot;,
Safe:    &quot;&lt;h2&gt;Lol&lt;/h2&gt;&quot;,
Bool:    true,
Num:     10,
Nested: struct {
Num  int
Bool bool
}{
Num:  9,
Bool: true,
},
}))
}

Output:

&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Hello&lt;/h1&gt;
&lt;div class=&quot;content&quot;&gt;
Usafe Content = &lt;h2&gt;Lol&lt;/h2&gt;
Safe Content  = &amp;lt;h2&amp;gt;Lol&amp;lt;/h2&amp;gt;
Bool          = true
Num           = 10
Nested.Num    = 9
Nested.Bool   = true
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

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.

huangapple
  • 本文由 发表于 2014年1月29日 22:25:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/21433920.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定