英文:
Go template/html iteration to generate table from struct
问题
给定一个结构体的集合,我该如何使用“range”模板迭代器打印出一个表格,每个结构体对应一行,每个字段值对应一列,而不需要显式命名字段?
container := []Node
type Node struct {
Contact_id int
Employer_id int
First_name string
Middle_name string
Last_name string
}
模板代码:
{{range .container}}
<tr>
<td>{{.Prefix}}</td>
<td>{{.First_name}}</td>
<td>{{.Middle_name}}</td>
<td>{{.Last_name}}</td>
<td>{{.Contact_id}}</td>
<td>{{.Employer_id}}</td>
</tr>
{{end}}
------------------------
当我尝试使用以下方式迭代值时:
{{range .container}}
{{range .}}
<td>{{.}}</td>
{{end}}
{{end}}
我被告知不能迭代值。
有没有一种简洁的方法来实现这个?
<details>
<summary>英文:</summary>
Given a collection of structs, how can I use the "range" template iterator to print out a table that assigns a row per struct, and a column per field value without explicity naming the fields?
container := []Node
type Node struct {
Contact_id int
Employer_id int
First_name string
Middle_name string
Last_name string
}
Template Code:
{{range .container}}
<tr>
<td>{{.Prefix}}</td>
<td>{{.First_name}}</td>
<td>{{.Middle_name}}</td>
<td>{{.Last_name}}</td>
<td>{{.Contact_id}}</td>
<td>{{.Employer_id}}</td>
</tr>
{{end}}
------------------------
When I try iterating through the values using
{{range .container}}
{{range .}}
<td>{{.}}</td>
{{end}}
{{end}}
I am told that I cannot iterate over the Values.
Is there any clean way to do this?
</details>
# 答案1
**得分**: 16
使用`html/template`包时,无法迭代结构体中的字段。在该包的[文档](http://golang.org/pkg/text/template/)中,可以阅读到以下内容:
>{{range pipeline}} T1 {{end}}
>管道的值必须是数组、切片、映射或通道。
也就是说,管道不能是结构体。你可以采取以下方法之一:
* 使用中间类型,例如`[][]interface{}`,作为传递给模板的容器变量
* 将每个`<td>`单元格分别输出,就像你展示的那样
* 创建一个模板函数,将结构体值转换为可迭代的某种类型
由于结构体在编译时定义,并且在运行时不会改变其结构,因此迭代是不必要的,也不会使模板更清晰。我建议不要这样做。
**编辑**
但有时反射是一件好事。Brenden还指出,实际上可以让`range`迭代从函数返回的值。如果使用反射,这将是最简单的方法。
使用模板函数的完整工作示例:
package main
import (
"html/template"
"os"
"reflect"
)
type Node struct {
Contact_id int
Employer_id int
First_name string
Middle_name string
Last_name string
}
var templateFuncs = template.FuncMap{"rangeStruct": RangeStructer}
// 在模板中,我们使用rangeStruct将结构体值转换为可迭代的切片
var htmlTemplate = `{{range .}}<tr>
{{range rangeStruct .}} <td>{{.}}</td>
{{end}}</tr>
{{end}}`
func main() {
container := []Node{
{1, 12, "Accipiter", "ANisus", "Nisus"},
{2, 42, "Hello", "my", "World"},
}
// 创建模板并注册模板函数
t := template.New("t").Funcs(templateFuncs)
t, err := t.Parse(htmlTemplate)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, container)
if err != nil {
panic(err)
}
}
// RangeStructer接受第一个参数(必须是结构体)并将每个字段的值返回为切片。如果没有参数或第一个参数不是结构体,则返回nil。
func RangeStructer(args ...interface{}) []interface{} {
if len(args) == 0 {
return nil
}
v := reflect.ValueOf(args[0])
if v.Kind() != reflect.Struct {
return nil
}
out := make([]interface{}, v.NumField())
for i := 0; i < v.NumField(); i++ {
out[i] = v.Field(i).Interface()
}
return out
}
输出:
<tr>
<td>1</td>
<td>12</td>
<td>Accipiter</td>
<td>ANisus</td>
<td>Nisus</td>
</tr>
<tr>
<td>2</td>
<td>42</td>
<td>Hello</td>
<td>my</td>
<td>World</td>
</tr>
[Playground](http://play.golang.org/p/38W6Lw3zSE)
<details>
<summary>英文:</summary>
With the `html/template`, you cannot iterate over the fields in a struct. In the [documentation](http://golang.org/pkg/text/template/) for the package, you can read:
>{{range pipeline}} T1 {{end}}
>The value of the pipeline must be an array, slice, map, or channel.
That is, Pipeline cannot be a struct. Either you need to:
* use an intermediate type, eg. `[][]interface{}`, as container variable that you pass to the template
* type out each <td> cell separately as you've shown
* create a template function that converts struct values to some type you can iterate over
Since a struct is defined at compile-time and won't change its structure during runtime, iteration is not necessary and wouldn't make things more clear in the template. I would advise against it.
**Edit**
But sometimes reflection is a good thing. Brenden also pointed out that you can actually let range iterate over the value returned from a function. If using reflection, this would be the easiest approach.
Full working example using a template function:
package main
import (
"html/template"
"os"
"reflect"
)
type Node struct {
Contact_id int
Employer_id int
First_name string
Middle_name string
Last_name string
}
var templateFuncs = template.FuncMap{"rangeStruct": RangeStructer}
// In the template, we use rangeStruct to turn our struct values
// into a slice we can iterate over
var htmlTemplate = `{{range .}}<tr>
{{range rangeStruct .}} <td>{{.}}</td>
{{end}}</tr>
{{end}}`
func main() {
container := []Node{
{1, 12, "Accipiter", "ANisus", "Nisus"},
{2, 42, "Hello", "my", "World"},
}
// We create the template and register out template function
t := template.New("t").Funcs(templateFuncs)
t, err := t.Parse(htmlTemplate)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, container)
if err != nil {
panic(err)
}
}
// RangeStructer takes the first argument, which must be a struct, and
// returns the value of each field in a slice. It will return nil
// if there are no arguments or first argument is not a struct
func RangeStructer(args ...interface{}) []interface{} {
if len(args) == 0 {
return nil
}
v := reflect.ValueOf(args[0])
if v.Kind() != reflect.Struct {
return nil
}
out := make([]interface{}, v.NumField())
for i := 0; i < v.NumField(); i++ {
out[i] = v.Field(i).Interface()
}
return out
}
Output:
<tr>
<td>1</td>
<td>12</td>
<td>Accipiter</td>
<td>ANisus</td>
<td>Nisus</td>
</tr>
<tr>
<td>2</td>
<td>42</td>
<td>Hello</td>
<td>my</td>
<td>World</td>
</tr>
[Playground](http://play.golang.org/p/38W6Lw3zSE)
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论