package template

import "text/template"

Package template 实现了用于生成文本输出的数据驱动模板。

要生成 HTML 输出,请参阅 html/template,它具有与此包相同的接口, 但会自动保护 HTML 输出免受某些攻击。

模板通过将其应用于数据结构来执行。模板中的注解引用数据结构的元素 (通常是结构体的字段或 map 的键)来控制执行并导出要显示的值。 模板的执行遍历结构体并设置游标,用句点 '.' 表示,称为"点"(dot), 随着执行进行,将其设置为结构体中当前位置的值。

此包使用的安全模型假定模板作者是可信的。该包不会自动转义输出, 因此如果将代码注入模板并由不受信任的源执行,可能导致任意代码执行。

模板的输入文本是任意格式的 UTF-8 编码文本。"动作"--数据求值或控制结构-- 由 "{{" 和 "}}" 定界;动作外的所有文本原样复制到输出。

解析后,模板可以安全地并行执行,尽管如果并行执行共享一个 Writer, 输出可能会交错。

下面是一个打印 "17 items are made of wool" 的简单示例。

type Inventory struct {
	Material string
	Count    uint
}
sweaters := Inventory{"wool", 17}
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)
if err != nil { panic(err) }

更多复杂的示例如下。

文本和空格

默认情况下,执行模板时,动作之间的所有文本都会被原样复制。 例如,上面示例中的字符串 " items are made of " 在程序运行时出现在标准输出中。

但是,为了帮助格式化模板源代码,如果动作的左分隔符(默认 "{{") 后紧跟减号和空白,则会从紧邻的前一个文本中修剪所有尾随空白。 类似地,如果右分隔符("}}")前有空白和减号,则会从紧接的后续文本中 修剪所有前导空白。在这些修剪标记中,空白必须存在: "{{- 3}}" 类似于 "{{3}}",但会修剪紧邻的前一个文本,而 "{{-3}}" 解析为包含数字 -3 的动作。

例如,执行源为

"{{23 -}} < {{- 45}}"

的模板时,生成的输出为

"23<45"

对于此修剪,空白字符的定义与 Go 中相同:空格、水平制表符、回车符和换行符。

动作

以下是动作列表。"参数"和"管道"是对数据的求值,详情见相应章节。

{{/* a comment */}}
{{- /* a comment with white space trimmed from preceding and following text */ -}}
	注释;被丢弃。可能包含换行符。
	注释不嵌套,必须像这里所示的那样在分隔符处开始和结束。

{{pipeline}}
	管道值的默认文本表示(与 fmt.Print 打印的相同)被复制到输出。

{{if pipeline}} T1 {{end}}
	如果管道的值为空,则不生成输出;否则执行 T1。
	空值包括 false、0、任何 nil 指针或接口值,以及任何长度为零的数组、切片、映射或字符串。
	点不受影响。

