Go --- html/template模板包的使用

这里说的是go 语言中自带的包html/template里的一些基本操作

当然还有text/template,也是个模板包,但是这个并不是网页安全的,如果 往模板上传的是标签,它是会将标签转成html格式的,这一般来说是不允许的。

在本博客中所有的例子都是经过go build,打包成可执行文件执行的,如果使用goland启动标志其中会导致找不到包而报错。如果过真的想用启动标志启动,建议改下解析文件模板的路径。
例子码云地址:go-templateLearn


## 简单使用

要使用模板需要分三个步骤,分别是定义、解析和渲染,下面咱一步一步来

  1. 定义

    创建一个.tmpl或是.tpl文件,在goland中第一次创建这种类型的文件他会让你选用什么文件的格式去提示这类文件,这时候选择 go template files。

在这里插入图片描述

  • test.tmpl

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <title>测试</title>
    </head>
    <body>
        {{ . }}<br>
        {{ .name}}<br>
        {{ .sex}}
    </body>
    </html>
    

    其中{{ }} 是模板里的标识符,该标识符可能会与Vue中某些代码冲突,后面会提供修改默认标识符的方法,在表示符中的 . 代表着数据,这个数据是从后台传来的。

  1. 解析

    t, err := template.ParseFiles("./test.tmpl")
    

    将刚才定义的模板文件解析到程序中,上方调用的方法是解析多个文件

    解析方法

    // 解析多个文件
    func ParseFiles(filenames ...string) (*Template, error) {
    	return parseFiles(nil, readFileOS, filenames...)
    }
    // 通过正则表达式解析多个文件
    func ParseGlob(pattern string) (*Template, error) {
    	return parseGlob(nil, pattern)
    }
    // 类似于上面两种方法,但是是从文件系统fs读取,而不是主机操作系统的文件系统。
    func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
    	return parseFS(nil, fs, patterns)
    }
    
  2. 渲染

    	mp := map[string]interface{}{
    		"name": "张三",
    		"sex": "男",
    	}
    	err = t.Execute(w, mp)
    

    将数据渲染到刚解析的模板中

    // 可以看出这个data是个空接口类型,也就意味着是什么值都可以传的
    func (t *Template) Execute(wr io.Writer, data interface{}) error {
    	if err := t.escape(); err != nil {
    		return err
    	}
    	return t.text.Execute(wr, data)
    }
    

    另外一种渲染方法,指定模板文件进行渲染,适用于有好多解析文件在一起时使用

    func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
    	tmpl, err := t.lookupAndEscapeTemplate(name)
    	if err != nil {
    		return err
    	}
    	return tmpl.text.Execute(wr, data)
    }
    
    • main.go

      package main
      
      import (
      	"fmt"
      	"html/template"
      	"net/http"
      )
      
      func sayHello(w http.ResponseWriter,r *http.Request)  {
      	t, err := template.ParseFiles("./test.tmpl")
      	if err != nil {
      		fmt.Printf("parse file failed err := %v",err)
      	}
      	mp := map[string]interface{}{
      		"name": "张三",
      		"sex": "男",
      	}
      	err = t.Execute(w, mp)
      	if err != nil {
      		fmt.Printf("execute file failed err := %v",err)
      	}
      
      }
      
      func main() {
      	http.HandleFunc("/",sayHello)
      	err := http.ListenAndServe(":9000", nil)
      	if err != nil {
      		fmt.Printf("listen address failed err = %v",err)
      	}
      }
      

    注释

    注释使用的符号为{{/* */}},支持多行注释,如

    {{/*
    	注释内容
    */}}
    

    变量的使用

    在模板中使用变量是利用 $ 符号

    赋值

    {{ $obj := 数据 }}
    

    声明变量之后就可以在模板文件中使用了。

    例子:

    test.tmpl

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <title>变量使用</title>
    </head>
    <body>
    
        MSG :{{ . }}<br>
        姓名 :{{ .Name }}<br>
        性别 : {{ .Sex }}<br>
    {{/*    使用变量  */}}
        <div>
        {{ $num := "123" }}
        年岁 :{{ $num }}
        </div>
    </body>
    </html>
    

    main.go

    package main
    
    import (
    	"fmt"
    	"html/template"
    	"net/http"
    )
    
    func sayHello(w http.ResponseWriter,r *http.Request)  {
    	t, err := template.ParseFiles("./test.tmpl")
    	if err != nil {
    		fmt.Printf("parse file failed err := %v",err)
    	}
    	err = t.Execute(w, struct {
    		Name string
    		Sex  string
    	}{
    		Name: "张三",
    		Sex: "女",
    	})
    	if err != nil {
    		fmt.Printf("execute file failed err := %v",err)
    	}
    
    }
    
    func main() {
    	http.HandleFunc("/",sayHello)
    	err := http.ListenAndServe(":9000", nil)
    	if err != nil {
    		fmt.Printf("listen address failed err = %v",err)
    	}
    }
    

​ 这里要注意一点,就是当传给模板的数据为结构体时,根据go语言的特性,属性名小写的属性外界将获取不到值。

判断与清楚空白符操作

判断

在模板中的判断语句写法同go语言类似

{{ if [比较函数] 变量 [比较对象] }}
	如果为真要执行的语句
{{ end }}

如果是只有变量的话,就判断变量是否存在值,如果存在就执行。

也可以使用 if … else … 语句,或if … else if … 语句,如

{{ if [比较函数] 变量 [比较对象] }}

{{ else }}

{{ end }}
{{ if [比较函数] 变量 [比较对象] }}

{{ else if [比较函数] 变量 [比较对象]}}

{{ end }}

去空白字符

使用

清楚变量左右两侧的空白符号,当然也可以只清除一侧,只需要将不需要清除的一侧的 - 舍去
{{- -}}

例子:

test.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>判断与清楚空白符操作</title>
</head>
<body>

    MSG :{{ . }}<br>
    姓名 :{{ .Name }}<br>
    性别 : {{ .Sex }}<br>
    {{ $num := .Age }}
    年龄 :{{ $num }}
{{/*    判断使用 注意,要先写条件之后跟上比较对象*/}}
{{/*   比较函数
eq   ==       eq可以进行多个值比较 如 eq n1 n2 n3 ,就会拿n1 分别跟n2,n3比较
ne   !=
lt   <
le   <=
gt   >
ge   >=
*/}}
    <div>
    {{ if lt $num 18 }}
        好好吃饭
    {{ else if ge $num 18 }}
        别在熬夜了
    {{end}}
    </div>
{{/* {{- -}} 取出空白符的符号使用*/}}
    {{ $num }} = {{ $num }}<br>
{{/*    取出空白符,让左侧或右侧能与其他的文本贴贴*/}}
    {{ $num -}} = {{- $num }}
</body>
</html>

main.go

package main

import (
	"fmt"
	"html/template"
	"net/http"
	"os"
	"path/filepath"
)

func sayHello(w http.ResponseWriter,r *http.Request)  {
	// ./ 代表项目路径
	t, err := template.ParseFiles("./test.tmpl")

	if err != nil {
		fmt.Printf("parse file failed err := %v",err)
	}
	err = t.Execute(w, struct {
		Name string
		Sex  string
		Age  int
	}{
		Name: "                 张三  ",
		Sex: "女",
		Age: 20,
	})
	if err != nil {
		fmt.Printf("execute file failed err := %v",err)
	}

}

