Golang - template/text和template/html

AI 摘要: Go语言中的模板有两种类型,text/template和html/template,两者语法一致,后者考虑了HTML代码注入安全。模板语法包括注释、行为、函数、模板命名和嵌套等。

template在Go语言中的定位

  1. 模板作用,类似于smarty模板引擎,有自己的模板语法
  2. 支持text/templatehtml/template,后者是在前者的基础上面,考虑了很多HTML代码注入安全方面的风险,通常是将HTML标签实体化;同时两者的目标语法都是一致的!
  3. 模板核心内容有注释、行为(参数、管道,迭代,变量、比较)、函数、模板命名和嵌套等

1. template/text

  • 基于{{}},以及.
  • 模板部分多采用"`“符号
  • 支持条件判断
  • 支持array、slice、map的range迭代
  • 支持变量初始化声明和赋值
  • 支持方法调用
  • 支持函数调用

1.1. 示例

模板使用模式,再复杂的例子都是类似:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type Inventory struct {
    Material string
    Count    uint
}
sweaters := Inventory{"wool", 17}

// 模板创建+解析文本模板
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
if err != nil { panic(err) }

// 基于被解析的模板tmpl,应用指定的模板数据,并写到指定的输出
err = tmpl.Execute(os.Stdout, sweaters)
if err != nil { panic(err) }

模板语法

