Go 每日一库-template


安装

无,此库为标准库,有 text/template 和 html/template 两种

功能

实时渲染文本,实现动态页面。

用法

模板语法

.和作用域

模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象(即 go 程序传入的一个数据结构),可以通过结构体的字段或者字典的键等来获取对应的字段,如{{.Name}}
其本质是模板执行时会遍历结构并将指针表示为.(称之为”dot”)指向运行过程中数据结构的当前位置的值。

用作模板的输入文本必须是utf-8编码的文本。

当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如:

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

type UserInfo struct {
	Name   string
	Gender string
	Age    int
}

func tmpl(w http.ResponseWriter, r *http.Request) {
	// 解析指定文件生成模板对象
	tmpl, err := template.ParseFiles("./hello.html")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}
	// 利用给定数据渲染模板,并将结果写入w
	user := UserInfo{
		Name:   "枯藤",
		Gender: "男",
		Age:    18,
	}
	tmpl.Execute(w, user)
}
func main() {
	server := http.Server{
		Addr: "127.0.0.1:9090",
	}
	http.HandleFunc("/tmpl", tmpl)
	server.ListenAndServe()
}

HTML文件代码如下:

<!--hello.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello</title>
</head>
<body>
    <p>Hello {{.Name}}</p>
    <p>性别:{{.Gender}}</p>
    <p>年龄:{{.Name}}</p>
</body>
</html>

同理,当我们传入的变量是map时,也可以在模板文件中通过.根据key来取值。

package main

import (
	"html/template"
	"net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
	tmpl, _ := template.ParseFiles("hello.html")
	maps := make(map[string]string)
	maps["Name"] = "亚丝娜"
	tmpl.Execute(w, maps)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:9090",
	}
	http.HandleFunc("/tmpl", tmpl)
	server.ListenAndServe()
}
<!--hello.html-->
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Go Web</title>
</head>
<body>
hello {{.Name}}
</body>
</html>

注释

{{/* a comment */}}注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。

package main

import (
   "html/template"
   "net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
   tmpl, _ := template.ParseFiles("hello.html")
   maps := make(map[string]string)
   maps["Name"] = "亚丝娜"
   tmpl.Execute(w, maps)
}

func main() {
   server := http.Server{
      Addr: "127.0.0.1:9090",
   }
   http.HandleFunc("/tmpl", tmpl)
   server.ListenAndServe()
}
<!--hello.html-->
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Go Web</title>
</head>
<body>
{{/* a comment */}}
hello {{.Name}}
</body>
</html>

效果图,注意,多了一个空行

去除空白

使用上面的注释发现多出了一行空行,此时就来讲解如何去除空行,已经更广的如何去除空白。

template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,对于要解析的内容,不要随意缩进、随意换行