func main() {
	http.HandleFunc("/",sayHello)
	root := filepath.Dir(os.Args[0])
	fmt.Println(root)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

循环、with和与预定义函数的使用

循环

使用range关键字进行遍历

这个变量只能是数组、切片、map或者通道
{{ range 变量 }}

{{ end }}
{{ range $index,$valuse = 变量 }}

{{ end }}

range中也可以使用else语句,如果所遍历的这个变量长度为0,则执行else语句

{{ range 变量 }}

{{ else }}

{{ end }}

with

with的作用为重新定义 . 所代表的数据,这个重新定义有一个范围,只有在范围内 . 才代表with重新定义的那个数据

{{ with 变量 }}
在这中间 . 都将被替换为变量的数据
{{ end }}

当赋值的变量为空时,可以使用 else 语句来检测 .有没有被重新赋值

{{ with 变量 }}

{{ else }}
如果变量为空则执行这里的语句
{{ end }}

预定义函数

模板中的预定义函数有:

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。
    这个函数在html/template中不可用。
urlquery
    以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
    这个函数在html/template中不可用。
js
    返回与其参数的文本表示形式等效的转义JavaScript。
call
    执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
    其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;

例子:

test.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>循环,with和预定义函数的使用</title>
</head>
<body>
{{/* 循环 */}}
    {{ range $index,$v1 := . }}
    下标:{{ $index }}
    姓名:{{ $v1 }}
        <br>
    {{ end }}
<hr>
{{/* with 更改点的值*/}}
    开始时:{{ . }}<br>
    {{ with "斗地主研讨会"}}
        转换后:{{ . }}
    {{ end }}
<hr>
{{/* 预定义函数 */}}
{{/*
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。
    这个函数在html/template中不可用。
urlquery
    以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
    这个函数在html/template中不可用。
js
    返回与其参数的文本表示形式等效的转义JavaScript。
call
    执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
    其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
*/}}
    研讨会人数:{{ len . }}
</body>
</html>

main.go

package main

import (
	"fmt"
	"html/template"
	"net/http"
	"os"
	"path/filepath"
)

func sayHello(w http.ResponseWriter,r *http.Request)  {
	// ./ 代表项目路径
	t, err := template.ParseFiles("./test.tmpl")

	if err != nil {
		fmt.Printf("parse file failed err := %v",err)
	}
	strings := []string{"张安","潘凤","李翔"}
	err = t.Execute(w,strings)
	if err != nil {
		fmt.Printf("execute file failed err := %v",err)
	}

}

func main() {
	http.HandleFunc("/",sayHello)
	root := filepath.Dir(os.Args[0])
	fmt.Println(root)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

自定义函数

若是觉得模板中的预定义函数不够用,这时候就需要定义自己的函数了。

我们一般不在模板中定义自己的函数,而是在渲染模板之前给定自己所定义的函数,这将使用了Fancs方法。

scold := func(a string)  string {
		return a + "TNND"
	}
// 这个New就是用给的这个名字重新分配一个模板
// 调用Funcs方法前要先调用New方法
	t, err := template.New("test").Funcs(template.FuncMap{"scold" : scold}).ParseFiles("./test.tmpl")

Funcs方法:

// 可以看出,在Funcs中要传入一个 map[string]interface{} ,
// 其中是string代表的自定义的方法在模板中叫啥名
// 第二个空接口穿的应该是自定义的函数
func (t *Template) Funcs(funcMap FuncMap) *Template {
	t.text.Funcs(template.FuncMap(funcMap))
	return t
}
type FuncMap map[string]interface{}

在模板中使用自定义函数时,要用到管道的方法

{{ a | b }}
这种写法的意思是将 a 的输出通过管道再作为 b 的输入,最后呈现出来的是 b 的输出

例子:

test.tmpl

{{ define "test" }}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>自定义函数</title>
</head>

<body>
    {{/*
       {{ a | b }}
       这种写法的意思是将 a 的输出通过管道再作为 b 的输入,最后呈现出来的是 b 的输出
     */}}
    {{ . | scold }}
</body>
</html>
{{end}}

main.go

package main

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

func sayHello(w http.ResponseWriter,r *http.Request)  {
	scold := func(a string)  string {
		return a + "TNND"
	}
	t, err := template.New("test").Funcs(template.FuncMap{"scold" : scold}).ParseFiles("./test.tmpl")
	if err != nil {
		fmt.Printf("parse file failed err := %v",err)
	}
	err = t.Execute(w, "为什么不喝,")
	if err != nil {
		fmt.Printf("execute file failed err := %v",err)
	}

}

func main() {
	http.HandleFunc("/",sayHello)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

嵌套

就是在一个模板中嵌套另外的一个模板。

需要使用的语法为

在需要嵌套的地方
{{ template 模板名 . }}
其中,这个 . 是在模板中传递数据用的

例子:

test.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>嵌套</title>
</head>
<body>
    ul :<br>
    {{ template "ul.tmpl" . }}
    <hr>
    ol :<br>
    {{ template "ol.tmpl" . }}
</body>
</html>

ol.tmpl

<ol>
    <li>{{ .name }}</li>
    <li>{{ .sex }}</li>
</ol>

ul.tmpl

<ul>
    <li>{{ .name }}</li>
    <li>{{ .sex }}</li>
</ul>

main.go

package main

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

func nest(w http.ResponseWriter,r *http.Request)  {
	// 不能写成template.ParseFiles("./ul.tmpl","./test.tmpl","./ol.tmpl")
	// 因为是test.tmpl是主模板,ul.tmpl和ol.tmpl需要等主模板解析完之后在解析
	t, err := template.ParseFiles("./test.tmpl","./ul.tmpl","./ol.tmpl")
	if err != nil {
		fmt.Printf("parse file failed err := %v",err)
	}
	mp := map[string]interface{}{
		"name": "张大炮",
		"sex": "女",
	}
	err = t.Execute(w, mp)
	if err != nil {
		fmt.Printf("execute file failed err := %v",err)
	}
}

func main() {
	http.HandleFunc("/",nest)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

填空 block

相当于继承,一个网页项目会有一些公共的页面信息,为了不每个页面都将这些信息写上一边,就要有根模板,这时候其他的页面就需要继承根模板。

使用block

将数据传递给该模板名的模板
{{ block 模板名 数据}}

{{ end }}

在使用时一定要引入根模板

用 . 来接受根模板传来的所有数据
{{ template 跟模板名 .}}

例子:

test.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>填空</title>
</head>
<body>
    <div>
        <p style="text-align: center">
            {{ block "content" .}}
            {{ end }}
        </p>
    </div>
</body>
</html>

content.tmpl

{{ template "test.tmpl" .}}

{{/*
一个项目里面肯定要有好些个功能,如果每一个功能都有一些相同作用的文件,
这样就很难保证这些文件不会出现重复的问题,
有两个解决方案,
1. 在每一个模板上方定义一个名字
2. 创建一个包,包里分层放,不同层代表着不同的功能 然后使用template.ParseGlob() 正则解析
*/}}
{{ define "content" }}
    {{ . }}
{{end}}

main.go

package main

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

func sayHello(w http.ResponseWriter,r *http.Request)  {
	t, err := template.ParseFiles("./test.tmpl","./content.tmpl")
	if err != nil {
		fmt.Printf("parse file failed err := %v",err)
	}
	err = t.ExecuteTemplate(w,"content", "这都是大棚的瓜")
	if err != nil {
		fmt.Printf("execute file failed err := %v",err)
	}

}

func main() {
	http.HandleFunc("/",sayHello)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

安全测试

就像开头所说,html/template这个包传递给的模板的数据会进行安全处理。

测试:

test.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>安全测试</title>
</head>
<body>
{{/*并不会出现执行标签的现象*/}}
    弹窗:{{ .msg }}<br>
    链接1:{{ .a }}<br>
    链接2:{{ .a | trust }}<br>
</body>
</html>

main.go

package main

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

func sayHello(w http.ResponseWriter,r *http.Request)  {
	t, err := template.New("test.tmpl").Funcs(template.FuncMap{
		"trust": func(s string) template.HTML {
			return template.HTML(s)
		},
	}).ParseFiles("./test.tmpl")
	if err != nil {
		fmt.Printf("parse file failed err := %v",err)
	}
	m := map[string]interface{}{
		"msg":"<script>alert('张三撺掇着李四去王五家打了赵六')</script>",
		"a": "<a href='https://blog.csdn.net/weixin_52025712'>本人博客</a>",
	}
	err = t.Execute(w, m)
	if err != nil {
		fmt.Printf("execute file failed err := %v",err)
	}

}

func main() {
	http.HandleFunc("/",sayHello)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

修改默认标识符

模板中默认标识符 {{ }} 可能会与其他语言中的表示符冲突,这时候我们就要重新定义默认标识符,需要使用Delims方法

// 修改默认标识符为 [ ]
t, err := template.New("test.tmpl").Delims("[","]").ParseFiles("./test.tmpl")

Delims:

// left和rigth分别代表标识符左右两个部分
func (t *Template) Delims(left, right string) *Template {
	t.text.Delims(left, right)
	return t
}

例子:

test.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>修改默认标识符</title>
</head>
<body>
    [ . ]
</body>
</html>

main.go

package main

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

func sayHello(w http.ResponseWriter,r *http.Request)  {
	t, err := template.New("test.tmpl").Delims("[","]").ParseFiles("./test.tmpl")
	if err != nil {
		fmt.Printf("parse file failed err := %v",err)
	}
	err = t.Execute(w, "hello")
	if err != nil {
		fmt.Printf("execute file failed err := %v",err)
	}

}

func main() {
	http.HandleFunc("/",sayHello)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

一个简单的表单传值的例子

get.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>表单传值</title>
</head>
<body>
    <form action="/" method="post">
        <label>
            <input type="text" name="name">
        </label><br>
        <label>
            <input type="text" name="age">
        </label><br>
        <label>
            <input type="radio" name="sex" value="">
        </label><br>
        <label>
            <input type="radio" name="sex" value="">
        </label><br>
    <button type="submit">提交</button>
</form>

</body>
</html>

post.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>接受表单传过来的值</title>
</head>
<body>
    姓名:{{ .name }}<br>
    性别:{{ .sex }}<br>
    年龄:{{ .age }}
</body>
</html>

main.go

package main

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

func form(w http.ResponseWriter,r *http.Request)  {
	switch r.Method {
	case "GET":
		t, err := template.ParseFiles("./get.tmpl")
		if err != nil {
			fmt.Printf("parse file failed err := %v",err)
		}
		err = t.Execute(w, nil)
		if err != nil {
			fmt.Printf("execute file failed err := %v",err)
		}
	case "POST":
		t, err := template.ParseFiles("./post.tmpl")
		if err != nil {
			fmt.Printf("parse file failed err := %v",err)
		}
		name := r.FormValue("name")
		//fmt.Printf("name = %s\n",name)
		sex := r.FormValue("sex")
		//fmt.Printf("sex = %s\n",sex)
		age := r.FormValue("age")
		//fmt.Printf("age = %s\n",age)
		m := map[string]interface{}{
			"name":name,
			"sex":sex,
			"age":age,
		}
		err = t.Execute(w, m)
		if err != nil {
			fmt.Printf("execute file failed err := %v",err)
		}
	}


}

func main() {
	// 配置路由
	http.HandleFunc("/",form)

	// 启动
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("listen address failed err = %v",err)
	}
}

参考文章:

  1. 李文周的博客

  2. xyz098的简书

希望大家能多去支持一下上面的两位大佬

最后再向大家推荐一首今天在单曲循环的歌遇见 西安话版 吴昊晨

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值