{{if pipeline}} T1 {{else}} T0 {{end}}
	如果管道的值为空,则执行 T0;否则执行 T1。点不受影响。

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
	为了简化 if-else 链的外观,if 的 else 动作可以直接包含另一个 if;
	效果与编写
		{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
	完全相同。

{{range pipeline}} T1 {{end}}
	管道的值必须是数组、切片、映射、iter.Seq、iter.Seq2、整数或通道。
	如果管道的值为零长度,则不产生输出;否则,点被设置为数组、切片或映射的连续元素,
	并执行 T1。如果值是映射且键是具有定义顺序的基本类型,则元素将按排序的键顺序遍历。

{{range pipeline}} T1 {{else}} T0 {{end}}
	管道的值必须是数组、切片、映射、iter.Seq、iter.Seq2、整数或通道。
	如果管道的值为零长度,则点不受影响并执行 T0;
	否则,点被设置为数组、切片或映射的连续元素并执行 T1。

{{break}}
	提前结束最内层的 {{range pipeline}} 循环,停止当前迭代并绕过所有剩余迭代。

{{continue}}
	停止最内层 {{range pipeline}} 循环的当前迭代,并开始下一次迭代。

{{template "name"}}
	执行具有指定名称的模板,数据为 nil。

{{template "name" pipeline}}
	执行具有指定名称的模板,点设置为管道的值。

{{block "name" pipeline}} T1 {{end}}
	block 是定义模板的简写
		{{define "name"}} T1 {{end}}
	然后在原地执行它
		{{template "name" pipeline}}
	典型用途是定义一组根模板,然后通过在内部重新定义 block 模板来自定义。

{{with pipeline}} T1 {{end}}
	如果管道的值为空,则不生成输出;否则将点设置为管道的值并执行 T1。

{{with pipeline}} T1 {{else}} T0 {{end}}
	如果管道的值为空,则点不受影响并执行 T0;
	否则将点设置为管道的值并执行 T1。

{{with pipeline}} T1 {{else with pipeline}} T0 {{end}}
	为了简化 with-else 链的外观,with 的 else 动作可以直接包含另一个 with;
	效果与编写
		{{with pipeline}} T1 {{else}}{{with pipeline}} T0 {{end}}{{end}}
	完全相同。

参数

参数是一个简单值,由以下之一表示。

参数可以求值为任何类型;如果它们是指针,实现会在需要时自动间接到基类型。 如果求值产生函数值,例如结构体的函数值字段,该函数不会自动调用, 但可以用作 if 动作等的真值。要调用它,请使用下面定义的 call 函数。

管道

管道是一个可能链接的"命令"序列。命令是一个简单值(参数)或函数或方法调用, 可能有多个参数:

Argument
	结果是求值参数的值。
.Method [Argument...]
	该方法可以单独使用或作为链的最后一个元素,
	但与链中间的方法不同,它可以接受参数。
	结果是用参数调用方法的值:
		dot.Method(Argument1, etc.)
functionName [Argument...]
	结果是使用名称调用关联函数的值:
		function(Argument1, etc.)
	函数和函数名在下面描述。

管道可以通过管道字符 '|' 分隔一系列命令来"链接"。 在链接管道中,每个命令的结果作为最后一个参数传递给下一个命令。 管道中最后一个命令的输出是管道的值。

命令的输出可以是一个值或两个值,第二个值的类型为 error。 如果第二个值存在且求值非空,执行终止并将错误返回给 Execute 的调用者。

变量

动作中的管道可以初始化一个变量来捕获结果。 初始化语法为

$variable := pipeline

其中 $variable 是变量名。声明变量的动作不产生输出。

先前声明的变量也可以使用语法赋值

$variable = pipeline

如果"range"动作初始化了一个变量,该变量被设置为迭代的连续元素。 此外,"range"可以声明两个变量,用逗号分隔:

range $index, $element := pipeline

在这种情况下,$index 和 $element 分别被设置为数组/切片索引或映射键和元素的连续值。 注意,如果只有一个变量,它被分配元素;这与 Go range 子句中的约定相反。

变量的作用域扩展到控制结构("if"、"with"或"range")的"end"动作, 或者如果没有这样的控制结构,则扩展到模板的末尾。 模板调用不会从其调用点继承变量。

当执行开始时,$ 被设置为传递给 Execute 的数据参数,即点的起始值。

示例

这里有一些演示管道和变量的单行模板示例。 所有都产生引用的单词 "output":

{{"\"output\""}}
	字符串常量。
{{`"output"`}}
	原始字符串常量。
{{printf "%q" "output"}}
	函数调用。
{{"output" | printf "%q"}}
	函数调用,其最终参数来自上一个命令。
{{printf "%q" (print "out" "put")}}
	带括号的参数。
{{"put" | printf "%s%s" "out" | printf "%q"}}
	更复杂的调用。
{{"output" | printf "%s" | printf "%q"}}
	更长的链。
{{with "output"}}{{printf "%q" .}}{{end}}
	使用点的 with 动作。
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
	创建和使用变量的 with 动作。
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
	在另一个动作中使用变量的 with 动作。
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
	相同的,但使用管道。

函数

在执行期间,函数在两个函数映射中找到:首先在模板中,然后在全局函数映射中。 默认情况下,模板中没有定义函数,但可以使用 Funcs 方法添加它们。

预定义的全局函数如下命名。

and
	通过返回第一个空参数或最后一个参数来返回其参数的布尔 AND。
	也就是说,"and x y" 的行为类似于 "if x then y else x"。
	求值从左到右进行,并在确定结果时返回。
call
	返回调用第一个参数(必须是函数)的结果,其余参数作为参数。
	因此 "call .X.Y 1 2" 在 Go 表示法中是 dot.X.Y(1, 2),
	其中 Y 是函数值字段、映射条目或类似的东西。
	第一个参数必须是求值产生函数类型值的结果(与 print 之类的预定义函数不同)。
	函数必须返回一个或两个结果值,第二个结果的类型为 error。
	如果参数不匹配或返回的错误值非空,执行停止。
html
	返回其参数文本表示的转义 HTML 等效值。
	此函数在 html/template 中不可用,有几个例外。
index
	返回通过以下参数索引其第一个参数的结果。
	因此 "index x 1 2 3" 在 Go 语法中是 x[1][2][3]。
	每个索引项必须是映射、切片或数组。
slice
	slice 返回用其余参数切片其第一个参数的结果。
	因此 "slice x 1 2" 在 Go 语法中是 x[1:2],
	而 "slice x" 是 x[:],"slice x 1" 是 x[1:],
	"slice x 1 2 3" 是 x[1:2:3]。第一个参数必须是字符串、切片或数组。
js
	返回其参数文本表示的转义 JavaScript 等效值。
len
	返回其参数的整数长度。
not
	返回其单个参数的布尔否定。
or
	通过返回第一个非空参数或最后一个参数来返回其参数的布尔 OR,
	也就是说,"or x y" 的行为类似于 "if x then x else y"。
	求值从左到右进行,并在确定结果时返回。
print
	fmt.Sprint 的别名。
printf
	fmt.Sprintf 的别名。
println
	fmt.Sprintln 的别名。
urlquery
	返回其参数文本表示的转义值,适用于嵌入 URL 查询。
	此函数在 html/template 中不可用,有几个例外。

布尔函数将任何零值视为 false,非零值视为 true。

还有一组定义为函数的二元比较运算符:

eq
	返回 arg1 == arg2 的布尔真值。
ne
	返回 arg1 != arg2 的布尔真值。
lt
	返回 arg1 < arg2 的布尔真值。
le
	返回 arg1 <= arg2 的布尔真值。
gt
	返回 arg1 > arg2 的布尔真值。
ge
	返回 arg1 >= arg2 的布尔真值。

为了更简单的多路相等测试,eq(仅)接受两个或更多参数, 并将后续参数与第一个参数比较,实际上返回

arg1==arg2 || arg1==arg3 || arg1==arg4 ...

(然而,与 Go 中的 || 不同,eq 是函数调用,所有参数都将被求值。)

比较函数适用于 Go 定义为可比较的任何值。 对于基本类型(如整数),规则放宽:忽略大小和确切类型, 因此任何整数值(有符号或无符号)都可以与任何其他整数值进行比较。 (比较的是算术值,而不是位模式,因此所有负整数都小于所有无符号整数。) 但是,与往常一样,不能将 int 与 float32 等进行比较。

关联模板

每个模板都有一个创建时指定的字符串名称。此外,每个模板都与零个或多个其他模板关联, 它可以通过名称调用这些模板;这种关联是传递的,形成模板的名称空间。

模板可以使用模板调用来实例化另一个关联模板;请参阅上面"template"动作的说明。 名称必须是包含调用的模板关联的模板之一。

嵌套模板定义

解析模板时,可以定义并关联正在解析的模板。 模板定义必须出现在模板的顶层,类似于 Go 程序中的全局变量。

此类定义的语法是用"define"和"end"动作包围每个模板声明。

define 动作通过提供字符串常量来命名正在创建的模板。 这是一个简单的例子:

{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}

这定义了两个模板 T1 和 T2,以及一个在执行时调用其他两个的 T3。 最后它调用 T3。如果执行此模板将产生文本

ONE TWO

根据构造,模板只能存在于一个关联中。如果需要让模板可从多个关联寻址, 则必须多次解析模板定义以创建不同的 *Template 值, 或者必须使用 Template.CloneTemplate.AddParseTree 复制。

可以多次调用 Parse 来组装各种关联模板; 请参阅 ParseFilesParseGlobTemplate.ParseFilesTemplate.ParseGlob 获取解析存储在文件中的相关模板的简单方法。

模板可以直接执行,也可以通过 Template.ExecuteTemplate 执行, 后者执行按名称识别的关联模板。 要调用上面的示例,我们可以写

err := tmpl.Execute(os.Stdout, "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}

或者通过名称显式调用特定模板

err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}

Index

Examples

Functions

func HTMLEscape

func HTMLEscape(w io.Writer, b []byte)

HTMLEscape 将纯文本数据 b 的转义 HTML 等效值写入 w。

func HTMLEscapeString

func HTMLEscapeString(s string) string

HTMLEscapeString 返回纯文本数据 s 的转义 HTML 等效值。

func HTMLEscaper

func HTMLEscaper(args ...any) string

HTMLEscaper 返回其参数文本表示的转义 HTML 等效值。

func IsTrue

func IsTrue(val any) (truth, ok bool)

IsTrue 报告值是否"真"(在其类型的零值意义上), 以及值是否有有意义的真值。这是 if 和其他类似动作使用的真值定义。

func JSEscape

func JSEscape(w io.Writer, b []byte)

JSEscape 将纯文本数据 b 的转义 JavaScript 等效值写入 w。

func JSEscapeString

func JSEscapeString(s string) string

JSEscapeString 返回纯文本数据 s 的转义 JavaScript 等效值。

func JSEscaper

func JSEscaper(args ...any) string

JSEscaper 返回其参数文本表示的转义 JavaScript 等效值。

func URLQueryEscaper

func URLQueryEscaper(args ...any) string

URLQueryEscaper 返回其参数文本表示的转义值,适用于嵌入 URL 查询。

Types

type ExecError

type ExecError struct {
	Name string // 模板名称。
	Err  error  // 预格式化的错误。
}

ExecError 是 Execute 在求值模板时出错时返回的自定义错误类型。 (如果发生写入错误,则返回实际错误;它不会是 ExecError 类型。)

func (ExecError) Error

func (e ExecError) Error() string

func (ExecError) Unwrap

func (e ExecError) Unwrap() error

type FuncMap

type FuncMap map[string]any

FuncMap 是定义从名称到函数映射的 map 类型。 每个函数必须只有一个返回值,或两个返回值,其中第二个是 error 类型。 在执行期间,如果第二个(错误)返回值求值为非 nil,执行终止, Execute 返回该错误。

Execute 返回的错误包装底层错误;调用 errors.AsType 来展开它们。

当模板执行用参数列表调用函数时,该列表必须可赋值给函数的参数类型。 适用于任意类型参数的函数可以使用类型为 interface{} 或 reflect.Value 的参数。 类似地,注定返回任意类型结果的函数可以返回 interface{} 或 reflect.Value

type Template

type Template struct {
	*parse.Tree
	// contains filtered or unexported fields
}

Template 是解析后模板的表示。*parse.Tree 字段仅导出供 html/template 使用, 所有其他客户端应将其视为未导出。

Example
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	// Define a template.
	const letter = `
Dear {{.Name}},
{{if .Attended}}
It was a pleasure to see you at the wedding.
{{- else}}
It is a shame you couldn't make it to the wedding.
{{- end}}
{{with .Gift -}}
Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
`

	// Prepare some data to insert into the template.
	type Recipient struct {
		Name, Gift string
		Attended   bool
	}
	var recipients = []Recipient{
		{"Aunt Mildred", "bone china tea set", true},
		{"Uncle John", "moleskin pants", false},
		{"Cousin Rodney", "", false},
	}

	// Create a new template and parse the letter into it.
	t := template.Must(template.New("letter").Parse(letter))

	// Execute the template for each recipient.
	for _, r := range recipients {
		err := t.Execute(os.Stdout, r)
		if err != nil {
			log.Println("executing template:", err)
		}
	}

}

