text/template 包详解
概述
text/template 包实现了用于生成文本输出的数据驱动模板。要生成 HTML 输出,请参见 html/template 包,它与本包具有相同的接口,但会自动保护 HTML 输出免受某些攻击。
主要用途:
- 生成动态文本内容
- 数据驱动的模板渲染
- 配置文件生成
- 代码生成
- 报告生成
核心概念:
- 模板(Template):解析后的模板表示
- 动作(Action):数据评估或控制结构,由
{{和}}分隔 - 管道(Pipeline):可能链接的命令序列
- 变量(Variable):以
$开头的标识符 - 点(Dot):当前执行位置的值,用
.表示
安全模型:
- 假设模板作者是可信的
- 包不自动转义输出
- 在模板中注入代码可能导致任意代码执行
包导入
import "text/template"
函数详解(按 A-Z 分层归类)
H
HTMLEscape
func HTMLEscape(w io.Writer, b []byte)
作用:将纯文本数据 b 的转义 HTML 等效形式写入 w
参数说明:
w:输出写入器b:要转义的字节切片
示例:
var buf bytes.Buffer
template.HTMLEscape(&buf, []byte("<script>alert('XSS')</script>"))
fmt.Println(buf.String())
// 输出:<script>alert('XSS')</script>
HTMLEscapeString
func HTMLEscapeString(s string) string
作用:返回纯文本数据 s 的转义 HTML 等效形式
参数说明:
s:要转义的字符串
返回值:
- 转义后的字符串
示例:
escaped := template.HTMLEscapeString("<script>alert('XSS')</script>")
fmt.Println(escaped)
// 输出:<script>alert('XSS')</script>
HTMLEscaper
func HTMLEscaper(args ...any) string
作用:返回参数的文本表示的转义 HTML 等效形式
参数说明:
args:可变参数列表
返回值:
- 转义后的字符串
示例:
escaped := template.HTMLEscaper("<html>", "&", "special")
fmt.Println(escaped)
// 输出:<html> & special
I
IsTrue
func IsTrue(val any) (truth, ok bool)
作用:报告值是否为“true“(即不是其类型的零值),以及值是否有意义的真值
参数说明:
val:要检查的值
返回值:
truth:值是否为真ok:值是否有意义的真值
说明:
- 这是 if 和其他动作使用的真值定义
- 空值为:false、0、任何 nil 指针或接口值、长度为零的数组、切片、映射或字符串
示例:
truth, ok := template.IsTrue(42)
fmt.Printf("truth=%v, ok=%v\n", truth, ok)
// 输出:truth=true, ok=true
truth, ok = template.IsTrue(0)
fmt.Printf("truth=%v, ok=%v\n", truth, ok)
// 输出:truth=false, ok=true
truth, ok = template.IsTrue(nil)
fmt.Printf("truth=%v, ok=%v\n", truth, ok)
// 输出:truth=false, ok=false
J
JSEscape
func JSEscape(w io.Writer, b []byte)
作用:将纯文本数据 b 的转义 JavaScript 等效形式写入 w
参数说明:
w:输出写入器b:要转义的字节切片
示例:
var buf bytes.Buffer
template.JSEscape(&buf, []byte("alert('XSS')"))
fmt.Println(buf.String())
// 输出:\u0061lert\u0028\u0027XSS\u0027\u0029
JSEscapeString
func JSEscapeString(s string) string
作用:返回纯文本数据 s 的转义 JavaScript 等效形式
参数说明:
s:要转义的字符串
返回值:
- 转义后的字符串
示例:
escaped := template.JSEscapeString("alert('XSS')")
fmt.Println(escaped)
// 输出:\u0061lert\u0027XSS\u0027
JSEscaper
func JSEscaper(args ...any) string
作用:返回参数的文本表示的转义 JavaScript 等效形式
参数说明:
args:可变参数列表
返回值:
- 转义后的字符串
示例:
escaped := template.JSEscaper("alert", "(", "'XSS'", ")")
fmt.Println(escaped)
U
URLQueryEscaper
func URLQueryEscaper(args ...any) string
作用:返回参数的文本表示的转义值,形式适合嵌入 URL 查询
参数说明:
args:可变参数列表
返回值:
- 转义后的字符串
示例:
escaped := template.URLQueryEscaper("name", "=", "John&Doe")
fmt.Println(escaped)
// 输出:name%3DJohn%26Doe
类型详解(按 A-Z 分层归类)
E
ExecError
type ExecError struct {
Name string // 模板名称
Err error // 底层错误
}
作用:当 Execute 在评估模板时出错时返回的自定义错误类型
方法:
Error
func (e ExecError) Error() string
实现 error 接口
Unwrap
func (e ExecError) Unwrap() error
返回底层错误,支持 errors.As 和 errors.Is
示例:
tmpl, err := template.New("test").Parse("{{.Field}}")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, nil)
if err != nil {
var execErr template.ExecError
if errors.As(err, &execErr) {
fmt.Printf("Template %s error: %v\n", execErr.Name, execErr.Err)
}
}
F
FuncMap
type FuncMap map[string]any
作用:定义从名称到函数的映射的类型
要求:
- 每个函数必须有单个返回值,或两个返回值(第二个是 error 类型)
- 如果第二个(error)返回值在评估期间为非 nil,执行终止并返回该错误
- 函数参数必须可分配给函数的参数类型
- 可以使用
interface{}或reflect.Value类型接受任意类型的参数
示例:
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"repeat": func(s string, n int) string {
return strings.Repeat(s, n)
},
"add": func(a, b int) int {
return a + b
},
}
tmpl, err := template.New("test").Funcs(funcMap).Parse(`
{{"hello" | upper}}
{{repeat "ab" 3}}
{{add 10 20}}
`)
T
Template
type Template struct {
// 包含导出或未导出的字段
}
作用:解析后的模板的表示
说明:
*parse.Tree字段仅导出供html/template使用- 其他客户端应将其视为未导出
Template 方法详解(按 A-Z 分层归类)
A
AddParseTree
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)
作用:将参数解析树与模板 t 关联,并指定名称
参数说明:
name:模板名称tree:解析树
返回值:
- 关联的模板
- 错误(如果有)
说明:
- 如果模板未定义,此树成为其定义
- 如果已定义且有该名称,则替换现有定义
- 否则创建、定义并返回新模板
示例:
tree, err := parse.Parse("template1", `{{define "T1"}}Content{{end}}`)
if err != nil {
panic(err)
}
tmpl := template.New("main")
result, err := tmpl.AddParseTree("T1", tree.Tree)
if err != nil {
panic(err)
}
C
Clone
func (t *Template) Clone() (*Template, error)
作用:返回模板的副本,包括所有关联的模板
返回值:
- 克隆的模板
- 错误(如果有)
说明:
- 实际表示不被复制,但关联模板的命名空间被复制
- 对副本的进一步 Parse 调用会将模板添加到副本而非原始模板
- 可用于准备通用模板,然后通过添加变体定义用于其他模板
示例:
base, err := template.New("base").Parse(`{{define "header"}}Header{{end}}`)
if err != nil {
panic(err)
}
// 克隆基础模板
variant, err := base.Clone()
if err != nil {
panic(err)
}
// 修改克隆的模板
variant.New("header").Parse(`{{define "header"}}Custom Header{{end}}`)
D
DefinedTemplates
func (t *Template) DefinedTemplates() string
作用:返回定义模板的列表字符串
返回值:
- 以 “; defined templates are: “ 为前缀的字符串
- 如果没有定义模板,返回空字符串
示例:
tmpl, err := template.New("main").Parse(`
{{define "T1"}}Template 1{{end}}
{{define "T2"}}Template 2{{end}}
`)
if err != nil {
panic(err)
}
fmt.Println(tmpl.DefinedTemplates())
// 输出:; defined templates are: T1 T2
De
Delims
func (t *Template) Delims(left, right string) *Template
作用:设置动作用分隔符,用于后续的 Parse、ParseFiles 或 ParseGlob 调用
参数说明:
left:左分隔符(空字符串表示默认的{{)right:右分隔符(空字符串表示默认的}})
返回值:
- 模板本身(支持链式调用)
示例:
tmpl, err := template.New("test").Delims("<%", "%>").Parse(`
<%if .Condition%>
Condition is true
<%end%>
`)
if err != nil {
panic(err)
}
E
Execute
func (t *Template) Execute(wr io.Writer, data any) error
作用:将解析后的模板应用到指定的数据对象,并将输出写入 wr
参数说明:
wr:输出写入器data:数据对象
返回值:
- 执行错误(如果有)
说明:
- 如果执行模板或写入输出时发生错误,执行停止
- 部分结果可能已写入输出
- 模板可以安全地并行执行
- 如果并行执行共享 Writer,输出可能会交错
- 如果 data 是 reflect.Value,模板应用于 reflect.Value 持有的具体值
示例:
type Person struct {
Name string
Age int
}
tmpl, err := template.New("test").Parse("Hello, {{.Name}}! You are {{.Age}} years old.")
if err != nil {
panic(err)
}
p := Person{Name: "Alice", Age: 30}
err = tmpl.Execute(os.Stdout, p)
if err != nil {
panic(err)
}
// 输出:Hello, Alice! You are 30 years old.
ExecuteTemplate
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error
作用:将 t 关联的具有指定名称的模板应用到指定的数据对象
参数说明:
wr:输出写入器name:模板名称data:数据对象
返回值:
- 执行错误(如果有)
示例:
tmpl, err := template.New("main").Parse(`
{{define "greeting"}}Hello, {{.Name}}{{end}}
{{define "farewell"}}Goodbye, {{.Name}}{{end}}
`)
if err != nil {
panic(err)
}
data := struct{ Name string }{Name: "Bob"}
tmpl.ExecuteTemplate(os.Stdout, "greeting", data)
// 输出:Hello, Bob
tmpl.ExecuteTemplate(os.Stdout, "farewell", data)
// 输出:Goodbye, Bob
F
Funcs
func (t *Template) Funcs(funcMap FuncMap) *Template
作用:将参数映射的元素添加到模板的函数映射中
参数说明:
funcMap:函数映射
返回值:
- 模板本身(支持链式调用)
说明:
- 必须在解析模板之前调用
- 如果映射中的值不是具有适当返回类型的函数,或名称不能在模板中用作函数,则会 panic
- 可以覆盖映射中的元素
示例:
funcMap := template.FuncMap{
"title": strings.Title,
"reverse": func(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
},
}
tmpl, err := template.New("test").Funcs(funcMap).Parse(`
{{"hello world" | title}}
{{"hello" | reverse}}
`)
L
Lookup
func (t *Template) Lookup(name string) *Template
作用:返回与 t 关联的具有指定名称的模板
参数说明:
name:模板名称
返回值:
- 找到的模板,如果不存在则返回 nil
示例:
tmpl, err := template.New("main").Parse(`
{{define "T1"}}Template 1{{end}}
{{define "T2"}}Template 2{{end}}
`)
if err != nil {
panic(err)
}
t1 := tmpl.Lookup("T1")
if t1 != nil {
t1.Execute(os.Stdout, nil)
}
t3 := tmpl.Lookup("T3")
if t3 == nil {
fmt.Println("Template T3 not found")
}
N
Name
func (t *Template) Name() string
作用:返回模板的名称
返回值:
- 模板名称字符串
示例:
tmpl := template.New("myTemplate")
fmt.Println(tmpl.Name())
// 输出:myTemplate
New
func (t *Template) New(name string) *Template
作用:分配一个新的、未定义的模板,与给定模板关联并具有相同的分隔符
参数说明:
name:新模板的名称
返回值:
- 新创建的模板
说明:
- 关联是可传递的,允许一个模板通过
{{template}}动作调用另一个模板 - 由于关联的模板共享底层数据,模板构建不能安全地并行进行
- 一旦模板构建完成,它们可以并行执行
示例:
base := template.New("base")
// 创建关联的模板
header := base.New("header")
footer := base.New("footer")
header.Parse(`{{define "header"}}Header Content{{end}}`)
footer.Parse(`{{define "footer"}}Footer Content{{end}}`)
O
Option
func (t *Template) Option(opt ...string) *Template
作用:为模板设置选项
参数说明:
opt:选项字符串列表
返回值:
- 模板本身(支持链式调用)
说明:
- 选项由字符串描述,可以是简单字符串或 “key=value” 格式
- 选项字符串中最多只能有一个等号
- 如果选项字符串无法识别或无效,Option 会 panic
已知选项:
missingkey=default或missingkey=invalid:默认行为,索引不存在的键时返回 “” missingkey=zero:返回映射类型元素的零值missingkey=error:立即停止执行并报错
示例:
// 设置缺失键时返回错误
tmpl, err := template.New("test").
Option("missingkey=error").
Parse("{{.MissingKey}}")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, map[string]string{"Exists": "value"})
// 执行会报错,因为 MissingKey 不存在
P
Parse
func (t *Template) Parse(text string) (*Template, error)
作用:将文本解析为模板 t 的模板体
参数说明:
text:模板文本
返回值:
- 解析后的模板
- 解析错误(如果有)
说明:
- 文本中的命名模板定义(
{{define ...}}或{{block ...}}语句)定义与 t 关联的附加模板,并从 t 的定义中移除 - 可以在连续的 Parse 调用中重新定义模板
- 仅包含空白和注释的模板定义体被视为空,不会替换现有模板的体
示例:
tmpl, err := template.New("test").Parse(`
{{define "T1"}}Template 1{{end}}
{{define "T2"}}Template 2{{end}}
Hello, {{.Name}}!
`)
if err != nil {
panic(err)
}
// 添加更多模板定义
_, err = tmpl.Parse(`{{define "T3"}}Template 3{{end}}`)
if err != nil {
panic(err)
}
ParseFS
func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error)
作用:类似于 ParseFiles 或 ParseGlob,但从文件系统 fsys 读取而不是主机操作系统的文件系统
参数说明:
fsys:文件系统patterns:glob 模式列表
返回值:
- 解析后的模板
- 解析错误(如果有)
示例:
//go:embed templates/*.tmpl
var templates embed.FS
tmpl, err := template.New("main").ParseFS(templates, "templates/*.tmpl")
if err != nil {
panic(err)
}
ParseFiles
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
作用:解析命名文件并将结果模板与 t 关联
参数说明:
filenames:文件名列表
返回值:
- 解析后的模板
- 解析错误(如果有)
说明:
- 必须至少有一个文件
- 如果发生错误,解析停止,返回的模板为 nil
- 创建的模板以参数文件的基本名称(filepath.Base)命名
- 在不同目录中解析具有相同名称的多个文件时,最后提到的一个将是结果
示例:
// 假设有文件 header.tmpl、body.tmpl、footer.tmpl
tmpl, err := template.New("main").ParseFiles(
"header.tmpl",
"body.tmpl",
"footer.tmpl",
)
if err != nil {
panic(err)
}
err = tmpl.ExecuteTemplate(os.Stdout, "main", data)
ParseGlob
func (t *Template) ParseGlob(pattern string) (*Template, error)
作用:解析模式标识的文件中的模板定义,并将结果模板与 t 关联
参数说明:
pattern:文件匹配模式(使用 filepath.Match 语义)
返回值:
- 解析后的模板
- 解析错误(如果有)
说明:
- 模式必须至少匹配一个文件
- 返回的模板将具有模式匹配的第一个文件的基本名称和解析内容
- 等价于调用 ParseFiles 并传入模式匹配的文件列表
示例:
// 解析当前目录下所有 .tmpl 文件
tmpl, err := template.New("main").ParseGlob("*.tmpl")
if err != nil {
panic(err)
}
T
Templates
func (t *Template) Templates() []*Template
作用:返回与 t 关联的定义模板的切片
返回值:
- 模板切片
示例:
tmpl, err := template.New("main").Parse(`
{{define "T1"}}Template 1{{end}}
{{define "T2"}}Template 2{{end}}
{{define "T3"}}Template 3{{end}}
`)
if err != nil {
panic(err)
}
for _, t := range tmpl.Templates() {
fmt.Printf("Template: %s\n", t.Name())
}
// 输出:
// Template: main
// Template: T1
// Template: T2
// Template: T3
包级函数详解
M
Must
func Must(t *Template, err error) *Template
作用:辅助函数,包装对返回 (*Template, error) 的函数的调用,如果错误非 nil 则 panic
参数说明:
t:模板err:错误
返回值:
- 模板
用途:
- 用于变量初始化
示例:
// 常见用法
var t = template.Must(template.New("name").Parse("text"))
// 在函数中
tmpl := template.Must(
template.New("test").
Funcs(funcMap).
Parse("Hello, {{.Name}}!"),
)
N
New
func New(name string) *Template
作用:分配一个新的、未定义的模板,具有给定名称
参数说明:
name:模板名称
返回值:
- 新创建的模板
示例:
tmpl := template.New("myTemplate")
// 继续链式调用
tmpl, err := tmpl.Parse("Content")
P
ParseFS
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)
作用:类似于 Template.ParseFiles 或 Template.ParseGlob,但从文件系统 fsys 读取
参数说明:
fsys:文件系统patterns:glob 模式列表
返回值:
- 解析后的模板
- 解析错误(如果有)
示例:
//go:embed templates/*
var templates embed.FS
tmpl, err := template.ParseFS(templates, "templates/*.tmpl")
if err != nil {
panic(err)
}
ParseFiles
func ParseFiles(filenames ...string) (*Template, error)
作用:创建新模板并从命名文件解析模板定义
参数说明:
filenames:文件名列表
返回值:
- 解析后的模板
- 解析错误(如果有)
说明:
- 必须至少有一个文件
- 如果发生错误,解析停止,返回的模板为 nil
- 返回的模板名称将具有第一个文件的基本名称和解析内容
- 在不同目录中解析具有相同名称的多个文件时,最后提到的一个将是结果
示例:
tmpl, err := template.ParseFiles("header.tmpl", "body.tmpl", "footer.tmpl")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, data)
ParseGlob
func ParseGlob(pattern string) (*Template, error)
作用:创建新模板并从模式标识的文件解析模板定义
参数说明:
pattern:文件匹配模式
返回值:
- 解析后的模板
- 解析错误(如果有)
说明:
- 文件根据 filepath.Match 语义匹配
- 模式必须至少匹配一个文件
- 返回的模板将具有模式匹配的第一个文件的基本名称和解析内容
- 等价于调用 ParseFiles 并传入模式匹配的文件列表
示例:
tmpl, err := template.ParseGlob("templates/*.tmpl")
if err != nil {
panic(err)
}
模板语法详解
动作(Actions)
注释
{{/* 这是一个注释 */}}
{{- /* 带空白修剪的注释 */ -}}
管道
{{.Field}}
{{.Method}}
{{function arg1 arg2}}
{{.Field | function | anotherFunc}}
条件
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
循环
{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T0 {{end}}
{{range $index, $element := pipeline}} T1 {{end}}
{{break}}
{{continue}}
模板包含
{{template "name"}}
{{template "name" pipeline}}
{{block "name" pipeline}} T1 {{end}}
With 语句
{{with pipeline}} T1 {{end}}
{{with pipeline}} T1 {{else}} T0 {{end}}
{{with pipeline}} T1 {{else with pipeline}} T0 {{end}}
参数(Arguments)
{{true}} // 布尔常量
{{"string"}} // 字符串常量
{{`raw string`}} // 原始字符串常量
{{42}} // 整数常量
{{3.14}} // 浮点常量
{{nil}} // nil
{{.}} // 点(当前值)
{{$var}} // 变量
{{.Field}} // 字段
{{.Key}} // 映射键
{{.Method}} // 方法
{{function}} // 函数
{{(.Field).Method}} // 分组
管道(Pipelines)
{{.Field}}
{{.Method arg1 arg2}}
{{function arg1 arg2}}
{{arg | function}}
{{arg | func1 | func2}}
变量(Variables)
{{$var := pipeline}}
{{$var = pipeline}}
{{range $index, $value := pipeline}}
预定义函数
逻辑函数
{{and x y}} // 逻辑与
{{or x y}} // 逻辑或
{{not x}} // 逻辑非
比较函数
{{eq x y}} // x == y
{{ne x y}} // x != y
{{lt x y}} // x < y
{{le x y}} // x <= y
{{gt x y}} // x > y
{{ge x y}} // x >= y
转换函数
{{html x}} // HTML 转义
{{js x}} // JavaScript 转义
{{urlquery x}} // URL 查询转义
其他函数
{{call func args}} // 调用函数
{{index x y z}} // 索引操作 x[y][z]
{{slice x}} // 切片操作
{{len x}} // 长度
{{print args}} // fmt.Sprint
{{printf fmt args}}// fmt.Sprintf
{{println args}} // fmt.Sprintln
典型示例
1. 基本模板使用
package main
import (
"os"
"text/template"
)
func main() {
// 创建并解析模板
tmpl, err := template.New("test").Parse("Hello, {{.Name}}!")
if err != nil {
panic(err)
}
// 执行模板
data := struct{ Name string }{Name: "Alice"}
err = tmpl.Execute(os.Stdout, data)
if err != nil {
panic(err)
}
}
2. 使用结构体字段
package main
import (
"os"
"text/template"
)
type Person struct {
Name string
Age int
City string
}
func main() {
tmpl := template.Must(template.New("person").Parse(`
Name: {{.Name}}
Age: {{.Age}}
City: {{.City}}
`))
p := Person{
Name: "Bob",
Age: 30,
City: "Beijing",
}
tmpl.Execute(os.Stdout, p)
}
3. 使用 If 条件
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(template.New("condition").Parse(`
{{if .Active}}
User is active
{{else}}
User is inactive
{{end}}
`))
data := struct{ Active bool }{Active: true}
tmpl.Execute(os.Stdout, data)
4. 使用 Range 循环
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(template.New("range").Parse(`
Users:
{{range .Users}}
- {{.Name}} ({{.Email}})
{{end}}
`))
data := struct {
Users []struct {
Name string
Email string
}
}{
Users: []struct {
Name string
Email string
}{
{"Alice", "alice@example.com"},
{"Bob", "bob@example.com"},
{"Charlie", "charlie@example.com"},
},
}
tmpl.Execute(os.Stdout, data)
}
5. 使用自定义函数
package main
import (
"os"
"strings"
"text/template"
)
func main() {
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
}
tmpl := template.Must(
template.New("funcs").Funcs(funcMap).Parse(`
Original: {{.Text}}
Upper: {{.Text | upper}}
Lower: {{.Text | lower}}
`))
data := struct{ Text string }{Text: "Hello World"}
tmpl.Execute(os.Stdout, data)
}
6. 使用模板定义和包含
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(template.New("main").Parse(`
{{define "header"}}
=== HEADER ===
{{end}}
{{define "footer"}}
=== FOOTER ===
{{end}}
{{template "header"}}
Main Content
{{template "footer"}}
`))
tmpl.Execute(os.Stdout, nil)
}
7. 使用 Block
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(template.New("main").Parse(`
{{block "content" .}}
Default Content
{{end}}
`))
// 重写 block
tmpl.New("content").Parse("Custom Content")
tmpl.Execute(os.Stdout, nil)
}
8. 使用变量
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(template.New("vars").Parse(`
{{$name := .Name}}
Hello, {{$name}}!
Your name has {{len $name}} letters.
{{range $i, $char := $name}}
Character {{$i}}: {{$char}}
{{end}}
`))
data := struct{ Name string }{Name: "Alice"}
tmpl.Execute(os.Stdout, data)
}
9. 从文件解析模板
package main
import (
"os"
"text/template"
)
func main() {
// 假设有 template.tmpl 文件
tmpl, err := template.ParseFiles("template.tmpl")
if err != nil {
panic(err)
}
data := struct{ Name string }{Name: "World"}
tmpl.Execute(os.Stdout, data)
}
10. 使用 Glob 模式解析多个文件
package main
import (
"os"
"text/template"
)
func main() {
// 解析所有 .tmpl 文件
tmpl, err := template.ParseGlob("templates/*.tmpl")
if err != nil {
panic(err)
}
data := struct{ Name string }{Name: "World"}
// 执行特定模板
tmpl.ExecuteTemplate(os.Stdout, "header.tmpl", data)
tmpl.ExecuteTemplate(os.Stdout, "body.tmpl", data)
tmpl.ExecuteTemplate(os.Stdout, "footer.tmpl", data)
}
11. 使用管道链
package main
import (
"os"
"strings"
"text/template"
)
func main() {
funcMap := template.FuncMap{
"trim": strings.TrimSpace,
"upper": strings.ToUpper,
"repeat": func(s string, n int) string {
return strings.Repeat(s, n)
},
}
tmpl := template.Must(
template.New("pipeline").Funcs(funcMap).Parse(`
{{" hello world " | trim | upper | repeat 2}}
`))
tmpl.Execute(os.Stdout, nil)
// 输出:HELLO WORLDHELLO WORLD
}
12. 使用 With 语句
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(template.New("with").Parse(`
{{with .User}}
Name: {{.Name}}
Age: {{.Age}}
{{else}}
No user data
{{end}}
`))
data := struct {
User *struct {
Name string
Age int
}
}{
User: &struct {
Name string
Age int
}{
Name: "Alice",
Age: 30,
},
}
tmpl.Execute(os.Stdout, data)
}
13. 使用比较函数
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(template.New("compare").Parse(`
{{if eq .Age 18}}
You are exactly 18 years old.
{{else if gt .Age 18}}
You are an adult.
{{else}}
You are a minor.
{{end}}
`))
data := struct{ Age int }{Age: 20}
tmpl.Execute(os.Stdout, data)
}
14. 使用 MissingKey 选项
package main
import (
"os"
"text/template"
)
func main() {
// 设置缺失键时返回错误
tmpl := template.Must(
template.New("test").
Option("missingkey=error").
Parse("{{.MissingKey}}"))
data := map[string]string{
"ExistingKey": "value",
}
err := tmpl.Execute(os.Stdout, data)
if err != nil {
println("Error:", err.Error())
}
}
15. 使用自定义分隔符
package main
import (
"os"
"text/template"
)
func main() {
tmpl := template.Must(
template.New("delims").
Delims("<%", "%>").
Parse(`
<%if .ShowGreeting%>
Hello, <% .Name %>!
<%end%>
`))
data := struct {
ShowGreeting bool
Name string
}{
ShowGreeting: true,
Name: "World",
}
tmpl.Execute(os.Stdout, data)
}
最佳实践
1. 使用 Must 简化初始化
// 推荐的做法
var tmpl = template.Must(template.New("name").Parse("text"))
// 不推荐
tmpl, err := template.New("name").Parse("text")
if err != nil {
panic(err)
}
2. 链式调用
tmpl := template.Must(
template.New("name").
Funcs(funcMap).
Option("missingkey=error").
Parse("template text"),
)
3. 分离模板定义
// 使用多个文件
tmpl, err := template.ParseFiles(
"header.tmpl",
"body.tmpl",
"footer.tmpl",
)
// 或使用 glob 模式
tmpl, err := template.ParseGlob("templates/*.tmpl")
4. 使用 Block 实现模板继承
base := template.Must(template.New("base").Parse(`
{{block "header" .}}Default Header{{end}}
{{block "content" .}}Default Content{{end}}
{{block "footer" .}}Default Footer{{end}}
`))
// 创建变体
variant := template.Must(base.Clone())
variant.New("header").Parse("Custom Header")
5. 错误处理
err := tmpl.Execute(os.Stdout, data)
if err != nil {
var execErr template.ExecError
if errors.As(err, &execErr) {
log.Printf("Template %s error: %v", execErr.Name, execErr.Err)
} else {
log.Printf("Execution error: %v", err)
}
}
6. 使用嵌入文件系统
//go:embed templates/*
var templates embed.FS
tmpl := template.Must(template.ParseFS(templates, "templates/*.tmpl"))
7. 预定义常用函数
var funcMap = template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
"trim": strings.TrimSpace,
// ... 更多函数
}
tmpl := template.Must(
template.New("name").Funcs(funcMap).Parse(text),
)
与其他包配合
html/template 包
import (
"html/template" // 用于 HTML 输出
)
// 接口相同,但会自动转义 HTML
tmpl := template.Must(template.New("html").Parse(`
<html>
<body>
<h1>{{.Title}}</h1> <!-- 自动转义 -->
</body>
</html>
`))
fmt 包
import (
"fmt"
"text/template"
)
// print、printf、println 是 fmt.Sprint、fmt.Sprintf、fmt.Sprintln 的别名
tmpl := template.Must(template.New("fmt").Parse(`
{{print .Text}}
{{printf "Formatted: %s" .Text}}
{{println .Text}}
`))
strings 包
import (
"strings"
"text/template"
)
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
"trim": strings.TrimSpace,
}
embed 包
import (
"embed"
"text/template"
)
//go:embed templates/*
var templates embed.FS
tmpl := template.Must(template.ParseFS(templates, "templates/*.tmpl"))
注意事项
1. 安全性
// text/template 不自动转义 HTML
// 对于 HTML 输出,使用 html/template
import "html/template" // 用于 HTML
import "text/template" // 用于纯文本
2. 并行执行
// 模板构建不能安全地并行进行
// 一旦构建完成,可以安全地并行执行
// 错误示例
go func() {
tmpl.Parse("template 1")
}()
go func() {
tmpl.Parse("template 2")
}()
// 正确示例
// 先构建完成
tmpl.Parse("template 1")
tmpl.Parse("template 2")
// 然后并行执行
go func() {
tmpl.Execute(w1, data1)
}()
go func() {
tmpl.Execute(w2, data2)
}()
3. 空值处理
// 空值:false, 0, nil, 长度为零的集合
tmpl := template.Must(template.New("test").Parse(`
{{if .EmptySlice}} // 不会执行
{{end}}
{{if .NonEmptySlice}} // 会执行
{{end}}
`))
4. 映射键访问
// 键不需要大写开头
data := map[string]string{
"name": "Alice", // 小写键
}
tmpl := template.Must(template.New("test").Parse(`
{{.name}} // 可以访问
`))
5. 方法调用
// 方法必须没有参数或返回 1-2 个值
type Data struct{}
func (d Data) GetName() string { return "Name" }
func (d Data) GetInfo() (string, error) { return "Info", nil }
// 可以调用
tmpl := template.Must(template.New("test").Parse(`
{{.GetName}}
{{.GetInfo}}
`))
快速参考
动作速查表
| 动作 | 说明 |
|---|---|
{{/* comment */}} | 注释 |
{{pipeline}} | 输出管道值 |
{{if pipeline}} T {{end}} | 条件执行 |
{{if}} T1 {{else}} T0 {{end}} | if-else |
{{range pipeline}} T {{end}} | 循环 |
{{range $i, $v := pipeline}} | 带索引的循环 |
{{template "name"}} | 包含模板 |
{{block "name" pipeline}} T {{end}} | 定义并执行块 |
{{with pipeline}} T {{end}} | 设置 dot 并执行 |
{{break}} | 跳出循环 |
{{continue}} | 继续下次循环 |
函数速查表
| 函数 | 说明 |
|---|---|
and | 逻辑与 |
or | 逻辑或 |
not | 逻辑非 |
eq | 等于 |
ne | 不等于 |
lt | 小于 |
le | 小于等于 |
gt | 大于 |
ge | 大于等于 |
html | HTML 转义 |
js | JavaScript 转义 |
urlquery | URL 查询转义 |
len | 长度 |
index | 索引 |
slice | 切片 |
call | 调用函数 |
print | fmt.Sprint |
printf | fmt.Sprintf |
println | fmt.Sprintln |
方法速查表
| 方法 | 说明 |
|---|---|
Execute | 执行模板 |
ExecuteTemplate | 执行指定模板 |
Parse | 解析模板文本 |
ParseFiles | 从文件解析 |
ParseGlob | 从 glob 模式解析 |
ParseFS | 从文件系统解析 |
Funcs | 添加函数 |
Option | 设置选项 |
Delims | 设置分隔符 |
Clone | 克隆模板 |
Lookup | 查找模板 |
Templates | 获取所有模板 |
DefinedTemplates | 获取定义列表 |
Name | 获取名称 |
New | 创建新模板 |
AddParseTree | 添加解析树 |
常见模式
// 字段访问
{{.Field}}
{{.Field1.Field2}}
// 映射访问
{{.Key}}
{{.Key1.Key2}}
// 方法调用
{{.Method}}
{{.Method.Arg}}
// 变量
{{$var := pipeline}}
{{$var = pipeline}}
// 循环
{{range .Items}}
{{.}}
{{end}}
{{range $i, $v := .Items}}
{{$i}}: {{$v}}
{{end}}
// 条件
{{if .Condition}}
True branch
{{else}}
False branch
{{end}}
// 管道
{{.Value | function1 | function2}}
// 模板包含
{{template "name"}}
{{template "name" .Data}}
// 块定义
{{block "name" .}}
Default content
{{end}}
总结
text/template 包提供了强大的数据驱动模板功能:
核心功能:
- 模板解析和执行
- 数据驱动的输出生成
- 控制结构(if、range、with)
- 自定义函数支持
- 模板继承和包含
主要类型:
Template:解析后的模板表示FuncMap:函数映射ExecError:执行错误类型
包级函数:
New:创建新模板Must:辅助函数ParseFiles/ParseGlob/ParseFS:解析模板HTMLEscape/JSEscape:转义函数
使用建议:
- 使用
Must简化初始化 - 使用链式调用提高可读性
- 对于 HTML 输出使用
html/template - 使用 Block 实现模板继承
- 预定义常用函数
- 正确处理错误
典型用法:
tmpl := template.Must(
template.New("name").
Funcs(funcMap).
Parse("Hello, {{.Name}}!"),
)
err := tmpl.Execute(os.Stdout, data)
通过 text/template 包,可以方便地生成各种动态文本内容,从简单的字符串替换到复杂的报告生成。