Go template/html iteration to generate table from struct

huangapple go评论106阅读模式
英文:

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 &quot;range&quot; 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}}
    
    &lt;tr&gt;
    &lt;td&gt;{{.Prefix}}&lt;/td&gt;
    &lt;td&gt;{{.First_name}}&lt;/td&gt;
    &lt;td&gt;{{.Middle_name}}&lt;/td&gt;
    &lt;td&gt;{{.Last_name}}&lt;/td&gt;
    
    &lt;td&gt;{{.Contact_id}}&lt;/td&gt;
    &lt;td&gt;{{.Employer_id}}&lt;/td&gt;
    
    &lt;/tr&gt;
    {{end}}

------------------------

When I try iterating through the values using

    {{range .container}}
     {{range .}}
     &lt;td&gt;{{.}}&lt;/td&gt; 
    {{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/)中,可以阅读到以下内容:

&gt;{{range pipeline}} T1 {{end}}  
&gt;管道的值必须是数组切片映射或通道

也就是说管道不能是结构体你可以采取以下方法之一

* 使用中间类型例如`[][]interface{}`作为传递给模板的容器变量
* 将每个`<td>`单元格分别输出就像你展示的那样
* 创建一个模板函数将结构体值转换为可迭代的某种类型

由于结构体在编译时定义并且在运行时不会改变其结构因此迭代是不必要的也不会使模板更清晰我建议不要这样做

**编辑**

但有时反射是一件好事Brenden还指出实际上可以让`range`迭代从函数返回的值如果使用反射这将是最简单的方法

使用模板函数的完整工作示例

    package main
    
    import (
    	&quot;html/template&quot;
    	&quot;os&quot;
    	&quot;reflect&quot;
    )
    
    type Node struct {
    	Contact_id  int
    	Employer_id int
    	First_name  string
    	Middle_name string
    	Last_name   string
    }
    
    var templateFuncs = template.FuncMap{&quot;rangeStruct&quot;: RangeStructer}
    
    // 在模板中,我们使用rangeStruct将结构体值转换为可迭代的切片
    var htmlTemplate = `{{range .}}&lt;tr&gt;
    {{range rangeStruct .}}	&lt;td&gt;{{.}}&lt;/td&gt;
    {{end}}&lt;/tr&gt;
    {{end}}`
    
    func main() {
    	container := []Node{
    		{1, 12, &quot;Accipiter&quot;, &quot;ANisus&quot;, &quot;Nisus&quot;},
    		{2, 42, &quot;Hello&quot;, &quot;my&quot;, &quot;World&quot;},
    	}
    
    	// 创建模板并注册模板函数
    	t := template.New(&quot;t&quot;).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 &lt; v.NumField(); i++ {
    		out[i] = v.Field(i).Interface()
    	}
    
    	return out
    }

输出

    &lt;tr&gt;
    	&lt;td&gt;1&lt;/td&gt;
    	&lt;td&gt;12&lt;/td&gt;
    	&lt;td&gt;Accipiter&lt;/td&gt;
    	&lt;td&gt;ANisus&lt;/td&gt;
    	&lt;td&gt;Nisus&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    	&lt;td&gt;2&lt;/td&gt;
    	&lt;td&gt;42&lt;/td&gt;
    	&lt;td&gt;Hello&lt;/td&gt;
    	&lt;td&gt;my&lt;/td&gt;
    	&lt;td&gt;World&lt;/td&gt;
    &lt;/tr&gt;

[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:

&gt;{{range pipeline}} T1 {{end}}  
&gt;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 &lt;td&gt; cell separately as you&#39;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&#39;t change its structure during runtime, iteration is not necessary and wouldn&#39;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 (
    	&quot;html/template&quot;
    	&quot;os&quot;
    	&quot;reflect&quot;
    )
    
    type Node struct {
    	Contact_id  int
    	Employer_id int
    	First_name  string
    	Middle_name string
    	Last_name   string
    }
    
    var templateFuncs = template.FuncMap{&quot;rangeStruct&quot;: RangeStructer}
    
    // In the template, we use rangeStruct to turn our struct values
    // into a slice we can iterate over
    var htmlTemplate = `{{range .}}&lt;tr&gt;
    {{range rangeStruct .}}	&lt;td&gt;{{.}}&lt;/td&gt;
    {{end}}&lt;/tr&gt;
    {{end}}`
    
    func main() {
    	container := []Node{
    		{1, 12, &quot;Accipiter&quot;, &quot;ANisus&quot;, &quot;Nisus&quot;},
    		{2, 42, &quot;Hello&quot;, &quot;my&quot;, &quot;World&quot;},
    	}
    
    	// We create the template and register out template function
    	t := template.New(&quot;t&quot;).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 &lt; v.NumField(); i++ {
    		out[i] = v.Field(i).Interface()
    	}
    
    	return out
    }

Output:

    &lt;tr&gt;
    	&lt;td&gt;1&lt;/td&gt;
    	&lt;td&gt;12&lt;/td&gt;
    	&lt;td&gt;Accipiter&lt;/td&gt;
    	&lt;td&gt;ANisus&lt;/td&gt;
    	&lt;td&gt;Nisus&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    	&lt;td&gt;2&lt;/td&gt;
    	&lt;td&gt;42&lt;/td&gt;
    	&lt;td&gt;Hello&lt;/td&gt;
    	&lt;td&gt;my&lt;/td&gt;
    	&lt;td&gt;World&lt;/td&gt;
    &lt;/tr&gt;

[Playground](http://play.golang.org/p/38W6Lw3zSE)

</details>



huangapple
  • 本文由 发表于 2013年11月15日 08:16:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/19991124.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定