Output:

Dear Aunt Mildred,

It was a pleasure to see you at the wedding.
Thank you for the lovely bone china tea set.

Best wishes,
Josie

Dear Uncle John,

It is a shame you couldn't make it to the wedding.
Thank you for the lovely moleskin pants.

Best wishes,
Josie

Dear Cousin Rodney,

It is a shame you couldn't make it to the wedding.

Best wishes,
Josie
Example (Block)
package main

import (
	"log"
	"os"
	"strings"
	"text/template"
)

func main() {
	const (
		master  = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
		overlay = `{{define "list"}} {{join . ", "}}{{end}} `
	)
	var (
		funcs     = template.FuncMap{"join": strings.Join}
		guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
	)
	masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
	if err != nil {
		log.Fatal(err)
	}
	overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
	if err != nil {
		log.Fatal(err)
	}
	if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
		log.Fatal(err)
	}
	if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
		log.Fatal(err)
	}
}

Output:

Names:
- Gamora
- Groot
- Nebula
- Rocket
- Star-Lord
Names: Gamora, Groot, Nebula, Rocket, Star-Lord
Example (Func)

This example demonstrates a custom function to process template text. It installs the strings.Title function and uses it to Make Title Text Look Good In Our Template's Output.

package main

import (
	"log"
	"os"
	"strings"
	"text/template"
)

