英文:
Variables inside templates in golang
问题
html/text
模板中变量的命名空间是什么?我以为在模板中变量$x
可以改变值,但是这个例子告诉我我不能这样做。
当我尝试按年份分组比赛时失败了 - 类似这样的(http://play.golang.org/p/EX1Aut_ULD):
package main
import (
"fmt"
"os"
"text/template"
"time"
)
func main() {
tournaments := []struct {
Place string
Date time.Time
}{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
{{with .Date}}
{{$year:=.Year}}
{{if ne $year $prev_year}}
Actions in year {{$year}}:
{{$prev_year:=$year}}
{{end}}
{{end}}
{{.Place}}, {{.Date}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, tournaments)
if err != nil {
fmt.Println("executing template:", err)
}
}
英文:
What is the namespace of variables inside html/text
templates? I thought that a variable $x
can change value inside a template, but this example shows me that I cannot.
I failed when I tried to group tournaments according year - something like this (http://play.golang.org/p/EX1Aut_ULD):
package main
import (
"fmt"
"os"
"text/template"
"time"
)
func main() {
tournaments := []struct {
Place string
Date time.Time
}{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
{{with .Date}}
{{$year:=.Year}}
{{if ne $year $prev_year}}
Actions in year {{$year}}:
{{$prev_year:=$year}}
{{end}}
{{end}}
{{.Place}}, {{.Date}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, tournaments)
if err != nil {
fmt.Println("executing template:", err)
}
}
答案1
得分: 19
在go1.11中,text/template和html/template能够设置现有变量的值,这意味着原始代码只需进行一个非常小的修改即可正常工作。
将代码中的
{{$prev_year:=$year}}
改为
{{$prev_year = $year}}
英文:
In go1.11 text/template and hence html/template became able to set the value of existing variables, which means that the original code can be made to work with one very small modification.
Change
{{$prev_year:=$year}}
To
{{$prev_year = $year}}
答案2
得分: 18
编辑:请参阅https://stackoverflow.com/a/52925780/1685538以获取更更新的答案。
原始答案:
https://golang.org/pkg/text/template/#hdr-Variables:
变量的作用域延伸到控制结构("if"、"with"或"range")的"end"操作,或者如果没有这样的控制结构,则延伸到模板的末尾。
因此,你使用{{$prev_year:=$year}}
定义的$prev_year
只在下一行({{end}}
)之前存在。
似乎没有办法绕过这个问题。
正确的做法是将这个逻辑从模板中移出,并在你的Go代码中进行分组。
这里有一个可行的示例:https://play.golang.org/p/DZoSXo9WQR
package main
import (
"fmt"
"os"
"text/template"
"time"
)
type Tournament struct {
Place string
Date time.Time
}
type TournamentGroup struct {
Year int
Tournaments []Tournament
}
func groupTournamentsByYear(tournaments []Tournament) []TournamentGroup {
if len(tournaments) == 0 {
return nil
}
result := []TournamentGroup{
{
Year: tournaments[0].Date.Year(),
Tournaments: make([]Tournament, 0, 1),
},
}
i := 0
for _, tournament := range tournaments {
year := tournament.Date.Year()
if result[i].Year == year {
// Add to existing group
result[i].Tournaments = append(result[i].Tournaments, tournament)
} else {
// New group
result = append(result, TournamentGroup{
Year: year,
Tournaments: []Tournament{
tournament,
},
})
i++
}
}
return result
}
func main() {
tournaments := []Tournament{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
Actions in year {{.Year}}:
{{range .Tournaments}}
{{.Place}}, {{.Date}}
{{end}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, groupTournamentsByYear(tournaments))
if err != nil {
fmt.Println("executing template:", err)
}
}
英文:
Edit: See https://stackoverflow.com/a/52925780/1685538 for a more up-to-date answer.
Original answer:
https://golang.org/pkg/text/template/#hdr-Variables:
> A variable's scope extends to the "end" action of the control
> structure ("if", "with", or "range") in which it is declared, or to
> the end of the template if there is no such control structure.
So the $prev_year
you define with {{$prev_year:=$year}}
only lives until.. the next line ({{end}}
).
It seems there is no way of going around that.
The "right" way to do this is to take that logic out of your template, and do the grouping in your Go code.
Here is a working example : https://play.golang.org/p/DZoSXo9WQR
package main
import (
"fmt"
"os"
"text/template"
"time"
)
type Tournament struct {
Place string
Date time.Time
}
type TournamentGroup struct {
Year int
Tournaments []Tournament
}
func groupTournamentsByYear(tournaments []Tournament) []TournamentGroup {
if len(tournaments) == 0 {
return nil
}
result := []TournamentGroup{
{
Year: tournaments[0].Date.Year(),
Tournaments: make([]Tournament, 0, 1),
},
}
i := 0
for _, tournament := range tournaments {
year := tournament.Date.Year()
if result[i].Year == year {
// Add to existing group
result[i].Tournaments = append(result[i].Tournaments, tournament)
} else {
// New group
result = append(result, TournamentGroup{
Year: year,
Tournaments: []Tournament{
tournament,
},
})
i++
}
}
return result
}
func main() {
tournaments := []Tournament{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
Actions in year {{.Year}}:
{{range .Tournaments}}
{{.Place}}, {{.Date}}
{{end}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, groupTournamentsByYear(tournaments))
if err != nil {
fmt.Println("executing template:", err)
}
}
答案3
得分: 6
如这个答案所提到的,变量“重新赋值”的作用范围仅限于{{end}}
块。因此,只使用标准变量是无法解决这个问题的,应该在执行模板的Go程序内部解决。
然而,在某些框架中,这并不容易(例如protoc-gen-gotemplate)。
Spring库为标准模板语言添加了额外的功能之一是可变映射,可以按以下方式使用:
// 初始化字典(也可以不带初始键/值进行初始化)
{{$myVar := dict "key" "value"}}
// 从字典中获取“key”(返回数组),然后从该数组中获取第一个元素
{{pluck "key" $myVar | first}}
// 条件更新块
{{if eq "some" "some"}}
// $_ 似乎是必需的,因为Go模板函数需要返回值
{{$_ := set $myVar "key" "newValue"}}
{{end}}
// 打印更新后的值
{{pluck "key" $myVar | first}}
这个小例子输出:
value
newValue
一个实用的方法是使用一个字典来存储所有可变变量,并将它们存储在对应的变量名下作为键。
参考:
英文:
As mentioned by this answer, the scope of that variable "re-assignment" ends with the {{end}}
block. Therefore using standard variables only there's no way around the problem and it should be solved inside the Go program executing the template.
In some frameworks however this is not that easy (e.g. protoc-gen-gotemplate).
The Sprig library adds additional functionality to the standard template language. One of them are mutable maps that can be used in the following way:
// init the dictionary (you can init it without initial key/values as well)
{{$myVar := dict "key" "value"}}
// getting the "key" from the dictionary (returns array) and then fetching the first element from that array
{{pluck "key" $myVar | first}}
// conditional update block
{{if eq "some" "some"}}
// the $_ seems necessary because Go template functions need to return something
{{$_ := set $myVar "key" "newValue"}}
{{end}}
// print out the updated value
{{pluck "key" $myVar | first}}
This little example prints out:
value
newValue
A pragmatic approach would be to use a single dictionary for all mutable variables and store them under their corresponding variable name as key.
Reference:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论