Golang template 包基本原理分析
作者:大大怪 发布时间:2024-04-25 15:18:32
template 概述
最近在做脚手架相关的内容, 研究了一下 Go 的 text/template
包, 接下来跟大家分享下 template 的基本原理.
在 Golang 的标准库中, 有两个和 template 有关的包, 一个是 html/template
, 另外一个是 text/template
, 这两个包的主要区别是 html 版本加入了很多对 js字符串 和 html标签 的处理, 下面我们主要用 text/template
作为案例说明.
解析流程
首先, 无论是使用 template.Parse()
还是 template.New()
都会创建一个 *Template
对象, 用来管理整个处理流程.
模板处理主要分为两个阶段, 第一个阶段是parse
阶段, 在这个阶段, 会把读入的数据, 不论是从文件读入还是直接从字符串读取的内容, 统一解析成节点树, 第二个阶段是 execute
阶段, 在这个阶段, 会把传进来的变量解析到节点树上, 生成最终的输出流, 然后写入到 io.Writer
中.
下边我们看一段简单的 使用template 的代码
package main
import (
"os"
"text/template"
)
var templateStr = `
package main
func main() {
fmt.Println("Hello, {{.Name}}!")
}
`
func main() {
// 创建template对象
t := template.New("demo")
// 解析模板内容
parse, err := t.Parse(templateStr)
if err != nil {
panic(err)
}
// 执行解析
data := map[string]string{"Name": "World"}
err = parse.Execute(os.Stdout, data)
if err != nil {
panic(err)
}
}
运行后可以看到输出:
package main
func main() {
fmt.Println("Hello, World!")
}
接下来我们就一起来分析下执行过程:
template.New
创建了一个template对象
// src/text/template/template.go
type Template struct {
name string
*parse.Tree
*common
leftDelim string
rightDelim string
}
Parse阶段
其中 *parse.Tree
只在 html/template
中使用, 表示解析完成的模板, *common
里包含了解析过程中所有输入的模板, 用一个 map
存储, 之所以使用map, 是因为 template 支持 {{define block}}
这种嵌套模板, 每一个模板快都会被解析成一个 Template对象, 然后放到这个map里, 供模板之间引用. 下面我们重点看下, 每个模板解析完之后生成的 parse.Tree
.
上边的代码生成的模板对象 t
包含的map只有一个元素, 元素的 Root
属性(即parse之后生成的 parse.Tree
), 会有三个节点.
其中 TextNode
直接存储了文本, ActionNode
会解析出 .Name
变量, 将文本字符串解析成为 parse.Tree
对象之后, Parse阶段就执行结束了, 接下来就是 Execute 阶段.
Execute阶段
// src/text/template/exec.go
func (t *Template) Execute(wr io.Writer, data any) error {
return t.execute(wr, data)
}
func (t *Template) execute(wr io.Writer, data any) (err error) {
defer errRecover(&err)
// 使用反射解析传入的参数
value, ok := data.(reflect.Value)
...
state := &state{
tmpl: t,
wr: wr,
vars: []variable{{"$", value}},
}
...
// 遍历节点
state.walk(value, t.Root)
return
}
Execute阶段开始的时候, 会先反射传入的 data, 用来解析模板内的变量, 接下来遍历模板的Root
也就是从节点树的根开始遍历处理每个节点, 这里使用了递归的方式.
// src/text/template/exec.go
func (s *state) walk(dot reflect.Value, node parse.Node) {
s.at(node)
switch node := node.(type) {
case *parse.ActionNode:
val := s.evalPipeline(dot, node.Pipe)
if len(node.Pipe.Decl) == 0 {
s.printValue(node, val)
}
...
case *parse.ListNode:
// 循环遍历节点, 递归
for _, node := range node.Nodes {
s.walk(dot, node)
}
...
case *parse.TextNode:
if _, err := s.wr.Write(node.Text); err != nil {
s.writeError(err)
}
...
}
我们看下 ActionNode
和 TextNode
的处理
ActionNode
ActionNode
在Parse阶段生成语法树, 在Execute阶段分为两步处理, 第一步, 是解析语法树, 把对应的变量替换成实际的值, 对应方法evalPipeline()
, 第二步是把生成的结果输出, 对应了代码printValue()
TextNode
TextNode
就很简单了, 直接把Node存储的文本原封不动的打印, 直接使用了Write()
方法写入
节点遍历完成之后, 所有的文本已经都输出到 io.Writer
中, 模板执行结束.
至此, 整个流程完成.
小结
text/template
的解析过程主要经历了两个阶段:
Parse阶段
在这个阶段, 输入内容经历了从 template.Template
到 parse.NodeList
再到 parse.Node
几个步骤, 将纯文本变成了可以统一处理的节点, 方便后续操作
Execute阶段
遍历全部的 parse.Node
, 根据不同的规则把每个node的内容处理过之后, 输出到 io.Writer
里, 完成执行
template功能用在代码生成上非常简单高效, 诸如脚手架生成基础开发模板, 还有 protobuf
等 IDL 生成代码都很方便,更多的实践应用,请关注脚本之家其它相关文章!
来源:https://juejin.cn/post/7139543557995495437


猜你喜欢
- 需求:用的是django的框架,想显示一个基本固定的页面,用到了form_layout上图的ROW中添加的是model中的字段名,可以显示对
- replace(param1,param2,param3)param1 正则表达式;param2 将匹配的字符替换成指定字符;param3
- 点击率预估模型0.前言本篇是一个基础机器学习入门篇文章,帮助我们熟悉机器学习中的神经网络结构与使用。日常中习惯于使用Python各种成熟的机
- 今天,使用各种所见即所得工具制作主页已经是一件非常容易的事情了。但是了解HTML源代码和语法,无疑对我们制作主页有更大的帮助,也可以使用户能
- 场景说明假设有一个mysql表被水平切分,分散到多个host中,每个host拥有n个切分表。 如果需要并发去访问这些表,快速得到查询结果,
- 关于浏览器的最离奇的统计结果之一就是Internet Explorer 版本6,7和8共存。截至本文,Internet Explorer各个
- 多线程多线程是个提高程序运行效率的好办法,本来要顺序执行的程序现在可以并行执行,可想而知效率要提高很多。但是多线程也不是能提高所有程序的效率
- 一、join函数(一)参数使用说明描述Python join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。语法join()
- SQL中的单记录函数 1.ASCII 返回与指定的字符对应的十进制数; SQL> select ascii('A')
- 异步操作数据的方式有两种常见的方式:XMLHttpRequest 和 iframe. 孰优孰劣在此我们不争论,只是想举一个例子说明在获取网片
- Go语言作为一门开源的编程语言,已经广泛应用于各个领域。作为一门现代化的编程语言,Go语言支持模块化开发,而包和依赖管理是模块化开发的重要组
- <% On Error Resume Next Const uploadPath = "/uploads/"
- Python中有3种内建的数据结构:列表、元组和字典。参考简明Python教程1. 列表list是处理一组有序项目的数据结构,即你可以在一个
- 主要利用了XMLHTTP的一些方法和属性来获取服务器的信息。 以下是全部源代码: &
- 1、简介这篇博客将会非常基础,如果有MySQL经验的可以跳过,写这篇博客的原因是给初学者看的。下面将会讲解如何使用select查看指定表的单
- (1)查看日期函数拓展phpinfo()<?phpphpinfo();打开上述页面之后,可以看到以下,证明已经安装日期拓展 浏览器输入
- 想要使用xpath来解析html内容, PHP自带两个对象DOMDocument,DOMXpath,其中初始化 loadHtml一般都会报很
- 背景:这个库的安装不是像其他的一样的直接使用 pip install XXX的形式,而是使用原始的Git方式1、apex这是NVIDIA开发
- 最近刚换工作不久,没太多的时间去整理工作中的东西,大部分时间都在用来熟悉新公司的业务,熟悉他们的代码框架了,最主要的是还有很多新东西要学,我
- 本文实例讲述了Python基于回溯法子集树模板解决野人与传教士问题。分享给大家供大家参考,具体如下:问题在河的左岸有N个传教士、N个野人和一