使用场景: 我们希望在模板里面动态解析指定的模板文件。
这个似乎使用go语言的模板嵌套 template 可以实现,不过模板嵌套声明里面是不支持使用变量的, 如:{{template "模板文件名" .}} 这里的"模板文件名"不能使用变量,原因我们另外分析, 所以只能是静态嵌套其他模板,不能实现动态嵌套其他模板!
golang动态模板解析实现思路
要实现模板文件的动态解析, 我们可以通过在自定义模板函数的方式来实现。即 这个函数接收一个 模板文件变量 和模板解析参数 然后将模板文件解析为 HTML后返回给调用模板,这样就实现了模板的动态解析。 【ps: 如果你有更好的思路,欢迎反馈分享 :)】
golang HTML动态模板解析实现代码 示例
Go
package main
import (
"bytes"
"html/template"
"path/filepath"
"github.com/spf13/viper" // 配置信息解析,可以使用其他你喜欢的
)
// 获取所有自定义模板函数
func GetFuncs() template.FuncMap {
// fmap map[string]any
return template.FuncMap{
"TplInclude": TplInclude,
}
}
// 动态模板解析函数 将指定的模板文件渲染为html内容后返回
//
// tplFile 模板文件 这个可以是相对exe所在文件夹的路径 或者绝对路径
// datas 可选参数 要应用到模板文件中的数据 通常为map格式
//
// 返回可直接渲染页面的 template.HTML 格式的字符串
// @author: TekinTian <tekinTian@gmail.com>
// @see https://dev.tekin.cn
func TplInclude(tplFile string, datas ...interface{}) template.HTML {
var data interface{} // 应用到模板中的数据,默认空
if len(datas) > 0 {
data = datas[0] // 从可选参数中获取
}
// 从配置文件中获取分隔符
delss := viper.GetStringSlice("viewer.delimiters")
if len(delss) != 2 {
delss = []string{"", ""} // 使用默认的分隔符,这里留空go最后解析的时候就会使用默认的分隔符 {{ }} 详情src/text/template/parse/lex.go
}
// 获取*template.Template对象 注意如果用到了自定义分隔符,自定义函数,则需要再这里重新注册
t := template.New(tplFile).Delims(delss[0], delss[1]).Funcs(GetFuncs())
// 解析模板文件 注意这里的模板文件路径必须是相对于exe运行文件所在的路径 或者 绝对路径
tpl, err := t.ParseFiles(filepath.Join("templates", tplFile))
if err != nil {
return template.HTML(err.Error())
}
// 新建一个字符串缓存 writer, 解析后的模板内容将写入到这里
// 这里如果是直接输出到浏览器的话用浏览器的writer即可, gin的输出writer为 c.Writer
// 我们这里不直接输出到浏览器,而是输出到模板文件中,所以用这个自定义的writer接收后转为字符串输出到模板中.
buf := bytes.NewBufferString("")
// 渲染模板
if err := tpl.Execute(buf, data); err == nil {
return template.HTML(buf.String()) // 将缓存中的所有数据以html格式放回 注意必须是template.HTML格式 否则页面不会被解析
} else {
return template.HTML(err.Error())
}
}
gin框架中使用示例
就是将我们自定义的函数通过 template.FuncMap方式加载到模板里面,特别提醒顺序,必须在r.LoadHTMLFiles函数之前,否则你的函数在模板里面生效!! 另外这里的模板加载,我们动态加载的模板在主r里面可以不用加载,只需要加载一个使用动态加载函数的模板就可以。
main.go
Go
package main
import (
"fmt"
"html/template"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 就按照我们自定义的函数
r.SetFuncMap(template.FuncMap{
"TplInclude": TplInclude,
})
// 注意这里的模板加载 ,也可以是这样 r.LoadHTMLGlob("templates/*.html")
r.LoadHTMLFiles("templates/index.html")
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.hml", map[string]interface{}{
"now": time.Now().Format(time.DateTime),
"tpl": "xcontent.html", // 这里将要动态加载的模板作为变量传递给模板,然后在模板里面调用我们自定义的函数实现动态加载
})
})
r.Run(":8080")
}
index.html示例
{{TplInclude .tpl .}} .tpl就是控制器里面传递来的变量参数, 最后一个 . 表示当前所有的变量信息。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态模板加载示例</title>
</head>
<body>
普通变量调用示例:
Now: {{ .now }} <br />
<hr>
调用我们自定义的模板函数, 传递2个参数 第一个.tpl是控制器传来的变量, 第二个.是当前的所有变量信息:<br />
{{TplInclude .tpl .}}
</body>
</html>
xcontent.html 这个就是一个你自己定义的需要动态加载的模板文件,省略了。。。
总结
动态模板解析这里的关键点就是如何将指定的模板文件解析为HTML字符串,一般我们常见的就是直接将模板文件解析后输出到浏览器, 而这里是将模板文件解析后返回HTML格式的字符串,这就需要我们使用自定义的writer来接收模板解析后的内容,然后将这些内容返回, 注意这里在返回的时候必须使用 template.HTML 类型, 否则你返回的字符串将是被转码后的html,是不会被浏览器渲染的。
附 template包中定义的返回类型参考
我们上面就用到了下面的这个 HTML类型, 他其实就是一个string类型的类型定义, 当然如果我们希望实现动态渲染css,js 等其他内容,也需要使用下面定义的对应的类型才行哦。
Go
package template
// Strings of content from a trusted source.
type (
// CSS encapsulates known safe content that matches any of:
// 1. The CSS3 stylesheet production, such as `p { color: purple }`.
// 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`.
// 3. CSS3 declaration productions, such as `color: red; margin: 2px`.
// 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`.
// See https://www.w3.org/TR/css3-syntax/#parsing and
// https://web.archive.org/web/20090211114933/http://w3.org/TR/css3-syntax#style
//
// Use of this type presents a security risk:
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
CSS string
// HTML encapsulates a known safe HTML document fragment.
// It should not be used for HTML from a third-party, or HTML with
// unclosed tags or comments. The outputs of a sound HTML sanitizer
// and a template escaped by this package are fine for use with HTML.
//
// Use of this type presents a security risk:
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
HTML string
// HTMLAttr encapsulates an HTML attribute from a trusted source,
// for example, ` dir="ltr"`.
//
// Use of this type presents a security risk:
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
HTMLAttr string
// JS encapsulates a known safe EcmaScript5 Expression, for example,
// `(x + y * z())`.
// Template authors are responsible for ensuring that typed expressions
// do not break the intended precedence and that there is no
// statement/expression ambiguity as when passing an expression like
// "{ foo: bar() }\n['foo']()", which is both a valid Expression and a
// valid Program with a very different meaning.
//
// Use of this type presents a security risk:
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
//
// Using JS to include valid but untrusted JSON is not safe.
// A safe alternative is to parse the JSON with json.Unmarshal and then
// pass the resultant object into the template, where it will be
// converted to sanitized JSON when presented in a JavaScript context.
JS string
// JSStr encapsulates a sequence of characters meant to be embedded
// between quotes in a JavaScript expression.
// The string must match a series of StringCharacters:
// StringCharacter :: SourceCharacter but not `\` or LineTerminator
// | EscapeSequence
// Note that LineContinuations are not allowed.
// JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not.
//
// Use of this type presents a security risk:
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
JSStr string
// URL encapsulates a known safe URL or URL substring (see RFC 3986).
// A URL like `javascript:checkThatFormNotEditedBeforeLeavingPage()`
// from a trusted source should go in the page, but by default dynamic
// `javascript:` URLs are filtered out since they are a frequently
// exploited injection vector.
//
// Use of this type presents a security risk:
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
URL string
// Srcset encapsulates a known safe srcset attribute
// (see https://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-img-srcset).
//
// Use of this type presents a security risk:
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
Srcset string
)