2. 文本和空格删除

  • 删除左侧空格:{{-
  • 删除右侧空格:" -}}"
1
2
3
4
5
tmpl, _ := template.New("test").Parse(`{{"prefix" -}}  del-left < del-right   {{- "suffix"}}`)
    _ = tmpl.Execute(os.Stdout, nil)

// 输出
prefixdel-left < del-rightsuffix

2.1. 模板行为 - Actions

参数“arguments”和管线“pipelines”是对应数据的求值操作处理,除此外variable也是一块!

pipeline下面可以直接理解为输出管道,{{pipeline}}理解为从管道输出copy出来的值!

2.1.1. 注释

1
2
3
4
{{/* a comment */}}

//两边删除空格注释
{{- /* a comment with white space trimmed from preceding and following text */ -}}

2.1.2. 格式输出值(类似fmt.Print)

1
{{pipeline}}

2.1.3. 条件判断 - IF END

空值代表false,0,nil指针,nil接口等空值

1
2
3
4
{{if pipeline}} T1 {{end}} 
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}

2.1.4. 数据迭代 - Range END

可被pipeline内容需要为array, slice, map, or channel,若pipleline长度为0,则不做输出!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// pipline长度为0,T1不会执行;若成功迭代,“.”代表被迭代的元素,并且T1执行
// 若值是map并且key是可比较的基础类型,元素将按key顺序访问
{{range pipeline}} T1 {{end}}

// 长度为0,执行T0
{{range pipeline}} T1 {{else}} T0 {{end}}

// kv迭代,针对map和slice、array支持kv变量迭代
{{range $k, $v := pipeline}}
    {{$k}} : {{$v}}
{{end}}

// range迭代map、slice只有一个变量情况下,为迭代的元素的值!(这点特别注意,与go语法相反)
{{range $v := pipeline}}
    {{$v}} 
{{end}}

2.4. 自定义目标变量 - Variables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 自定义一个$list变量,其pipeline由.prod.size数组输出
{{$list := .prod.size}}
{{range $list}}
    {{.}}
{{end}}


// 定义模板变量和赋值目标变量
{{$variable := pipeline}}
{{$variable = pipeline}}

2.1.5. 模板执行

1
2
3
4
5
// 指定名称的模板执行
{{template "name"}}

// 附带pipeline数据执行name模板,在name模板中,"."代表pipeline的值
{{template "name" pipeline}}

2.1.6. 自定义模板

自定义模板通常定义一系列根模板,然后在其他模板块中使用

1
2
3
4
5
6
// 内部定义一个name模板,并以数据执行它
{{block "name" pipeline}} T1 {{end}}

// 等价于
{{define "name"}} T1 {{end}} # 定义模板
{{template "name" pipeline}} # 执行模板

2.1.7. with操作

1
2
3
4
5
// 类似与if,当pipeline不为空,则执行T1
{{with pipeline}} T1 {{end}}

// 类似于if..else..end
{{with pipeline}} T1 {{else}} T0 {{end}}

2.2. Arguments:模板参数

  • A boolean, string, character, integer, floating-point, imaginary or complex constant in Go syntax
  • nil关键字
  • “.",代表结果的值
  • “$name”,代表变量名称
  • “.Field”:数据项的值
  • “.Field1.Filed2”:数据项链 或者 “$x.Field1.Field2”
  • “.Key”:数据的key,必须为map类型数据
    • 支持链:".Field1.Key1.Field2.Key2”
    • 不像其他Field需要大写,key在变量上支持小写:"$x.key1.key2”
  • “.Method”:使用".“作为接收器,执行”.Method()"
    • 这类方法必须要有一个确定的值类型返回,或者一个值+err返回(如果出错,执行中断,错误返回给调用者)
    • 方法支持链式调用:".Field1.Key1.Method1.Field2.Key2.Method2"
    • 方法支持变量调用:"$x.Method1.Field"
  • “fun”
    • printf “%q” “output”
  • 支持实例项调用
    • “print (.F1 arg1) (.F2 arg2)”
    • “(.StructValuedMethod “arg”).Field”

使用已定义的call函数在下面。

2.3. Pipelines

通道是一串命令,是一个简单的参数,或函数或方法调用,通道利用"|“字符分割。

2.5. Action、Arguments、Variable、Pipelines示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{{"\"output\""}}
    A string constant.

{{`"output"`}}
    A raw string constant.

{{printf "%q" "output"}}
    A function call.

{{"output" | printf "%q"}}
    A function call whose final argument comes from the previous
    command.

{{printf "%q" (print "out" "put")}}
    A parenthesized argument.(带括号的参数)

{{"put" | printf "%s%s" "out" | printf "%q"}}
    A more elaborate call.

{{"output" | printf "%s" | printf "%q"}}
    A longer chain.

{{with "output"}}{{printf "%q" .}}{{end}}
    A with action using dot.

{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
    A with action that creates and uses a variable.

{{with $x := "output"}}{{printf "%q" $x}}{{end}}
    A with action that uses the variable in another action.

{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
    The same, but pipelined.

3. Functions函数

执行函数有两处:模板函数、全局函数

3.1. 预定义的全局函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 关系类型函数
- and:"and x y"
- not: "not x"
- or: "or x y"

// 打印类
- print: fmt.Sprint别名
- printf: {{ printf "%-20v" data }} // 注意没有,
- println: {{ println . }} // 调试打印

// 索引值相关
- index: 返回索引值,"index x 1 2 3"
- len: 变量长度

// 其他
- call: "call .X.Y 1 2"、dot.X.Y(1, 2)
- js: 返回js转义值
- urlquery:表单套件增强,用于URL查询 (在html/template中存在异常,不要使用)
- html: 返回转义HTML(在html/template中存在异常,不要使用)

3.2. 自定义模板函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User:   {{.User.Login}}
Title:  {{.Title | printf "%.64s"}}
Age:    {{.CreatedAt | daysAgo}} days
{{end}}`

func daysAgo(t time.Time) int {
    return int(time.Since(t).Hours() / 24)
}

// template.New先创建并返回一个模板;Funcs方法将daysAgo等自定义函数注册到模板中,并返回模板;最后调用Parse函数分析模板。
report, err := template.New("report").
    Funcs(template.FuncMap{"daysAgo": daysAgo}).
    Parse(templ)
if err != nil {
    log.Fatal(err)
}

3.3. 比较类函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 条件判断函数
- eq arg1 arg2 :等价于 arg1 == arg2
- ne
- lt
- le
- gt
- ge

// 多值条件判断
arg1==arg2 || arg1==arg3 || arg1==arg4 ...

4. 模板嵌套和执行

4.1. 嵌套

1
2
3
4
5
6
7
8
// 目标定义
`{{define "T1"}}ONE{{end}}
    {{define "T2"}}TWO{{end}}
    {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
    {{template "T3"}}`

// 目标执行后
ONE TWO

4.2. 执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 调用全部
err := tmpl.Execute(os.Stdout, "no data needed")
if err != nil {
    log.Fatalf("execution failed: %s", err)
}

// 调用部分
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
if err != nil {
    log.Fatalf("execution failed: %s", err)
}

5. 其他文本格式输出相关的

  • text/tabwriter
  • text/template
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 表格输出
func textTableShow(w io.Writer, data map[string]interface{}) {
    const format = "%v\t%v\n"
    tw := new(tabwriter.Writer).Init(w, 0, 8, 2, ' ', 0)
    fmt.Fprintf(tw, format, "db.Stats Key", "Values")
    fmt.Fprintf(tw, format, "----------", "-----")
    for k, v := range data {
        fmt.Fprintf(tw, format, k, v)
    }
    tw.Flush()
}

// 文本模板输出
func textTmplShow(w io.Writer, data map[string]interface{}) {
    // tmpl
    const tmplTxt = `DB STATUS
-----------------------{{range $k, $v := .}}
{{printf "%-18v" $k}} => {{printf "%v" .}}{{end}}
`
    tmpl, err := template.New("showStatus").Parse(tmplTxt)
    if err != nil {
        ErrHandle(err, "template.Parse")
    }
    if err := tmpl.Execute(w, data); err != nil {
        ErrHandle(err, "template.Execute")
    }
}