假设您正在使用Web应用程序,并且需要返回列出最新推文的HTML。 您需要从数据库加载推文列表,并根据该信息创建HTML。
Go标准库中的text/template和html/template包可以实现数据驱动模板以生成文本输出:
var tmplStr = `User {{.User}} has {{.TotalTweets}} tweets.
{{- $tweetCount := len .RecentTweets }}
Recent tweets:
{{range $idx, $tweet := .RecentTweets}}Tweet {{$idx}} of {{$tweetCount}}: '{{.}}'
{{end -}}
Most recent tweet: '{{index .RecentTweets 0}}'
t := template.New("tweets")
t, err := t.Parse(tmplStr)
if err != nil {
log.Fatalf("template.Parse() failed with '%s'\n", err)
}
data := struct {
User string
TotalTweets int
RecentTweets []string
}{
User: "kjk",
TotalTweets: 124,
RecentTweets: []string{"hello", "there"},
}
err = t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
User kjk has 124 tweets.
Recent tweets:
Tweet 0 of 2: 'hello'
Tweet 1 of 2: 'there'
Most recent tweet: 'hello'
每个模板都有名字. template.New("tweets") 创建了空模板tweets.
t.Parse(s string) 用来解析模板.
t.Execute(w io.Writer, v interface{}) 使用数据填充模板并将结果写入io.Writer.
{{ ... }} 包裹的内容将被执行替换, 如{{.TweetCount}}
Data passed to a template can be hierarchical (i.e. a struct withing a struct within a struct…).
Current context . refers to current scope within the data.
Initial . refers to top-level scope:
tmplStr := "Data: {{.}}\n"
t := template.Must(template.New("simple").Parse(tmplStr))
execWithData := func(data interface{}) {
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
}
execWithData(5)
execWithData("foo")
st := struct {
Number int
Str string
}{
Number: 3,
Str: "hello",
}
execWithData(st)
Data: 5
Data: foo
Data: {3 hello}
Values that don’t have pre-defined formatting are printed using Stringer interface. For custom formatting of your type in a template implement String() string method.
{{range .Tweets}}{{end}} evaluates inner part for every element of []string slice Tweets and sets current context . within the inner part to elements of Tweets slice.
{{index .RecentTweets 0}} is equivalent to RecentTweets[0] in Go code.
Text in a template is copied verbatim. Having to preserve whitespace can lead to ugly templates.
To help write more readable templates We can add - at the beginning or end of action as seen in {{end -}}.
This remove whitespace before or after the action.
{{range .RecentTweets}} changes variable scope and we don’t have access to data outside. If we need to access data from upper scope, we can define variables like {{ $tweetCount := len .RecentTweets }}.
Methods as data
In a template {{ .Foo }} will either access struct field Foo or call a function Foo():
var tmplStr = Data from a field: '{{ .Field }}' Data from a method: '{{ .Method }}'
t := template.New("method")
t, err := t.Parse(tmplStr)
if err != nil {
log.Fatalf("template.Parse() failed with '%s'\n", err)
}
data := Data{
Field: 5,
}
err = t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
Data from a field: '5'
Data from a method: 'data from a method'
## if action
To conditionally render parts of the template, use if action:
const tmplStr = `{{range . -}}
{{if .IsNew}}'{{.Name}}' is new{{else}}'{{.Name}}' is not new{{end}}
{{end}}`
t := template.Must(template.New("if").Parse(tmplStr))
data := []struct {
Name string
IsNew bool
}{
{"Bridge", false},
{"Electric battery", true},
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
'Bridge' is not new
'Electric battery' is new
false values
Templating uses “truthy” logic for deciding what values are true or false in the context of if action:
const tmplStr = `{{range . -}}
{{printf "%- 16s" .Name}} is: {{if .Value}}true{{else}}false{{end}}
{{end}}`
t := template.Must(template.New("if").Parse(tmplStr))
var nilPtr *string = nil
var nilSlice []float32
emptySlice := []int{}
data := []struct {
Name string
Value interface{}
}{
{"bool false", false},
{"bool true", true},
{"integer 0", 0},
{"integer 1", 1},
{"float32 0", float32(0)},
{"float64 NaN", math.NaN},
{"empty string", ""},
{"non-empty string", "haha"},
{"nil slice", nilSlice},
{"empty slice", emptySlice},
{"non-empty slice", []int{3}},
{"nil pointer", nilPtr},
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
bool false is: false
bool true is: true
integer 0 is: false
integer 1 is: true
float32 0 is: false
float64 NaN is: true
empty string is: false
non-empty string is: true
nil slice is: false
empty slice is: false
non-empty slice is: true
nil pointer is: false
Avoid printing empty slices
Truthy logic is useful when we want to show different text if a list of items is empty:
type UserTweets struct {
User string
Tweets []string
}
const tmplStr = `
{{- if not .Tweets -}}
User '{{.User}}' has no tweets.
{{ else -}}
User '{{.User}}' has {{ len .Tweets }} tweets:
{{ range .Tweets -}}
'{{ . }}'
{{ end }}
{{- end}}`
t := template.Must(template.New("if").Parse(tmplStr))
data := UserTweets{
User: "kjk",
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
data = UserTweets{
User: "masa",
Tweets: []string{"tweet one", "tweet two"},
}
err = t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
User 'kjk' has no tweets.
User 'masa' has 2 tweets:
'tweet one'
'tweet two'
range action
Just as in Go, range can iterate over arrays, slices, maps and channels.
对数组的迭代
const tmplStr = `Elements of arrays or slice: {{ range . }}{{ . }} {{end}}
t := template.Must(template.New("range").Parse(tmplStr))
array := [...]int{3, 8}
err := t.Execute(os.Stdout, array)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
slice := []int{12, 5}
err = t.Execute(os.Stdout, slice)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
Elements of arrays or slice: 3 8
Elements of arrays or slice: 12 5
对map的迭代
const tmplStr = `Elements of map:
{{ range $k, $v := . }}{{ $k }}: {{ $v }}
{{end}}`
t := template.Must(template.New("range").Parse(tmplStr))
data := map[string]int{
"one": 1,
"five": 5,
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
Elements of map:
five: 5
one: 1
对channel的迭代
const tmplStr = `Elements of a channel: {{ range . }}{{ . }} {{end}}
t := template.Must(template.New("range").Parse(tmplStr))
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
ch <- i
}
close(ch)
}()
err := t.Execute(os.Stdout, ch)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
Elements of a channel: 0 1 2
内建函数
Templating engine supports calling functions like {{ len .Tweet }} where len is a function that returns length of an array or slice.
and, or, not
and, or, not are for logical operations:
const tmplStr = `Or: {{ if or .True .False }}true{{ else }}false{{ end }}
And: {{ if and .True .False }}true{{ else }}false{{ end }}
Not: {{ if not .False }}true{{ else }}false{{ end }}
t := template.Must(template.New("and_or_not").Parse(tmplStr))
data := Data{True: true, False: false}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
Or: true
And: false
Not: true
index
index is for accessing elements of a slice by index or values in a map by key.
const tmplStr = `Slice[0]: {{ index .Slice 0 }}
SliceNested[1][0]: {{ index .SliceNested 1 0 }}
Map["key"]: {{ index .Map "key" }}
t := template.Must(template.New("index").Parse(tmplStr))
data := struct {
Slice []string
SliceNested [][]int
Map map[string]int
}{
Slice: []string{"first", "second"},
SliceNested: [][]int{
{3, 1},
{2, 3},
},
Map: map[string]int{
"key": 5,
},
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
Slice[0]: first
SliceNested[1][0]: 2
Map["key"]: 5
len
返回数组或map的长度
const tmplStr = `len(nil) : {{ len .SliceNil }}
len(emptySlice): {{ len .SliceEmpty }}
len(slice) : {{ len .Slice }}
len(map) : {{ len .Map }}
`
t := template.Must(template.New("len").Parse(tmplStr))
data := struct {
SliceNil []int
SliceEmpty []string
Slice []bool
Map map[int]bool
}{
SliceNil: nil,
SliceEmpty: []string{},
Slice: []bool{true, true, false},
Map: map[int]bool{5: true, 3: false},
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
len(nil) : 0
len(emptySlice): 0
len(slice) : 3
len(map) : 2
print, printf, println
print 作用同 fmt.Sprint.
printf 作用同 fmt.Sprintf.
println 作用同 fmt.Sprintln.
const tmplStr = `print: {{ print .Str .Num }}
println: {{ println .Str .Num }}
printf: {{ printf "%s %#v %d" .Str .Str .Num }}
`
t := template.Must(template.New("print").Parse(tmplStr))
data := struct {
Str string
Num int
}{
Str: "str",
Num: 8,
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
print: str8
println: str 8
printf: str "str" 8
js, html, urlquery
js, html and urlquery is for escaping text so that it can be safely inserted in a JavaScript, HTML and URL context:
const tmplStr = `js escape : {{ js .JS }}
html escape: {{ html .HTML }}
url escape : {{ urlquery .URL }}
`
t := template.Must(template.New("print").Parse(tmplStr))
data := struct {
JS string
HTML string
URL string
}{
JS: `function me(s) { return "foo"; }`,
HTML: `<div>text</div>`,
URL: `http://www.programming-books.io`,
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
js escape : function me(s) { return "foo"; }
html escape: <div>text</div>
url escape : http%3A%2F%2Fwww.programming-books.io
自定义函数
你可以自定义函数, 然后在模板中使用
const tmplStr = `5 + 5 = {{ sum 5 .Arg }}`
customFunctions := template.FuncMap{
"sum": sum,
}
t := template.Must(template.New("func").Funcs(customFunctions).Parse(tmplStr))
data := struct {
Arg int
}{
Arg: 5,
}
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
5 + 5 = 10
HTML 模板
html/template包和text/template包有相同的功能.
The difference is that html/template understands structure of HTML and JavaScript code inside HTML.
Inserted text is escaped based on its surrounding context which eliminates cross-site scripting bugs.
const tmplStr = `<div onlick="{{ .JS }}">{{ .HTML }}</div>
`
txt := text_template.Must(text_template.New("text").Parse(tmplStr))
html := html_template.Must(html_template.New("html").Parse(tmplStr))
data := struct {
JS string
HTML string
URL string
}{
JS: `foo`,
HTML: `<span>text</span>`,
URL: `http://www.programming-books.io`,
}
err := txt.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
fmt.Println()
err = html.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
<div onlick="foo"><span>text</span></div>
<div onlick=""foo""><span>text</span></div>
Inserting unescaped HTML
Sometimes you need to subvert escaping of text:
const tmplStr = `<div onlick="{{ .JS }}">{{ .HTML }}</div>
`
html := template.Must(template.New("html").Parse(tmplStr))
data := struct {
JS string
HTML string
}{
JS: `foo`,
HTML: `<span>text</span>`,
}
fmt.Printf("Escaped:\n")
err := html.Execute(os.Stdout, data)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
fmt.Printf("\nUnescaped:\n")
data2 := struct {
JS template.JS
HTML template.HTML
}{
JS: `foo`,
HTML: `<span>text</span>`,
}
err = html.Execute(os.Stdout, data2)
if err != nil {
log.Fatalf("t.Execute() failed with '%s'\n", err)
}
Escaped:
<div onlick=""foo""><span>text</span></div>Unescaped:
<div onlick="foo"><span>text</span></div>
template.HTML and template.JS are type alises for string so you can assign string values to them.
Templating engine recognizes those types and disables escaping for them.