0

Golang | 模板

 2 years ago
source link: https://ijayer.github.io/post/tech/code/golang/20171010-go-template/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

什么是模板

Go中模板处理通过包: text/templatehtml/template 完成

一个模板就是一个字符串或一个文件,里面包含了一个或多个双花括号 {{}} 包含的对象

Go中模板解析

Go中模板解析主要步骤:

  1. 创建模板对象
  2. 加载模板字串(或模板文件)
  3. 执行渲染模板

Note: 其中最后一步就是将加载的字符和数据进行格式化

  • 模板标签: 标签用 {{}} 括起来
  • 模板注释: 注释用 {{/* a comment */}} 括起来表示注释内容

Go渲染 template 的时候,接收一个 interface{} 类型的变量, 我们在模板文件或模板字串中取变量值然后渲染到模板里

模板渲染时 interface{} 接收的变量通常为:

  • struct: 在模板中用struct的字段进行渲染
  • map[string][interface{}: 在模板中使用key进行渲染

Usage:

  • {{}}: 表示模板渲染时需要被替换的字段

  • {{.}}: 表示输出当前对象的值

  • {{.FieldOrMethodName}}: 表示输出struct对象的字段或方法名称为’FieldOrMethodName’的值

    • 注:要访问的对象必须可导出的
    • 当 FieldOrMethodName 为匿名字段时, 可访问其内部字段或方法, 如 “New”: {{.FieldOrMethodName.New}}
    • 当 New 是一个方法并返回一个struct对象,同样也可以访问其字段或方法: {{.FieldOrMethodName.New.Field1}}
  • {{.Method1 "参数值1" "参数值2"}}: 调用方法“Method1”,将后面的参数值依次传递给此方法,并输出其返回值。

模板参数可以是 Go 中的基本数据类型,亦可是数组切片或者一个结构体

在模板中定义变量: 变量名称由字母和数字组成,并附带 $ 前缀, 用 := 赋值

  • 比如,{{$X := “ok”}} or {{$Y := “pipeline”}}

  • {{$FieldName}}: 用于输出在模板中定义的名称为 FieldName 的值,当 FieldName 本身是一个struct时,可访问其字段

Go的模板支持 bool 和 string 类型的条件判断, 语法格式如下:

// if
{{if .condition}}
    some content
{{end}}

// if-else 
{{if .condition}}
    some content
{{else}}
    some content
{{end}}

// if-else if
{{if .condition}}
    some content
{{else if .condition}}
    some content
{{end}}

Note: 当.condition为bool类型时,值为true表示执行;当.condition为string类型时,值非空表示执行

内置的逻辑判断函数

  • not 非

    {{if not .condition}}    
    {{end}}
    • and 与
    {{if and .condition1 .condition2}} 
    {{end}}
  • or 或

    {{if or .condition1 .condition2}} 
    {{end}}
    • eq 等于
    {{if eq .var1 .var2}} 
    {{end}}
  • ne 不等于

    {{if ne .var1 .var2}} 
    {{end}}
    • lt 小于
    {{if lt .var1 .var2}} 
    {{end}}
  • le 小于等于

    {{if le .var1 .var2}} 
    {{end}}
    • gt 大于
    {{if gt .var1 .var2}} 
    {{end}}
  • ge 大于等于

    {{if ge .var1 .var2}} 
    {{end}}

    Go模板的管道函数提供和Unix的pipe一样的功能,可用于过滤简化数据等

    Go的模板提供了自定义模板函数的功能:template包创建新模板的时候通过调用 Funcs 方法将自定义函数注册到该模板中,后续通过该模板渲染的文件均支持调用这些自定义函数

    函数集合定义

    自定义模板函数的集合定义:

    type FuncMap map[string]interface{}

Note:

  • key: 方法名,用于模板字串或文件中调用
  • value: 函数。该函数对其参数个数无限制,但对函数返回值有两种限制

    • 只返回一个值
    • 返回两个值,且第二个值必须为 error 类型

自定义和模板函数

  1. 创建一个 FuncMap类型的 map,key 为模板函数名称, value为函数定义

  2. 将 FuncMap 注册到模板中

Note:

  • FuncMap注册必须在 Parse | ParseFiles之前
  • 代码示例参考: 函数调用
  • {{FuncName}}

    • 调用 FuncName 模板函数(等同于执行 “FuncName()” 并输出其返回值)
  • {{FuncName "Param1" "Param2"}}

    • 调用 FuncName(Param1, Param2), 并输出其返回值
    • type Para struct {
          X int
          Y int
      }
      
      var tmpl2 = `Result is {{sum .X .Y}}`
      
      func TmplTest2() {
          funcMap := template.FuncMap{"sum": add}         // 定义 FuncMap类型对象
      
          t := template.New("Call func").Funcs(funcMap)   // 注册 Funcmap到模板
          t, _ = t.Parse(tmpl2)
      
          p := Para{
              X: 1,
              Y: 2,
          }
      
          t.Execute(os.Stdout, p)
      }
      
      func add(x, y int) int {
          return x + y
      }
  • {{.FieldName | FuncName}}

    • 将竖线 “|” 左边的 “.FieldName” 变量值作为函数参数传送, 并输出其返回值。

对于数组,切片或map,可用迭代的action,与go的迭代类似,使用range:{{range ...}} ... {{end}}

遍历slice.1

遍历 slice,且获取其索引和值

{{range $i, $v := .slice}}
    the value is {{$i}}, {{$v}}
{{end}}

Note:

  • 在该循环中可通过 $i$v访问遍历的值
  • range…end结构内部如要使用外部的变量,比如.Var2,需要这样写:$.Var2 ((即:在外部变量名称前加符号“$”即可,单独的“$”意义等同于global))

Code:

type Slices struct {
    Names  []string
    OutVar string
}

var tmpl3 = `
{{range $i, $v := .Names}}
    the index is {{$i}} and the value is {{$v}}
    outside variable is {{$.OutVar}} {{/* obtain outside variable */}}
{{end}}
`

func TmplTest3() {
	s := Slices{
        Names: []string{"1", "2", "3", "4", "5", "6", "7"},
        OutVar: "0"
	}

	t := template.New("range test")
	t, _ = t.Parse(tmpl3)
	t.Execute(os.Stdout, s)
}

遍历slice.2

{{range .slice}}
    the value is {{.}}
{{end}}

Note: 该循环没有或取其index和value,需通过 . 访问对应的值

Code:

var tmpl = `Hello {{.Username}}
{{range .Emails}}
	an email {{.}}
{{end}}
{{range .Friends}}
	my friend name is {{.Name}}
{{end}}
`

with·上下文

{{with ...}} ... {{end}}

with: 操作当前对象的值,类似上下文概念,其含义为创建一个封闭的作用域,在其范围内,可用 .action处理变量,而与外面的 . 无关,只与为with参数有关:

{{with arg}}
    {{/* 此时的 . 就是arg */}}
{{end}}

Code:

type Friend struct {
	Fname string
}

type Person struct {
	UserName string
	Emails   []string
	Friends  []*Friend
}

var tmpl = `hello {{.UserName}}!
{{range .Emails}}
	an email {{.}}
{{end}}
{{with .Friends}} 
{{range .}}
	my friend name is {{.Fname}}
{{end}}
{{else}} 
    this is else {{.}}
{{end}}
`

Note: with语句也可有 else,else中的点则和 with 外面的 . 作用一样

Must操作

模板包包含一个函数: Must,其作用为检测模板是否正确(例如:大括号是否匹配,注释是否正确关闭,变量是否是正确的书写)

func main() {
	tOk := template.New("first")
	template.Must(tOk.Parse(" some static text /* and a comment */"))
	fmt.Println("The first one parsed OK.")

	template.Must(template.New("second").Parse("some static text {{ .Name }}"))
	fmt.Println("The second one parsed OK.")

	fmt.Println("The next one ought to fail.")
	tErr := template.New("check parse error with Must")
	template.Must(tErr.Parse(" some static text {{ .Name }"))
}

子模板定义

{{define "子模板名称"}} 模板内容 {{end}}

子模板调用

{{template "子模板名称"}}

Code: 定义三个模板文件,header.tmpl、content.tmpl、footer.tmpl测试嵌套模板

// header.tmpl
{{define "header"}}
<html>
<head>
    <title> 演示信息 </title>
</head>
<body>
{{end}}
// content.tmpl
{{define "content"}}
{{template "header"}}
<h1> 演示嵌套 </h1>
<u1>
    <li> 嵌套使用 define 定义子模板</li>
    <li> 调用使用 template "footer" </li>
</u1>
{{template "footer"}}
{{end}
// footer.tmpl
{{define "footer"}}
</body>
</html>
{{end}}
// Embedded template
func EmbeddedTmpl() {
	path := "/home/work/code/Go_Path/src/instance.golang.com/tmpl/tmpl"
	t, err := template.ParseFiles(path+"/header.tmpl", path+"/content.tmpl", path+"/footer.tmpl")
	if err != nil {
		logrus.Error(err)
	}

	t.ExecuteTemplate(os.Stdout, "header", nil)  // 渲染指定模板 header
	t.ExecuteTemplate(os.Stdout, "content", nil) // 渲染指定模板 content
	t.ExecuteTemplate(os.Stdout, "footer", nil)  // 渲染指定模板 footer
}

Output:

<html>
<head>
    <title> 演示信息 </title>
</head>
<body>


<html>
<head>
    <title> 演示信息 </title>
</head>
<body>

<h1> 演示嵌套 </h1>
<u1>
    <li> 嵌套使用 define 定义子模板</li>
    <li> 调用使用 template "footer" </li>
</u1>

</body>
</html>



</body>
</html>

Note:

  • template.ParseFiles 可把所有嵌套模板解析到我们的模板对象
  • 每一个 {{define “template_name”}} 定义的文件都是一个独立的模板文件,与其他模板文件并行存在; 解析后的模板文件在内部存储类似一种map结构,即key-表示模板名称,value-模板内容
  • ExecuteTemplate()用来渲染定义好的子模板文件

子模板访问父模板变量

使用 {{template "content" .}} 可将当前变量传送给子模板

模板相关函数详解

Parse & ParseFiles & ParseBlob

函数定义:

func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
func (t *Template) Parse(text string) (*Template, error)
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
func (t *Template) ParseGlob(pattern string) (*Template, error)

Note:

  • Parse 用来解析一个字符串,字符串代表模版的内容,并且嵌套的模版也会和这个模版进行关联。
  • ParseFiles 用来解析一组命名的文件模版,当模版定义在不同的文件中的时候,使用这个方法可以产生一个可执行的模版,模版执行的时候不会出错。
  • ParseGlob 与ParseFiles类似,它使用filepath.Glob模式匹配的方式遍历文件,将这些文件生成模版
  • 两个函数还可以作为 *Template的方法使用。作为函数使用的时候,它返回模版的名字是第一个文件的名字,模版以第一个文件作为base模版。同时后面的文件也会生成模版作为这个模版的关联模板,你可以通过Lookup方法查找到这个模版,因为每个模版都保存着它的关联模版:

Execute & ExecuteTemplate

func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

Note:

  • Execute 直接接收data的值渲染模板
  • 使用 ExecuteTemplate 可以选择渲染 t 关联的模版作为渲染的主模版; name-指定主模版

See Also

Thanks to the authors 🙂


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK