我的编程空间,编程开发者的网络收藏夹
学习永远不晚

深入解析Gotemplate模板使用详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

深入解析Gotemplate模板使用详解

本文只关注Go text/template的底层结构,带上了很详细的图片以及示例帮助理解,有些地方也附带上了源码进行解释。有了本文的解释,对于Go template的语法以及html/template的用法,一切都很简单。

关于template的语法以及具体使用方法,见:Go template用法详解

入门示例

package main

import (
	"html/template"
	"os"
)

type Person struct {
	Name string
	Age    int
}

func main() {
	p := Person{"longshuai", 23}
	tmpl, err := template.New("test").Parse("Name: {{.Name}}, Age: {{.Age}}")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, p)
	if err != nil {
		panic(err)
	}
	fmt.Println(tmpl)
}

上面定义了一个Person结构,有两个大写字母开头(意味着这俩字段是导出的)的字段Name和Age。然后main()中创建了Person的实例对象p。

紧接着使用template.New()函数创建了一个空Template实例(对象),然后通过这个template实例调用Parse()方法,Parse()方法用来解析、评估模板中需要执行的action,其中需要评估的部分都使用{{}}包围,并将评估后(解析后)的结果赋值给tmpl。

最后调用Execute()方法,该方法将数据对象Person的实例p应用到已经解析的tmpl模板,最后将整个应用合并后的结果输出到os.Stdout。

上面的示例很简单,两个注意点:

  • 流程:构建模板对象New()-->解析数据Parse()-->应用合并Execute()
  • Parse()解析的对象中包含了{{}},其中使用了点(.),{{.Name}}代表Execute()第二个参数p对象的Name字段,同理{{.Age}}

也就是说,{{.}}代表的是要应用的对象,类似于java/c++中的this,python/perl中的self。

更通用地,{{.}}表示的是所处作用域的当前对象,而不仅仅只代表Execute()中的第二个参数对象。例如,本示例中{{.}}代表顶级作用域的对象p,如果Parse()中还有嵌套的作用域range,则{{.}}代表range迭代到的每个元素对象。如果了解perl语言,{{.}}可以理解为默认变量$_

模板关联(associate)

template中有不少函数、方法都直接返回*Template类型。

上图中使用红色框线框起来一部分返回值是*Template的函数、方法。对于函数,它们返回一个Template实例(假设为t),对于使用t作为参数的Must()函数和那些框起来的Template方法,它们返回的*Template其实是原始实例t

例如:

t := template.New("abc")
tt,err := t.Parse("xxxxxxxxxxx")

这里的t和tt其实都指向同一个模板对象。

这里的t称为模板的关联名称。通俗一点,就是创建了一个模板,关联到变量t上。但注意,t不是模板的名称,因为Template中有一个未导出的name字段,它才是模板的名称。可以通过Name()方法返回name字段的值,而且仔细观察上面的函数、方法,有些是以name作为参数的。

之所以要区分模板的关联名称(t)和模板的名称(name),是因为一个关联名称t(即模板对象)上可以"包含"多个name,也就是多个模板,通过t和各自的name,可以调用到指定的模板

模板结构详解

首先看Template结构:

type Template struct {
	name string
	*parse.Tree
	*common
	leftDelim  string
	rightDelim string
}

name是这个Template的名称,Tree是解析树,common是另一个结构,稍后解释。leftDelim和rightDelim是左右两边的分隔符,默认为{{}}

这里主要关注name和common两个字段,name字段没什么解释的。common是一个结构:

type common struct {
	tmpl   map[string]*Template // Map from name to defined templates.
	option option
	muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
	parseFuncs FuncMap
	execFuncs  map[string]reflect.Value
}

这个结构的第一个字段tmpl是一个Template的map结构,key为template的name,value为Template。也就是说,一个common结构中可以包含多个Template,而Template结构中又指向了一个common结构。所以,common是一个模板组,在这个模板组中的(tmpl字段)所有Template都共享一个common(模板组),模板组中包含parseFuncs和execFuncs。