func main() {
	// First we create a FuncMap with which to register the function.
	funcMap := template.FuncMap{
		// The name "title" is what the function will be called in the template text.
		"title": strings.Title,
	}

	// A simple template definition to test our function.
	// We print the input text several ways:
	// - the original
	// - title-cased
	// - title-cased and then printed with %q
	// - printed with %q and then title-cased.
	const templateText = `
Input: {{printf "%q" .}}
Output 0: {{title .}}
Output 1: {{title . | printf "%q"}}
Output 2: {{printf "%q" . | title}}
`

	// Create a template, add the function map, and parse the text.
	tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText)
	if err != nil {
		log.Fatalf("parsing: %s", err)
	}

	// Run the template to verify the output.
	err = tmpl.Execute(os.Stdout, "the go programming language")
	if err != nil {
		log.Fatalf("execution: %s", err)
	}

}

Output:

Input: "the go programming language"
Output 0: The Go Programming Language
Output 1: "The Go Programming Language"
Output 2: "The Go Programming Language"
Example (Funcs)

This example demonstrates registering two custom template functions and how to overwite one of the functions after the template has been parsed. Overwriting can be used, for example, to alter the operation of cloned templates.

package main

import (
	"log"
	"os"
	"strings"
	"text/template"
)

func main() {

	// Define a simple template to test the functions.
	const tmpl = `{{ . | lower | repeat }}`

	// Define the template funcMap with two functions.
	var funcMap = template.FuncMap{
		"lower":  strings.ToLower,
		"repeat": func(s string) string { return strings.Repeat(s, 2) },
	}

	// Define a New template, add the funcMap using Funcs and then Parse
	// the template.
	parsedTmpl, err := template.New("t").Funcs(funcMap).Parse(tmpl)
	if err != nil {
		log.Fatal(err)
	}
	if err := parsedTmpl.Execute(os.Stdout, "ABC\n"); err != nil {
		log.Fatal(err)
	}

	// [Funcs] must be called before a template is parsed to add
	// functions to the template. [Funcs] can also be used after a
	// template is parsed to overwrite template functions.
	//
	// Here the function identified by "repeat" is overwritten.
	parsedTmpl.Funcs(template.FuncMap{
		"repeat": func(s string) string { return strings.Repeat(s, 3) },
	})
	if err := parsedTmpl.Execute(os.Stdout, "DEF\n"); err != nil {
		log.Fatal(err)
	}
}

