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
)
相关推荐
活宝小娜42 分钟前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点1 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow1 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o1 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
----云烟----1 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024061 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic2 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā2 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
向宇it2 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康2 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud