【Golang】text/template使用

这一篇将使用及测试template包

创建模板

首先是创建模板的办法:

  1. func New(name string) *Template 创建一个名为name的模板。

  2. func ParseFiles(filenames ...string) (*Template, error) ParseFiles函数创建一个模板并解析filenames指定的文件里的模板定义。返回的模板的名字是第一个文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要提供一个文件。如果发生错误,会停止解析并返回nil。

  3. func ParseGlob(pattern string) (*Template, error) ParseGlob创建一个模板并解析匹配pattern的文件(参见glob规则)里的模板定义。返回的模板的名字是第一个匹配的文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要存在一个匹配的文件。如果发生错误,会停止解析并返回nil。ParseGlob等价于使用匹配pattern的文件的列表为参数调用ParseFiles。

  4. func Must(t *Template, err error) *Template Must函数用于包装返回(*Template, error)的函数/方法调用,它会在err非nil时panic,一般用于变量初始化: var t = template.Must(template.New("name").Parse("text"))

func New(name string) *Template

	t := template.New("test")
	fmt.Printf("%#v",t.Name()) // "test"

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

准备一个文件:test.tpl

我叫:{{.name}},性别:{{if .isMan}}男{{else}}女{{end}}
	t2, err := template.ParseFiles("src/tmp/test.tpl")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(t2.Name())  // test.tpl
	}

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

	t3, err := template.ParseGlob("src/tmp/*.tpl")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%v", t3.Name()) // test.tpl
	}

渲染模板

  1. func (t *Template) Execute(wr io.Writer, data interface{}) (err error) Execute方法将解析好的模板应用到data上,并将输出写入wr。如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。模板可以安全的并发执行。

  2. func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error ExecuteTemplate方法类似Execute,但是使用名为name的t关联的模板产生输出。

试一试:

	t1 := template.New("test")
	t1, err := t1.Parse(`我叫:{{.Name}},性别:{{if .IsMan}}男{{else}}女{{end}}`)
	if err != nil {
		fmt.Println(err)
	}

	type human struct {
		name  string
		isMan bool
	}
	h := &human{name: "程序幼儿员", isMan: true}

	err = t1.Execute(os.Stdout, h)
	if err != nil {
		fmt.Println(err)
	}

运行的时候出错了:template: test:1:11: executing "test" at <.Name>: can't evaluate field Name in type *tmp.human

那么阅读文档中所说的 虽然键也必须是字母和数字构成的标识字符串,但不需要以大写字母起始 就不是说可以使用非公开成员了,那么为什么说不需要以大写字母起始呢?不明白。希望有人可以解答。这里还是按照严格的大小写来吧:

	t1 := template.New("test")
	t1, err := t1.Parse(`我叫:{{.Name}},性别:{{if .IsMan}}男{{else}}女{{end}}`)
	if err != nil {
		fmt.Println(err)
	}

	type human struct {
		Name  string
		IsMan bool
	}
	h := &human{Name: "程序幼儿员", IsMan: true}

	err = t1.Execute(os.Stdout, h)
	fmt.Println()
	err = t1.ExecuteTemplate(os.Stdout, "test", h)
我叫:程序幼儿员,性别:男
我叫:程序幼儿员,性别:男

嵌套模板

	t1 := template.New("test")
	t1, err := t1.Parse(`{{template "title"}}我叫:{{.Name}},性别:{{if .IsMan}}男{{else}}女{{end}}{{template "e"}}`)
	if err != nil {
		fmt.Println(0, err)
	}
	_, err = t1.Parse(`{{define "title"}}我是title:{{end}}`)
	_, err = t1.Parse(`{{define "e"}}!!!{{end}}`)
	if err != nil {
		fmt.Println(1, err)
	}
	type human struct {
		Name  string
		IsMan bool
	}
	h := &human{Name: "程序幼儿员", IsMan: true}

	err = t1.Execute(os.Stdout, h)
	if err != nil {
		fmt.Println(2, err)
	}
	fmt.Println()
	err = t1.ExecuteTemplate(os.Stdout, "e", h)
	if err != nil {
		fmt.Println(3, err)
	}
我是title:我叫:程序幼儿员,性别:男!!!
!!!

模板复制

因为返回的是一个指针,所以直接传值是不行的,需要使用clone()方法

	t1 := template.New("test")
	t1, err := t1.Parse(`{{template "title"}}我叫:{{.Name}},性别:{{if .IsMan}}男{{else}}女{{end}}{{template "e"}}`)
	t2 := t1
	if err != nil {
		fmt.Println(0, err)
	}
	_, err = t1.Parse(`{{define "title"}}我是title:{{end}}`)
	_, err = t1.Parse(`{{define "e"}}!!!{{end}}`)
	t3, err := t1.Lookup("e").Clone()
	if err != nil {
		fmt.Println(1, err)
	}
	type human struct {
		Name  string
		IsMan bool
	}
	h := &human{Name: "程序幼儿员", IsMan: true}

	err = t1.Execute(os.Stdout, h)
	if err != nil {
		fmt.Println(2, err)
	}
	fmt.Println()
	err = t2.Execute(os.Stdout, h)
	if err != nil {
		fmt.Println(2, err)
	}
	fmt.Println()
	err = t3.Execute(os.Stdout, h)
	if err != nil {
		fmt.Println(2, err)
	}
我是title:我叫:程序幼儿员,性别:男!!!
我是title:我叫:程序幼儿员,性别:男!!!
!!!

注入函数

测试了一下注入函数和解析模板是有先后顺序的,在注入函数前解析模板会报错:

先解析模板(错误)

	t1 := template.New("test")
	f := template.FuncMap{
		"dump": func() string { return "dump now!" },
	}
	t1, err := t1.Parse(`{{dump}}`)
	if err != nil {
		fmt.Println(0, err)
	}
	t1.Funcs(f)
	t1.Execute(os.Stdout, nil)
0 template: test:1: function "dump" not defined

先注入函数(正确)

	t1 := template.New("test")
	f := template.FuncMap{
		"dump": func() string { return "dump now!" },
	}
	t1.Funcs(f)
	t1, err := t1.Parse(`{{dump}}`)
	if err != nil {
		fmt.Println(0, err)
	}
	t1.Execute(os.Stdout, nil)
dump now!

嵌套顺序是否也有先后

虽然函数注入有先后,但是测试了一下嵌套的顺序似乎没有先后:

	t1 := template.New("test")
	f := template.FuncMap{
		"dump": func() string { return "dump now!" },
	}
	t1.Funcs(f)
	_, err := t1.Parse(`{{define "dump"}}{{dump}}{{end}}`)
	_, err = t1.Parse(`{{template "dump"}}{{template "dump1"}}`)
	_, err = t1.Parse(`{{define "dump1"}}  [dddd]   {{end}}`)
	if err != nil {
		fmt.Println(0, err)
	}
	t1.Execute(os.Stdout, nil)
dump now!  [dddd]

模板语法使用

模板的语法使用就不写了,大家应该都不是第一次使用模板了,大同小异,就不浪费时间了=-=没写过的话可以自己上手试试

关于html/template

html/tamplate包也不想再另外写一篇了,因为这个包是基于text/template包封装的对html更安全的包,使用方法是大致类似的,只是多了几个数据类型。 html/template对特殊字符是会自动转义的,举个手册的demo:

text/template

import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

输出

Hello, <script>alert('you have been pwned')</script>!

html/template

import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

输出

Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!

可以看到在html/template包中自动对<>进行了转义,详情可以查看手册

假设{{.}}是O'Reilly: How are <i>you</i>?,下表展示了{{.}}用于左侧模板时的输出:

Context                          {{.}} After
{{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'>                O&#39;Reilly: How are you?
<a href="/{{.}}">                O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}">              O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'>             O\x27Reilly: How are \x3ci\x3eyou...?
<a onx='f({{.}})'>               "O\x27Reilly: How are \x3ci\x3eyou...?"
<a onx='pattern = /{{.}}/;'>     O\x27Reilly: How are \x3ci\x3eyou...\x3f

如果用在不安全的上下文里,值就可能被过滤掉:

Context                          {{.}} After
<a href="{{.}}">                 #ZgotmplZ

因为"O'Reilly:"不是一个可以接受的协议名,如"http:"。

如果{{.}}是一个无害的词汇,如left,那么它就可以出现在更多地方。

Context                              {{.}} After
{{.}}                                left
<a title='{{.}}'>                    left
<a href='{{.}}'>                     left
<a href='/{{.}}'>                    left
<a href='?dir={{.}}'>                left
<a style="border-{{.}}: 4px">        left
<a style="align: {{.}}">             left
<a style="background: '{{.}}'>       left
<a style="background: url('{{.}}')>  left
<style>p.{{.}} {color:red}</style>   left

如果{{.}}是非字符串类型的值,可以用于JavaScript上下文环境里:

struct{A,B string}{ "foo", "bar" }

将该值应用在在转义后的模板里:

|<script>var pair = {{.}};</script>

模板输出为:

|<script>var pair = {"A": "foo", "B": "bar"};</script>

用来标注html安全的数据类型

先用官网的demo举例:

模板:

Hello, {{.}}!

可以采用如下调用:

tmpl.Execute(out, HTML(`<b>World</b>`))

来输出:

Hello, <b>World</b>!

而不是:

Hello, &lt;b&gt;World&lt;b&gt;!

可以看到想要在html/template中原样输出一些敏感的符号需要用到它的包内内置的几个数据类型:

type HTML

type HTML string

HTML用于封装一个已知安全的HTML文档片段。它不应被第三方使用,也不能用于含有未闭合的标签或注释的HTML文本。该类型适用于封装一个效果良好的HTML生成器生成的HTML文本或者本包模板的输出的文本。

type HTMLAttr

type HTMLAttr string

HTMLAttr用来封装一个来源可信的HTML属性,如 dir="ltr"

type JS

type JS string

JS用于封装一个已知安全的EcmaScript5表达式,如(x + y * z())。模板作者有责任确保封装的字符串不会破坏原有的语义,也不能包含有歧义的声明或表达式,如"{ foo: bar() }\n'foo'",这一句既是合法的表达式也是语义完全不同的合法程序。

type JSStr

type JSStr string

JSStr用于封装一个打算嵌入JavaScript表达式中的字符序列,该字符串必须匹配一系列StringCharacters:

StringCharacter :: 除了`\`和行终止符的SourceCharacter | EscapeSequence
注意不允许换行,JSStr("foo\\nbar")是可以的,但JSStr("foo\\\nbar")不可以。
type URL

type URL string

URL用来封装一个已知安全的URL或URL子字符串(参见RFC 3986)

形如javascript:checkThatFormNotEditedBeforeLeavingPage()的来源可信的URL应写进页面里,但一般动态的javascript: URL排除在外(不写进页面),因为它们是频繁使用的注入向量。

type CSS

type CSS string

CSS用于包装匹配如下任一条的已知安全的内容:

1. CSS3样式表,如`p { color: purple }`
2. CSS3规则,如`a[href=~"https:"].foo#bar`
3. CSS3声明,如`color: red; margin: 2px`
4. CSS3规则,如`rgba(0, 0, 255, 127)`

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论