Output:

abc
abc
def
def
def
Example (Glob)

Here we demonstrate loading a set of templates from a directory.

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"text/template"
)

// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
	name     string
	contents string
}

func createTestDir(files []templateFile) string {
	dir, err := os.MkdirTemp("", "template")
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		f, err := os.Create(filepath.Join(dir, file.name))
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		_, err = io.WriteString(f, file.contents)
		if err != nil {
			log.Fatal(err)
		}
	}
	return dir
}

func main() {
	// Here we create a temporary directory and populate it with our sample
	// template definition files; usually the template files would already
	// exist in some location known to the program.
	dir := createTestDir([]templateFile{
		// T0.tmpl is a plain template file that just invokes T1.
		{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
		// T1.tmpl defines a template, T1 that invokes T2.
		{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
		// T2.tmpl defines a template T2.
		{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
	})
	// Clean up after the test; another quirk of running as an example.
	defer os.RemoveAll(dir)

	// pattern is the glob pattern used to find all the template files.
	pattern := filepath.Join(dir, "*.tmpl")

	// Here starts the example proper.
	// T0.tmpl is the first name matched, so it becomes the starting template,
	// the value returned by ParseGlob.
	tmpl := template.Must(template.ParseGlob(pattern))

	err := tmpl.Execute(os.Stdout, nil)
	if err != nil {
		log.Fatalf("template execution: %s", err)
	}
}

Output:

T0 invokes T1: (T1 invokes T2: (This is T2))
Example (Helpers)

This example demonstrates one way to share some templates and use them in different contexts. In this variant we add multiple driver templates by hand to an existing bundle of templates.

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"text/template"
)

// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
	name     string
	contents string
}

func createTestDir(files []templateFile) string {
	dir, err := os.MkdirTemp("", "template")
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		f, err := os.Create(filepath.Join(dir, file.name))
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		_, err = io.WriteString(f, file.contents)
		if err != nil {
			log.Fatal(err)
		}
	}
	return dir
}