大概结构如下图:

除了需要关注的name和common,parseFuncs和execFuncs这两个字段也需要了解下,它们共同成为模板的FuncMap。

New()函数和init()方法

使用template.New()函数可以创建一个空的、无解析数据的模板,同时还会创建一个common,也就是模板组

func New(name string) *Template {
	t := &Template{
		name: name,
	}
	t.init()
	return t
}

其中t为模板的关联名称,name为模板的名称,t.init()表示如果模板对象t还没有common结构,就构造一个新的common组:

func (t *Template) init() {
	if t.common == nil {
		c := new(common)
		c.tmpl = make(map[string]*Template)
		c.parseFuncs = make(FuncMap)
		c.execFuncs = make(map[string]reflect.Value)
		t.common = c
	}
}

也就是说,template.New()函数不仅创建了一个模板,还创建了一个空的common结构(模板组)。需要注意,新创建的common是空的,只有进行模板解析(Parse(),ParseFiles()等操作)之后,才会将模板添加到common的tmpl字段(map结构)中

所以,下面的代码:

tmpl := template.New("mytmpl1")

执行完后将生成如下结构,其中tmpl为模板关联名称,mytmpl1为模板名称。

因为还没有进行解析操作,所以上图使用虚线表示尚不存在的部分。

实际上,在template包中,很多涉及到操作Template的函数、方法,都会调用init()方法保证返回的Template都有一个有效的common结构。当然,因为init()方法中进行了判断,对于已存在common的模板,不会新建common结构。

假设现在执行了Parse()方法,将会把模板name添加到common tmpl字段的map结构中,其中模板name为map的key,模板为map的value。

例如:

func main() {
	t1 := template.New("test1")
	tmpl,_ := t1.Parse(
			`{{define "T1"}}ONE{{end}}
			{{define "T2"}}TWO{{end}}
			{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
			{{template "T3"}}`)
	fmt.Println(t1)
	fmt.Println(tmpl)
	fmt.Println(t1.Lookup("test1"))  // 使用关联名称t1检索test1模板
	fmt.Println(t1.Lookup("T1"))
	fmt.Println(tmpl.Lookup("T2")) // 使用关联名称tmpl检索T2模板
	fmt.Println(tmpl.Lookup("T3"))
}

上述代码的执行结果:注意前3行的结果完全一致,所有行的第二个地址完全相同。

&{test1 0xc0420a6000 0xc0420640c0  }
&{test1 0xc0420a6000 0xc0420640c0  }
&{test1 0xc0420a6000 0xc0420640c0  }
&{T1 0xc0420a6100 0xc0420640c0  }
&{T2 0xc0420a6200 0xc0420640c0  }
&{T3 0xc0420a6300 0xc0420640c0  }

首先使用template.New()函数创建了一个名为test1的模板,同时创建了一个模板组(common),它们关联在t1变量上。

然后调用Parse()方法,在Parse()的待解析字符串中使用define又定义了3个新的模板对象,模板的name分别为T1、T2和T3,其中T1和T2嵌套在T3中,因为调用的是t1的Parse(),所以这3个新创建的模板都会关联到t1上。

也就是说,现在t1上关联了4个模板:test1、T1、T2、T3,它们全都共享同一个common。因为已经执行了Parse()解析操作,这个Parse()会将test1、T1、T2、T3的name添加到common.tmpl的map中。也就是说,common的tmpl字段的map结构中有4个元素。

结构如下图:

必须注意,虽然test1、T1、T2、T3都关联在t1上,但t1只能代表test1(所以上图中只有test1下面标注了t1),因为t1是一个Template类型。可以认为test1、T1、T2、T3这4个模板共享一个组,但T1、T2、T3都是对外部不可见的,只能通过特殊方法的查询找到它们。

另外,前文说过,template包中很多返回*Template的函数、方法返回的其实是原始的t(看源代码即可知道),这个规则也适用于这里的Parse()方法,所以tmpl和t1这两个变量是完全等价的,都指向同一个template,即test1。所以前面的执行结果中前3行完全一致。

再回头看上面代码的执行结果,假设结果中的每一行都分为3列,第一列为template name,第二个字段为parseTree的地址,第三列为common结构的地址。因为tmpl1、t1都指向test1模板,所以前3行结果完全一致。因为test1、T1、T2、T3共享同一个common,所以第三列全都相同。因为每个模板的解析树不一样,所以第二列全都不一样。

New()方法

除了template.New()函数,还有一个Template.New()方法:

// New allocates a new, undefined template associated with the given one and with the same
// delimiters. The association, which is transitive, allows one template to
// invoke another with a {{template}} action.
func (t *Template) New(name string) *Template {
	t.init()
	nt := &Template{
		name:       name,
		common:     t.common,
		leftDelim:  t.leftDelim,
		rightDelim: t.rightDelim,
	}
	return nt
}

看注释很难理解,但是看它的代码,结合前文的解释,New()方法的作用很明显。

首先t.init()保证有一个有效的common结构,然后构造一个新的Template对象nt,这个nt除了name和解析树parse.Tree字段之外,其它所有内容都和t完全一致。换句话说,nt和t共享了common。

也就是说,New()方法使得名为name的nt模板对象加入到了关联组中。更通俗一点,通过调用t.New()方法,可以创建一个新的名为name的模板对象,并将此对象加入到t模板组中

这和New()函数的作用基本是一致的,只不过New()函数是构建新的模板对象并构建一个新的common结构,而New()方法则是构建一个新的模板对象,并加入到已有的common结构中。

只是还是要说明,因为New()出来的新对象在执行解析之前(如Parse()),它们暂时都还不会加入到common组中,在New()出来之后,仅仅只是让它指向已有的一个common结构。

所以:

t1 := template.New("test1")
t1 = t1.Parse(...)
t2 := t1.New("test2")
t2 = t2.Parse(...)
t3 := t1.New("test3")

结构图:

如果t1和t2的Parse()中,都定义一个或多个name相同的模板会如何?例如:

t1 := template.New("test1")
t2 := t1.New("test2")
t1, _ = t1.Parse(
	`{{define "T1"}}ONE{{end}}
	{{define "T2"}}TWO{{end}}
	{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
	{{template "T3"}}`)
t2, _ = t2.Parse(
	`{{define "T4"}}ONE{{end}}
	{{define "T2"}}TWOO{{end}}
	{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
	{{template "T3"}}`)

	_ = t1.Execute(os.Stdout, "a")
	_ = t2.Execute(os.Stdout, "a")

在上面的t1和t2中,它们共享同一个common,且t1.Parse()中定义了T1、T2和T3,t2.Parse()中定义了T4、T2和T3,且两个T2的解析内容不一样(解析树不一样)。

因为T1、T2、T3、T4都会加入到t1和t2共享的common中,所以无论是通过t1还是通过t2这两个关联名称都能找到T1、T2、T3、T4。但是后解析的会覆盖先解析的,也就是说,无论是t1.Lookup("T2")还是t2.Lookup("T2")得到的T2对应的template,都是在t2.Parse()中定义的。当t1.Execute()的时候,会得到t2中定义的T2的值。

ONE TWOO
ONE TWOO

Parse()

Parse(string)方法用于解析给定的文本内容string。用法上很简单,前面也已经用过几次了,没什么可解释的。重点在于它的作用。

当创建了一个模板对象后,会有一个与之关联的common(如果不存在,template包中的各种函数、方法都会因为调用init()方法而保证common的存在)。只有在Parse()之后,才会将相关的template name放进common中,表示这个模板已经可用了,或者称为已经定义了(defined),可用被Execute()或ExecuteTemplate(),也表示可用使用Lookup()和DefinedTemplates()来检索模板。另外,调用了Parse()解析后,会将给定的FuncMap中的函数添加到common的FuncMap中,只有添加到common的函数,才可以在模板中使用。

Parse()方法是解析字符串的,且只解析New()出来的模板对象。如果想要解析文件中的内容,见后文ParseFiles()、ParseGlob()。

Lookup()、DefinedTemplates()和Templates()方法

这三个方法都用于检索已经定义的模板,Lookup()根据template name来检索并返回对应的template,DefinedTemplates()则是返回所有已定义的templates。Templates()和DefinedTemplates()类似,但是它返回的是[]*Template,也就是已定义的template的slice。

前面多次说过,只有在解析之后,模板才加入到common结构中,才算是已经定义,才能被检索或执行。

当检索不存在的templates时,Lookup()将返回nil。当common中没有模板,DefinedTemplates()将返回空字符串"",Templates()将返回空的slice。

func main() {
	t1 := template.New("test1")
	t2 := t1.New("test2")
	t1, _ = t1.Parse(
		`{{define "T1"}}ONE{{end}}
		{{define "T2"}}TWO{{end}}
		{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)
	t2, _ = t2.Parse(
		`{{define "T4"}}ONE{{end}}
		{{define "T2"}}TWOO{{end}}
		{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)

	fmt.Println(t1.DefinedTemplates())
	fmt.Println(t2.DefinedTemplates())
	fmt.Println(t2.Templates())
}

返回结果:

; defined templates are: "T1", "T2", "T3", "test1", "T4", "test2"
; defined templates are: "test1", "T4", "test2", "T1", "T2", "T3"
[0xc04201c280 0xc042064100 0xc04201c1c0 0xc04201c2c0 0xc04201c300 0xc042064080]

从结果可见,返回的顺序虽然不一致,但包含的template name是完全一致的。

Clone()方法

Clone()方法用于克隆一个完全一样的模板,包括common结构也会完全克隆

t1 := template.New("test1")
t1 = t1.Parse(...)
t2 := t1.New("test2")
t2 = t2.Parse(...)

t3, err := t1.Clone()
if err != nil {
	panic(err)
}

这里的t3和t1在内容上完全一致,但在内存中它们是两个不同的对象。但无论如何,目前t3中会包含t1和t2共享的common,即使t2中定义了{{define "Tx"}}...{{end}},这个Tx也会包含在t3中。

因为是不同的对象,所以修改t3,不会影响t1/t2。

看下面的例子:

func main() {
	t1 := template.New("test1")
	t2 := t1.New("test2")
	t1, _ = t1.Parse(
		`{{define "T1"}}ONE{{end}}
		{{define "T2"}}TWO{{end}}
		{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)
	t2, _ = t2.Parse(
		`{{define "T4"}}ONE{{end}}
		{{define "T2"}}TWOO{{end}}
		{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)

	t3, err := t1.Clone()
	if err != nil {
		panic(err)
	}

    // 结果完全一致
	fmt.Println(t1.Lookup("T4"))
	fmt.Println(t3.Lookup("T4"))
    
    // 修改t3
	t3,_ = t3.Parse(`{{define "T4"}}one{{end}}`)
    // 结果将不一致
	fmt.Println(t1.Lookup("T4"))
	fmt.Println(t3.Lookup("T4"))
}

Must()函数

正常情况下,很多函数、方法都返回两个值,一个是想要返回的值,一个是err信息。template包中的函数、方法也一样如此。

但有时候不想要err信息,而是直接取第一个返回值,并赋值给变量。操作大概是这样的:

t1 := template.New("ttt")
t1,err := t1.Parse(...)
if err != nil {
    panic(err)
}
...

Must()函数将上面的过程封装了,使得Must()可以简化上面的操作:

func Must(t *Template, err error) *Template {
	if err != nil {
		panic(err)
	}
	return t
}

当某个返回*Template,err的函数、方法需要直接使用时,可用将其包装在Must()中,它会自动在有err的时候panic,无错的时候只返回其中的*Template

这在赋值给变量的时候非常简便,例如:

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

ParseFiles()和ParseGlob()

Parse()只能解析字符串,要解析文件中的内容,需要使用ParseFiles()或ParseGlob()。

template包中有ParseFiles()和ParseGlob()函数,也有ParseFiles()和ParseGlob()方法。

这两个函数和这两个方法的区别,看一下文档就很清晰:

$ go doc template.ParseFiles
func ParseFiles(filenames ...string) (*Template, error)
    ParseFiles creates a new Template and parses the template definitions from
    the named files. The returned template's name will have the (base) name and
    (parsed) contents of the first file. There must be at least one file. If an
    error occurs, parsing stops and the returned *Template is nil.

$ go doc template.template.ParseFiles
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
    ParseFiles parses the named files and associates the resulting templates
    with t. If an error occurs, parsing stops and the returned template is nil;
    otherwise it is t. There must be at least one file.

解释很清晰。ParseFiles()函数是直接解析一个或多个文件的内容,并返回第一个文件名的basename作为Template的名称,也就是说这些文件的template全都关联到第一个文件的basename上。ParseFiles()方法则是解析一个或多个文件的内容,并将这些内容关联到t上。

看示例就一目了然。

例如,当前go程序的目录下有3个文件:a.cnf、b.cnf和c.cnf,它们的内容无所谓,反正空内容也可以解析。

func main() {
	t1,err := template.ParseFiles("a.cnf","b.cnf","c.cnf")
	if err != nil {
		panic(err)
	}
	fmt.Println(t1.DefinedTemplates())
	fmt.Println()
	fmt.Println(t1)
	fmt.Println(t1.Lookup("a.cnf"))
	fmt.Println(t1.Lookup("b.cnf"))
	fmt.Println(t1.Lookup("c.cnf"))
}

输出结果:

; defined templates are: "a.cnf", "b.cnf", "c.cnf"

&{a.cnf 0xc0420ae000 0xc042064140  }
&{a.cnf 0xc0420ae000 0xc042064140  }
&{b.cnf 0xc0420bc000 0xc042064140  }
&{c.cnf 0xc0420bc100 0xc042064140  }

从结果中可以看到,已定义的template name都是文件的basename,且t1和a.cnf这个template是完全一致的,即t1是文件列表中的第一个模板对象。

结构如下图:

理解了ParseFiles()函数,理解ParseFiles()方法、ParseGlob()函数、ParseGlob()方法,应该不会再有什么问题。但是还是有需要注意的地方:

func main() {
	t1 := template.New("test")
	t1,err := t1.ParseFiles("a.cnf","b.cnf","c.cnf")
	if err != nil {
		panic(err)
	}
    // 先注释下面这行
	//t1.Parse("")
	fmt.Println(t1.DefinedTemplates())
	fmt.Println()
	fmt.Println(t1)
	fmt.Println(t1.Lookup("a.cnf"))
	fmt.Println(t1.Lookup("b.cnf"))
	fmt.Println(t1.Lookup("c.cnf"))
}

执行结果:

; defined templates are: "a.cnf", "b.cnf", "c.cnf"

&{test <nil> 0xc0420640c0  }
&{a.cnf 0xc0420b0000 0xc0420640c0  }
&{b.cnf 0xc0420be000 0xc0420640c0  }
&{c.cnf 0xc0420be100 0xc0420640c0  }

发现template.New()函数创建的模板对象test并没有包含到common中。为什么?

因为t.ParseFiles()、t.ParseGlob()方法的解析过程是独立于t之外的,它们只解析文件内容,不解析字符串。而New()出来的模板,需要Parse()方法来解析才会加入到common中。

将上面的注释行取消掉,执行结果将如下:

; defined templates are: "a.cnf", "b.cnf", "c.cnf", "test"

&{test 0xc0420bc200 0xc0420640c0  }
&{a.cnf 0xc0420ae000 0xc0420640c0  }
&{b.cnf 0xc0420bc000 0xc0420640c0  }
&{c.cnf 0xc0420bc100 0xc0420640c0  }

具体原因可分析parseFiles()源码:

func parseFiles(t *Template, filenames ...string) (*Template, error) {
	if len(filenames) == 0 {
		// Not really a problem, but be consistent.
		return nil, fmt.Errorf("template: no files named in call to ParseFiles")
	}
	for _, filename := range filenames {
		b, err := ioutil.ReadFile(filename)
		if err != nil {
			return nil, err
		}
		s := string(b)

		// name为文件名的basename部分
		name := filepath.Base(filename)

		var tmpl *Template
		if t == nil {
			t = New(name)
		}
		// 如果调用t.Parsefiles(),则t.Name不为空
		// name也就不等于t.Name
		// 于是新New(name)一个模板对象给tmpl
		if name == t.Name() {
			tmpl = t
		} else {
			tmpl = t.New(name)
		}
		// 解析tmpl。如果选中了上面的else分支,则和t无关
		_, err = tmpl.Parse(s)
		if err != nil {
			return nil, err
		}
	}
	return t, nil
}

Execute()和ExecuteTemplate()

这两个方法都可以用来应用已经解析好的模板,应用表示对需要评估的数据进行操作,并和无需评估数据进行合并,然后输出到io.Writer中:

func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

两者的区别在于Execute()是应用整个common中已定义的模板对象,而ExecuteTemplate()可以选择common中某个已定义的模板进行应用。

例如:

func main() {
	t1 := template.New("test1")
	t1, _ = t1.Parse(`{{define "T1"}}ONE{{end}}
		{{- define "T2"}}TWO{{end}}
		{{- define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{- template "T3"}}`)
	
	_ = t1.Execute(os.Stdout,"")
	fmt.Println()
	fmt.Println("-------------")
	_ = t1.ExecuteTemplate(os.Stdout, "T2", "")
}

输出结果:

ONE TWO
-------------
TWO

FuncMap和Funcs()

template内置了一系列函数,但这些函数毕竟有限,可能无法满足特殊的需求。template允许我们定义自己的函数,添加到common中,然后就可以在待解析的内容中像使用内置函数一样使用自定义的函数。

自定义函数的优先级高于内置的函数优先级,即先检索自定义函数,再检索内置函数。也就是说,如果自定义函数的函数名和内置函数名相同,则内置函数将失效。

本文只对此稍作解释,本文的重点不是template的具体语法和用法。

在common结构中,有一个字段是FuncMap类型的:

type common struct {
	tmpl   map[string]*Template
	option option
	muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
	parseFuncs FuncMap
	execFuncs  map[string]reflect.Value
}

这个类型的定义为:

type FuncMap map[string]interface{}

它是一个map结构,key为模板中可以使用的函数名,value为函数对象(为了方便称呼,这里直接成为函数)。函数必须只有1个值或2个值,如果有两个值,第二个值必须是error类型的,当执行函数时err不为空,则执行自动停止。

函数可以有多个参数。假如函数str有两个参数,在待解析的内容中调用函数str时,如果调用方式为{{str . "aaa"}},表示第一个参数为当前对象,第二个参数为字符串"aaa"。

假如,要定义一个将字符串转换为大写的函数,可以:

import "strings"
func upper(str string) string {
	return strings.ToUpper(str)
}

然后将其添加到FuncMap结构中,并将此函数命名为"strupper",以后在待解析的内容中就可以调用"strupper"函数。

funcMap := template.FuncMap{
	"strupper": upper,
}

或者,直接将匿名函数放在FuncMap内部:

funcMap := template.FuncMap{
	"strupper": func(str string) string { return strings.ToUpper(str) },
}

现在只是定义了一个FuncMap实例,这个实例中有一个函数。还没有将它关联到模板,严格地说还没有将其放进common结构。要将其放进common结构,调用Funcs()方法(其实调用此方法也没有将其放进common,只有在解析的时候才会放进common):

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

例如:

funcMap := template.FuncMap{
	"strupper": func(str string) string { return strings.ToUpper(str) },
}
t1 := template.New("test")
t1 = t1.Funcs(funcMap)

这样,和t1共享common的所有模板都可以调用"strupper"函数。

注意,必须在解析之前调用Funcs()方法,在解析的时候会将函数放进common结构。

下面是完整的示例代码:

package main

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

func main() {
	funcMap := template.FuncMap{
		"strupper": upper,
	}
	t1 := template.New("test1")
	tmpl, err := t1.Funcs(funcMap).Parse(`{{strupper .}}`)
	if err != nil {
		panic(err)
	}
	_ = tmpl.Execute(os.Stdout, "go programming")
}

func upper(str string) string {
	return strings.ToUpper(str)
}

上面调用了{{strupper .}},这里的strupper是我们自定义的函数,"."是它的参数(注意,参数不是放进括号里)。这里的"."代表当前作用域内的当前对象,对于这个示例来说,当前对象就是那段字符串对象"go programming"。

 

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

深入解析Gotemplate模板使用详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

攻克 VUE 模板语法难关:深入解析

Vue 模板语法是 Vue.js 框架的基础,掌握它对于有效构建应用程序至关重要。本文深入解析 Vue 模板语法,从基本语法到高级特性,提供示例和技巧,帮助开发者攻克难关。
攻克 VUE 模板语法难关:深入解析
2024-03-04

C++模板编程的深入理解

什么是模板编程?模板编程是使用类型参数泛型化算法和数据结构,并生成可在编译时处理多种数据类型的代码。关键概念:模板函数和类:使用 template 关键字创建,接受类型参数,处理不同类型的输入。示例:排序数组函数可以泛型化为处理任何类型的数
C++模板编程的深入理解
2024-05-21

springboot中thymeleaf模板使用详解

这篇文章将更加全面详细的介绍thymeleaf的使用。thymeleaf 是新一代的模板引擎,在spring4.0中推荐使用thymeleaf来做前端模版引擎。thymeleaf介绍简单说, Thymeleaf 是一个跟 Velocity、
2023-05-31

C++标准模板库STL深入讲解

STL提供了一组表示容器、迭代器、函数对象和算法的模板。容器是一个与数组类似的单元,可以存储若干个值。STL容器是同质的,即存储的值的类型相同:算法是完成特定任务(如对数组进行排序或在链表中查找特定值)的处方
2022-12-26

深入解析Python编程中JSON模块的使用

JSON编码支持的基本数据类型为 None , bool , int , float 和 str , 以及包含这些类型数据的lists,tuples和dictionaries。 对于dictionaries,keys需要是字符串类型(字典中
2022-06-04

C++模板index_sequence使用示例详解

这篇文章主要为大家介绍了C++模板index_sequence使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-08

RxJava2Scheduler使用实例深入解析

这篇文章主要为大家介绍了RxJava2Scheduler使用实例深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

深入解析Python的Tornado框架中内置的模板引擎

template中的_parse方法是模板文法的解析器,而这个文件中一坨一坨的各种node以及block,就是解析结果的承载者,也就是说在经过parse处理过后,我们输入的tornado的html模板就变成了各种block的集合。 这些bl
2022-06-04

C++印刷模板使用方法详解

模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型
2022-11-13

GoJs面板绘图模板go.Panel使用示例详解

这篇文章主要为大家介绍了GoJs面板绘图模板go.Panel使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录