Go template/html iteration to generate table from struct

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

Go template/html iteration to generate table from struct

问题

给定一个结构体的集合,我该如何使用“range”模板迭代器打印出一个表格,每个结构体对应一行,每个字段值对应一列,而不需要显式命名字段?

  1. container := []Node
  2. type Node struct {
  3. Contact_id int
  4. Employer_id int
  5. First_name string
  6. Middle_name string
  7. Last_name string
  8. }
  9. 模板代码
  10. {{range .container}}
  11. <tr>
  12. <td>{{.Prefix}}</td>
  13. <td>{{.First_name}}</td>
  14. <td>{{.Middle_name}}</td>
  15. <td>{{.Last_name}}</td>
  16. <td>{{.Contact_id}}</td>
  17. <td>{{.Employer_id}}</td>
  18. </tr>
  19. {{end}}
  20. ------------------------
  21. 当我尝试使用以下方式迭代值时
  22. {{range .container}}
  23. {{range .}}
  24. <td>{{.}}</td>
  25. {{end}}
  26. {{end}}
  27. 我被告知不能迭代值
  28. 有没有一种简洁的方法来实现这个
  29. <details>
  30. <summary>英文:</summary>
  31. 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?
  32. container := []Node
  33. type Node struct {
  34. Contact_id int
  35. Employer_id int
  36. First_name string
  37. Middle_name string
  38. Last_name string
  39. }
  40. Template Code:
  41. {{range .container}}
  42. &lt;tr&gt;
  43. &lt;td&gt;{{.Prefix}}&lt;/td&gt;
  44. &lt;td&gt;{{.First_name}}&lt;/td&gt;
  45. &lt;td&gt;{{.Middle_name}}&lt;/td&gt;
  46. &lt;td&gt;{{.Last_name}}&lt;/td&gt;
  47. &lt;td&gt;{{.Contact_id}}&lt;/td&gt;
  48. &lt;td&gt;{{.Employer_id}}&lt;/td&gt;
  49. &lt;/tr&gt;
  50. {{end}}
  51. ------------------------
  52. When I try iterating through the values using
  53. {{range .container}}
  54. {{range .}}
  55. &lt;td&gt;{{.}}&lt;/td&gt;
  56. {{end}}
  57. {{end}}
  58. I am told that I cannot iterate over the Values.
  59. Is there any clean way to do this?
  60. </details>
  61. # 答案1
  62. **得分**: 16
  63. 使用`html/template`包时无法迭代结构体中的字段在该包的[文档](http://golang.org/pkg/text/template/)中,可以阅读到以下内容:
  64. &gt;{{range pipeline}} T1 {{end}}
  65. &gt;管道的值必须是数组切片映射或通道
  66. 也就是说管道不能是结构体你可以采取以下方法之一
  67. * 使用中间类型例如`[][]interface{}`作为传递给模板的容器变量
  68. * 将每个`<td>`单元格分别输出就像你展示的那样
  69. * 创建一个模板函数将结构体值转换为可迭代的某种类型
  70. 由于结构体在编译时定义并且在运行时不会改变其结构因此迭代是不必要的也不会使模板更清晰我建议不要这样做
  71. **编辑**
  72. 但有时反射是一件好事Brenden还指出实际上可以让`range`迭代从函数返回的值如果使用反射这将是最简单的方法
  73. 使用模板函数的完整工作示例
  74. package main
  75. import (
  76. &quot;html/template&quot;
  77. &quot;os&quot;
  78. &quot;reflect&quot;
  79. )
  80. type Node struct {
  81. Contact_id int
  82. Employer_id int
  83. First_name string
  84. Middle_name string
  85. Last_name string
  86. }
  87. var templateFuncs = template.FuncMap{&quot;rangeStruct&quot;: RangeStructer}
  88. // 在模板中,我们使用rangeStruct将结构体值转换为可迭代的切片
  89. var htmlTemplate = `{{range .}}&lt;tr&gt;
  90. {{range rangeStruct .}} &lt;td&gt;{{.}}&lt;/td&gt;
  91. {{end}}&lt;/tr&gt;
  92. {{end}}`
  93. func main() {
  94. container := []Node{
  95. {1, 12, &quot;Accipiter&quot;, &quot;ANisus&quot;, &quot;Nisus&quot;},
  96. {2, 42, &quot;Hello&quot;, &quot;my&quot;, &quot;World&quot;},
  97. }
  98. // 创建模板并注册模板函数
  99. t := template.New(&quot;t&quot;).Funcs(templateFuncs)
  100. t, err := t.Parse(htmlTemplate)
  101. if err != nil {
  102. panic(err)
  103. }
  104. err = t.Execute(os.Stdout, container)
  105. if err != nil {
  106. panic(err)
  107. }
  108. }
  109. // RangeStructer接受第一个参数(必须是结构体)并将每个字段的值返回为切片。如果没有参数或第一个参数不是结构体,则返回nil。
  110. func RangeStructer(args ...interface{}) []interface{} {
  111. if len(args) == 0 {
  112. return nil
  113. }
  114. v := reflect.ValueOf(args[0])
  115. if v.Kind() != reflect.Struct {
  116. return nil
  117. }
  118. out := make([]interface{}, v.NumField())
  119. for i := 0; i &lt; v.NumField(); i++ {
  120. out[i] = v.Field(i).Interface()
  121. }
  122. return out
  123. }
  124. 输出
  125. &lt;tr&gt;
  126. &lt;td&gt;1&lt;/td&gt;
  127. &lt;td&gt;12&lt;/td&gt;
  128. &lt;td&gt;Accipiter&lt;/td&gt;
  129. &lt;td&gt;ANisus&lt;/td&gt;
  130. &lt;td&gt;Nisus&lt;/td&gt;
  131. &lt;/tr&gt;
  132. &lt;tr&gt;
  133. &lt;td&gt;2&lt;/td&gt;
  134. &lt;td&gt;42&lt;/td&gt;
  135. &lt;td&gt;Hello&lt;/td&gt;
  136. &lt;td&gt;my&lt;/td&gt;
  137. &lt;td&gt;World&lt;/td&gt;
  138. &lt;/tr&gt;
  139. [Playground](http://play.golang.org/p/38W6Lw3zSE)
  140. <details>
  141. <summary>英文:</summary>
  142. 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:
  143. &gt;{{range pipeline}} T1 {{end}}
  144. &gt;The value of the pipeline must be an array, slice, map, or channel.
  145. That is, Pipeline cannot be a struct. Either you need to:
  146. * use an intermediate type, eg. `[][]interface{}`, as container variable that you pass to the template
  147. * type out each &lt;td&gt; cell separately as you&#39;ve shown
  148. * create a template function that converts struct values to some type you can iterate over
  149. 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.
  150. **Edit**
  151. 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.
  152. Full working example using a template function:
  153. package main
  154. import (
  155. &quot;html/template&quot;
  156. &quot;os&quot;
  157. &quot;reflect&quot;
  158. )
  159. type Node struct {
  160. Contact_id int
  161. Employer_id int
  162. First_name string
  163. Middle_name string
  164. Last_name string
  165. }
  166. var templateFuncs = template.FuncMap{&quot;rangeStruct&quot;: RangeStructer}
  167. // In the template, we use rangeStruct to turn our struct values
  168. // into a slice we can iterate over
  169. var htmlTemplate = `{{range .}}&lt;tr&gt;
  170. {{range rangeStruct .}} &lt;td&gt;{{.}}&lt;/td&gt;
  171. {{end}}&lt;/tr&gt;
  172. {{end}}`
  173. func main() {
  174. container := []Node{
  175. {1, 12, &quot;Accipiter&quot;, &quot;ANisus&quot;, &quot;Nisus&quot;},
  176. {2, 42, &quot;Hello&quot;, &quot;my&quot;, &quot;World&quot;},
  177. }
  178. // We create the template and register out template function
  179. t := template.New(&quot;t&quot;).Funcs(templateFuncs)
  180. t, err := t.Parse(htmlTemplate)
  181. if err != nil {
  182. panic(err)
  183. }
  184. err = t.Execute(os.Stdout, container)
  185. if err != nil {
  186. panic(err)
  187. }
  188. }
  189. // RangeStructer takes the first argument, which must be a struct, and
  190. // returns the value of each field in a slice. It will return nil
  191. // if there are no arguments or first argument is not a struct
  192. func RangeStructer(args ...interface{}) []interface{} {
  193. if len(args) == 0 {
  194. return nil
  195. }
  196. v := reflect.ValueOf(args[0])
  197. if v.Kind() != reflect.Struct {
  198. return nil
  199. }
  200. out := make([]interface{}, v.NumField())
  201. for i := 0; i &lt; v.NumField(); i++ {
  202. out[i] = v.Field(i).Interface()
  203. }
  204. return out
  205. }
  206. Output:
  207. &lt;tr&gt;
  208. &lt;td&gt;1&lt;/td&gt;
  209. &lt;td&gt;12&lt;/td&gt;
  210. &lt;td&gt;Accipiter&lt;/td&gt;
  211. &lt;td&gt;ANisus&lt;/td&gt;
  212. &lt;td&gt;Nisus&lt;/td&gt;
  213. &lt;/tr&gt;
  214. &lt;tr&gt;
  215. &lt;td&gt;2&lt;/td&gt;
  216. &lt;td&gt;42&lt;/td&gt;
  217. &lt;td&gt;Hello&lt;/td&gt;
  218. &lt;td&gt;my&lt;/td&gt;
  219. &lt;td&gt;World&lt;/td&gt;
  220. &lt;/tr&gt;
  221. [Playground](http://play.golang.org/p/38W6Lw3zSE)
  222. </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:

确定