func main() {
	// Here we create a temporary directory and populate it with our sample
	// template definition files; usually the template files would already
	// exist in some location known to the program.
	dir := createTestDir([]templateFile{
		// T1.tmpl defines a template, T1 that invokes T2.
		{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
		// T2.tmpl defines a template T2.
		{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
	})
	// Clean up after the test; another quirk of running as an example.
	defer os.RemoveAll(dir)

	// pattern is the glob pattern used to find all the template files.
	pattern := filepath.Join(dir, "*.tmpl")

	// Here starts the example proper.
	// Load the helpers.
	templates := template.Must(template.ParseGlob(pattern))
	// Add one driver template to the bunch; we do this with an explicit template definition.
	_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
	if err != nil {
		log.Fatal("parsing driver1: ", err)
	}
	// Add another driver template.
	_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
	if err != nil {
		log.Fatal("parsing driver2: ", err)
	}
	// We load all the templates before execution. This package does not require
	// that behavior but html/template's escaping does, so it's a good habit.
	err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
	if err != nil {
		log.Fatalf("driver1 execution: %s", err)
	}
	err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
	if err != nil {
		log.Fatalf("driver2 execution: %s", err)
	}
}

Output:

Driver 1 calls T1: (T1 invokes T2: (This is T2))
Driver 2 calls T2: (This is T2)
Example (If)

This example demonstrates how to use "if".

package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	type book struct {
		Stars float32
		Name  string
	}

	tpl, err := template.New("book").Parse(`{{ if (gt .Stars 4.0) }}"{{.Name }}" is a great book.{{ else }}"{{.Name}}" is not a great book.{{ end }}`)
	if err != nil {
		log.Fatalf("failed to parse template: %s", err)
	}

	b := &book{
		Stars: 4.9,
		Name:  "Good Night, Gopher",
	}
	err = tpl.Execute(os.Stdout, b)
	if err != nil {
		log.Fatalf("failed to execute template: %s", err)
	}

}

Output:

"Good Night, Gopher" is a great book.
Example (Share)

This example demonstrates how to use one group of driver templates with distinct sets of helper templates.

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"text/template"
)

// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
	name     string
	contents string
}

func createTestDir(files []templateFile) string {
	dir, err := os.MkdirTemp("", "template")
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		f, err := os.Create(filepath.Join(dir, file.name))
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		_, err = io.WriteString(f, file.contents)
		if err != nil {
			log.Fatal(err)
		}
	}
	return dir
}

func main() {
	// Here we create a temporary directory and populate it with our sample
	// template definition files; usually the template files would already
	// exist in some location known to the program.
	dir := createTestDir([]templateFile{
		// T0.tmpl is a plain template file that just invokes T1.
		{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
		// T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
		{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
	})
	// Clean up after the test; another quirk of running as an example.
	defer os.RemoveAll(dir)

	// pattern is the glob pattern used to find all the template files.
	pattern := filepath.Join(dir, "*.tmpl")

	// Here starts the example proper.
	// Load the drivers.
	drivers := template.Must(template.ParseGlob(pattern))

	// We must define an implementation of the T2 template. First we clone
	// the drivers, then add a definition of T2 to the template name space.

	// 1. Clone the helper set to create a new name space from which to run them.
	first, err := drivers.Clone()
	if err != nil {
		log.Fatal("cloning helpers: ", err)
	}
	// 2. Define T2, version A, and parse it.
	_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
	if err != nil {
		log.Fatal("parsing T2: ", err)
	}

	// Now repeat the whole thing, using a different version of T2.
	// 1. Clone the drivers.
	second, err := drivers.Clone()
	if err != nil {
		log.Fatal("cloning drivers: ", err)
	}
	// 2. Define T2, version B, and parse it.
	_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
	if err != nil {
		log.Fatal("parsing T2: ", err)
	}

	// Execute the templates in the reverse order to verify the
	// first is unaffected by the second.
	err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
	if err != nil {
		log.Fatalf("second execution: %s", err)
	}
	err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
	if err != nil {
		log.Fatalf("first: execution: %s", err)
	}

}

Output:

T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))

func Must

func Must(t *Template, err error) *Template

Must 是一个辅助函数,包装返回 (*Template, error) 的函数调用, 如果错误非 nil 则 panic。它旨在用于变量初始化,例如

var t = template.Must(template.New("name").Parse("text"))

func New

func New(name string) *Template

New 分配一个具有给定名称的新的未定义模板。

func ParseFS

func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)

ParseFS 类似于 Template.ParseFilesTemplate.ParseGlob, 但从文件系统 fsys 读取而不是从主机操作系统读取。 它接受 glob 模式列表(参见 path.Match)。 (请注意,大多数文件名作为匹配仅自身的 glob 模式。)

func ParseFiles

func ParseFiles(filenames ...string) (*Template, error)

ParseFiles 创建一个新的 Template 并从命名文件解析模板定义。 返回的模板的名称将是第一个文件的基本名称和解析内容。 必须至少有一个文件。如果发生错误,解析停止,返回的 *Template 为 nil。

当解析不同目录中具有相同名称的多个文件时, 最后提到的那个将是最终结果。例如, ParseFiles("a/foo", "b/foo") 将 "b/foo" 存储为名为 "foo" 的模板, 而 "a/foo" 不可用。

func ParseGlob

func ParseGlob(pattern string) (*Template, error)

ParseGlob 创建一个新的 Template 并从 pattern 标识的文件解析模板定义。 文件根据 filepath.Match 的语义匹配,pattern 必须至少匹配一个文件。 返回的模板将具有匹配 pattern 的第一个文件的 filepath.Base 名称和(解析的)内容。 ParseGlob 等价于使用 pattern 匹配的文件列表调用 ParseFiles

当解析不同目录中具有相同名称的多个文件时, 最后提到的那个将是最终结果。

func (*Template) AddParseTree

func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)

AddParseTree 将参数解析树与模板 t 关联,给予它指定的名称。 如果模板未被定义,此树成为其定义。如果已被定义且已有该名称, 则替换现有定义;否则创建、定义并返回新模板。

func (*Template) Clone

func (t *Template) Clone() (*Template, error)

Clone 返回模板的副本,包括所有关联的模板。实际表示不被复制, 但关联模板的名称空间被复制,因此在副本中对 Template.Parse 的进一步调用 会将模板添加到副本而不是原始模板。Clone 可用于准备通用模板, 并通过在克隆后添加变体来将它们与其他模板一起使用。

func (*Template) DefinedTemplates

func (t *Template) DefinedTemplates() string

DefinedTemplates 返回一个字符串,列出已定义的模板, 前缀为 "; defined templates are: "。如果没有,则返回空字符串。 用于在此处和 html/template 中生成错误消息。

func (*Template) Delims

func (t *Template) Delims(left, right string) *Template

Delims 将动作分隔符设置为指定的字符串,用于后续调用 Template.ParseTemplate.ParseFilesTemplate.ParseGlob。 嵌套模板定义将继承这些设置。空分隔符表示相应的默认值:{{ 或 }}。 返回值是模板,因此可以链式调用。

func (*Template) Execute

func (t *Template) Execute(wr io.Writer, data any) error

Execute 将解析后的模板应用于指定的数据对象,并将输出写入 wr。 如果执行模板或写入其输出时发生错误,执行停止,但部分结果可能已写入输出 writer。 模板可以安全地并行执行,尽管如果并行执行共享一个 Writer,输出可能会交错。

如果 data 是 reflect.Value,模板应用于 reflect.Value 持有的具体值, 如同 fmt.Print

func (*Template) ExecuteTemplate

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error

ExecuteTemplate 将与 t 关联的具有给定名称的模板应用于指定的数据对象, 并将输出写入 wr。如果执行模板或写入其输出时发生错误, 执行停止,但部分结果可能已写入输出 writer。 模板可以安全地并行执行,尽管如果并行执行共享一个 Writer,输出可能会交错。

func (*Template) Funcs

func (t *Template) Funcs(funcMap FuncMap) *Template

Funcs 将参数映射的元素添加到模板的函数映射中。 必须在解析模板之前调用。如果映射中的值不是具有适当返回类型的函数, 或者名称在语法上不能用作模板中的函数,它会 panic。 覆盖映射中的元素是合法的。返回值是模板,因此可以链式调用。

func (*Template) Lookup

func (t *Template) Lookup(name string) *Template

Lookup 返回与 t 关联的具有给定名称的模板。 如果没有这样的模板或模板没有定义,则返回 nil。

func (*Template) Name

func (t *Template) Name() string

Name 返回模板的名称。

func (*Template) New

func (t *Template) New(name string) *Template

New 分配一个与给定模板关联的新未定义模板,并具有相同的分隔符。 这种关联是传递的,允许一个模板通过 {{template}} 动作调用另一个模板。

因为关联的模板共享底层数据,所以不能安全地并行构建模板。 一旦模板构建完成,它们就可以并行执行。

func (*Template) Option

func (t *Template) Option(opt ...string) *Template

Option 为模板设置选项。选项由字符串描述,可以是简单字符串或 "key=value"。 选项字符串中最多只能有一个等号。如果选项字符串无法识别或无效,Option 会 panic。

已知选项:

missingkey: 控制如果在执行期间用不存在的键索引映射时的行为。

"missingkey=default" 或 "missingkey=invalid"
	默认行为:什么都不做,继续执行。
	如果打印,索引操作的结果是字符串 "<no value>"。
"missingkey=zero"
	操作返回映射类型元素的零值。
"missingkey=error"
	执行立即停止并报错。

func (*Template) Parse

func (t *Template) Parse(text string) (*Template, error)

Parse 将 text 解析为模板主体 for t。 text 中的命名模板定义({{define ...}} 或 {{block ...}} 语句) 定义了与 t 关联的附加模板,并从 t 自身的定义中移除。

可以在 successive 调用 Parse 中重新定义模板。 主体仅包含空白和注释的模板定义被视为空,不会替换现有模板的主体。 这允许使用 Parse 添加新的命名模板定义而不覆盖主模板主体。

func (*Template) ParseFS

func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error)

ParseFS 类似于 Template.ParseFilesTemplate.ParseGlob, 但从文件系统 fsys 读取而不是从主机操作系统读取。 它接受 glob 模式列表(参见 path.Match)。 (请注意,大多数文件名作为匹配仅自身的 glob 模式。)

func (*Template) ParseFiles

func (t *Template) ParseFiles(filenames ...string) (*Template, error)

ParseFiles 解析命名文件并将结果模板与 t 关联。 如果发生错误,解析停止,返回的模板为 nil;否则为 t。 必须至少有一个文件。由于 ParseFiles 创建的模板由参数文件的基本名称 (参见 filepath.Base)命名,t 通常应该具有文件的基本名称之一。 如果不是,根据调用 ParseFiles 前 t 的内容,t.Execute 可能失败。 在这种情况下,使用 t.ExecuteTemplate 执行有效模板。

当解析不同目录中具有相同名称的多个文件时, 最后提到的那个将是最终结果。

func (*Template) ParseGlob

func (t *Template) ParseGlob(pattern string) (*Template, error)

ParseGlob 解析由 pattern 标识的文件中的模板定义,并将结果模板与 t 关联。 文件根据 filepath.Match 的语义匹配,pattern 必须至少匹配一个文件。 ParseGlob 等价于使用 pattern 匹配的文件列表调用 Template.ParseFiles

当解析不同目录中具有相同名称的多个文件时, 最后提到的那个将是最终结果。

func (*Template) Templates

func (t *Template) Templates() []*Template

Templates 返回与 t 关联的已定义模板的切片。

Directories

parse Package parse 为 text/template 和 html/template 定义的模板构建解析树。