可以在{{`符号的后面加上短横线并保留一个或多个空格`- `来去除它前面的空白(包括换行符、制表符、空格等),即`{{- xxxx`。 如

<!--hello.html-->
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Go Web</title>
</head>
<body>
{{23}} < {{45}}        -> 23 < {{23}} < {{- 45}}      ->  23 <{{23 -}} < {{45}}      ->  23< {{23 -}} < {{- 45}}    ->  23<
	{{- 23}}
</body>
</html>
package main

import (
	"html/template"
	"net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
	tmpl, _ := template.ParseFiles("hello.html")
	tmpl.Execute(w, "")
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:9090",
	}
	http.HandleFunc("/tmpl", tmpl)
	server.ListenAndServe()
}
可以看到 1. `{{23}} < {{45}}
渲染后是23 < 45保留了原有的空格

  1. {{45}} ->中这么大段的空格只保留了一个
  2. {{23}} < {{- 45}}渲染后是23 <45不仅-没了,而且少了<{{`间的所有空白(可以自行实验,多点空白会怎样,是不是全部去除) 4. `{{23 -}} < {{45}}渲染后是23< 45而且少了
    }}<间的所有空白(和上一条其实一样)
  3. 如果你细心就会发现这里写的是空白,即回车,空格,制表符等都会被清除到只剩一个
  4. 而这里要注意,用于缩进的制表符,经验证在 text/template 中不会被替换,但是在 html/template 中会被替换

到现在你也知道应该怎么去除空行了,但是要注意,两边都去掉会出现并行现象(如果你发现怎么样都是并行的,那么说明你在看浏览器,而不是在看源代码(⓿_⓿)

去空白前

去空白后

两边都去掉

pipeline(管道)

其实前面提到的{{.}}就是管道,管道是指产生数据的操作。

Go的模板语法中支持使用管道符号|链接多个命令,用法:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。

注意 : 并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。

比如

<!--hello.html-->
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Go Web</title>
</head>
<body>
{{ . | printf "%s\n" "abcd" }}
</body>
</html>
package main

import (
	"html/template"
	"net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
	tmpl, _ := template.ParseFiles("hello.html")
	tmpl.Execute(w, "asd")
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:9090",
	}
	http.HandleFunc("/tmpl", tmpl)
	server.ListenAndServe()
}

变量

Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下:

$variable := pipeline

其中$variable是变量的名字。声明变量的action不会产生任何输出。

<!--hello.html-->
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Go Web</title>
</head>
<body>
{{    $variable :=. }}
{{ printf $variable }}
</body>
</html>
package main

import (
	"html/template"
	"net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
	tmpl, _ := template.ParseFiles("hello.html")
	tmpl.Execute(w, "asd")
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:9090",
	}
	http.HandleFunc("/tmpl", tmpl)
	server.ListenAndServe()
}

条件判断

Go模板语法中的条件判断有以下几种:

{{if pipeline}} T1 {{end}}

{{if pipeline}} T1 {{else}} T0 {{end}}

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

需要注意的是,pipeline 为 false 的情况是各种数据对象的 0 值:数值 0,指针或接口是 nil,数组、slice、map 或 string 则是 len 为0。

range

Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。

{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为 0,不会有任何输出

{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为 0,则会执行 T0。

range可以迭代slice、数组、map或channel。迭代的时候,会设置”.”为当前正在迭代的元素。

对于第一个表达式,当迭代对象的值为 0 值时,则 range 直接跳过,就像 if 一样。对于第二个表达式,则在迭代到 0 值时执行 else 语句。

需注意的是,range的参数部分是pipeline,所以在迭代的过程中是可以进行赋值的。但有两种赋值情况:

{{range $value := .}}
{{range $key,$value := .}}

如果range中只赋值给一个变量,则这个变量是当前正在迭代元素的值(在不赋值的情况下其实是用.表示当前迭代元素的值)。如果赋值给两个变量,则第一个变量是索引值(map/slice是数值,map是key),第二个变量是当前正在迭代元素的值。

例如

package main

import (
	"html/template"
	"net/http"
)

func main() {
	server := http.Server{
		Addr: "127.0.0.1:9090",
	}
	http.HandleFunc("/tmpl", process)
	server.ListenAndServe()
}

func process(w http.ResponseWriter, r *http.Request) {
	t1 := template.Must(template.ParseFiles("hello.html"))
	s := []string{
		"星期一",
		"星期二",
		"星期三",
		"星期四",
		"星期五",
		"星期六",
		"星期日"}
	t1.Execute(w, s)
}
<!--hello.html-->
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Go Web</title>
</head>
<body>
<ul>
	{{ range . }}
	<li>{{ . }}</li>
	{{ else }}
	<li> Nothing to show </li>
	{{ end}}
</ul>
</body>
</html>

with

{{with pipeline}} T1 {{end}}
如果pipeline为 0,不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。

{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为 0,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。

这个其实就是上面 if 的变形,没啥好写的。就是局部判 0 加局部变量的使用罢了。

预定义函数

执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。

预定义的全局函数如下:

and
    函数返回它的第一个empty参数或者最后一个参数;
    就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or
    返回第一个非empty参数或者最后一个参数;
    亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not
    返回它的单个参数的布尔值的否定
len
    返回它的参数的整数类型长度
index
    执行结果为第一个参数以剩下的参数为索引/键指向的值;
    如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print
    即fmt.Sprint
printf
    即fmt.Sprintf
println
    即fmt.Sprintln
html
    返回其参数文本表示的HTML逸码等价表示。
urlquery
    返回其参数文本表示的可嵌入URL查询的逸码等价表示。
js
    返回其参数文本表示的JavaScript逸码等价表示。
call
    执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
    其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    该函数类型值必须有12个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的errornil,模板执行会中断并返回给调用模板执行者该错误;

1.1.5. 比较函数

布尔函数会将任何类型的零值视为假,其余视为真。

下面是定义为函数的二元比较运算的集合:

eq      如果arg1 == arg2则返回真
ne      如果arg1 != arg2则返回真
lt      如果arg1 < arg2则返回真
le      如果arg1 <= arg2则返回真
gt      如果arg1 > arg2则返回真
ge      如果arg1 >= arg2则返回真

为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:

{{eq arg1 arg2 arg3}}

比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。但是,整数和浮点数不能互相比较。

1.1.6. 自定义函数

Go的模板支持自定义函数。

func sayHello(w http.ResponseWriter, r *http.Request) {
    htmlByte, err := ioutil.ReadFile("./hello.html")
    if err != nil {
        fmt.Println("read html failed, err:", err)
        return
    }
    // 自定义一个夸人的模板函数
    kua := func(arg string) (string, error) {
        return arg + "真帅", nil
    }
    // 采用链式操作在Parse之前调用Funcs添加自定义的kua函数
    tmpl, err := template.New("hello").Funcs(template.FuncMap{"kua": kua}).Parse(string(htmlByte))
    //其实就是 temol=tmpl.Funcs(template.FuncMap{"kua": kua})
    if err != nil {
        fmt.Println("create template failed, err:", err)
        return
    }

    user := UserInfo{
        Name:   "枯藤",
        Gender: "男",
        Age:    18,
    }
    // 使用user渲染模板,并将结果写入w
    tmpl.Execute(w, user)
}

我们可以在模板文件hello.html中使用我们自定义的kua函数了。

{{kua .Name}}

注意事项

参考文献


如果本文帮助到了你,帮我点个广告可以咩(o′┏▽┓`o)


文章作者: Anubis
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Anubis !
评论
  目录