golang template HTML动态模板解析实现

使用场景: 我们希望在模板里面动态解析指定的模板文件。

这个似乎使用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
)
相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
逐·風3 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
Devil枫3 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
尚梦4 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子4 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet