Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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())
// 输出:&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;

HTMLEscapeString

func HTMLEscapeString(s string) string

作用:返回纯文本数据 s 的转义 HTML 等效形式

参数说明

  • s:要转义的字符串

返回值

  • 转义后的字符串

示例

escaped := template.HTMLEscapeString("<script>alert('XSS')</script>")
fmt.Println(escaped)
// 输出:&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;

HTMLEscaper

func HTMLEscaper(args ...any) string

作用:返回参数的文本表示的转义 HTML 等效形式

参数说明

  • args:可变参数列表

返回值

  • 转义后的字符串

示例

escaped := template.HTMLEscaper("<html>", "&", "special")
fmt.Println(escaped)
// 输出:&lt;html&gt; &amp; 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=defaultmissingkey=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大于等于
htmlHTML 转义
jsJavaScript 转义
urlqueryURL 查询转义
len长度
index索引
slice切片
call调用函数
printfmt.Sprint
printffmt.Sprintf
printlnfmt.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:转义函数

使用建议

  1. 使用 Must 简化初始化
  2. 使用链式调用提高可读性
  3. 对于 HTML 输出使用 html/template
  4. 使用 Block 实现模板继承
  5. 预定义常用函数
  6. 正确处理错误

典型用法

tmpl := template.Must(
    template.New("name").
    Funcs(funcMap).
    Parse("Hello, {{.Name}}!"),
)

err := tmpl.Execute(os.Stdout, data)

通过 text/template 包,可以方便地生成各种动态文本内容,从简单的字符串替换到复杂的报告生成。