Go语言的template
阅读原文时间:2023年07月08日阅读:3

html/template包实现了数据驱动的模板,用于生成可防止代码注入的安全的HTML内容。它提供了和 text/template包相同的接口,Go语言中输出HTML的场景都应使用html/template这个包。

html/template会自动帮你转义掉有风险的代码,避免xss攻击

模板需要注意的

  • 模板文件通常定义为.tmpl和.tpl为后缀(也可以使用其他的后缀),必须使用UTF8编码。
  • 模板文件中使用{{和}}包裹和标识需要传入的数据。
  • 传给模板这样的数据就可以通过点号(.)来访问,如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。
  • 除{{和}}包裹的内容外,其他内容均不做修改原样输出。
  • 模板三步

模板文件

{{/*定义模板*/}}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>hello</title>
</head>
<body>
<p>hello {{ . }}</p>
</body>
</html>

go服务端

package main

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

func hello(w http.ResponseWriter, r *http.Request) {
    //打开模板
    t, err := template.ParseFiles("template/hello.tmpl")
    if err != nil {
        return
    }
    //渲染模板
    name := "hello"
    err := t.Execute(w, name)
    if err != nil {
        return
    }
}

func main() {
    http.HandleFunc("/", hello)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        return
    }
}

map作为参数传入,直接使用参数访问

服务端

m1 := map[string]interface{}{
        "name":   "ll",
        "gender": "female",
        "age":    0,
    }

模板文件

<p>hello {{ .name }}</p>
<p>hello {{ .age }}</p>
<p>hello {{ .gender }}</p>

结构体传入

名称必须是要大写才能被外界访问到

type User struct {
    Name   string
    Gender string
    Age    int
}

u1 := User{
Name:   "ll",
Gender: "female",
Age:    0,
}

模板文件

<p>hello {{ .Name }}</p>
<p>hello {{ .Age }}</p>
<p>hello {{ .Gender }}</p>

想要一次传入多个参数,可以使用map来进行传入

u1 := User{
       Name:   "struct_para",
       Gender: "female",
       Age:    0,
   }
   m1 := map[string]interface{}{
       "name":   "map_para",
       "gender": "female",
       "age":    0,
   }
   err = t.Execute(w, map[string]interface{}{
       "struct":  u1,
       "map_itf": m1,
   })

模板文件

<p>hello {{ .struct.Name }}</p>
<p>hello {{ .map_itf.name }}</p>

变量

{{$v := 114}}
{{$}}

移除空格

移除左右的空格
{{- .name -}}

条件判断

{{ $v1 := 114 }}
{{if $v1}}
{{$v1}}
{{else}}
Nothing
{{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,模板执行会中断并返回给调用模板执行者该错误;

比较函数

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

循环

{{range $idx,$hobby := .hobby}}
<p>{{$idx}} - {{$hobby}}</p>
{{else}}
    empty
{{end}}

with共同前缀

类似    var . = .struct
{{with .struct}}
<p>hello {{ .Name }}</p>
<p>hello {{ .Age }}</p>
<p>hello {{ .Gender }}</p>
{{end}}

自定义函数

len := func(str string) (int, error) {
    return len(str), nil
}
//渲染模板
//name := "hello"
t.Funcs(template.FuncMap{"len": len})

自定义函数用法

<p>hello {{ len .struct.Name }}</p>

模板嵌套

主模板

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>hello</title>
</head>
<body>

{{template "a.tmpl"}}
{{template "b.tmpl"}}
</body>
</html>
{{/*内部嵌套*/}}
{{define "a.tmpl"}}
    <a>
        <li>test_a_tmpl</li>
    </a>
{{end}}

外部嵌套

b.tmpl

<b>
    <li>test_b_tmpl</li>
</b>

服务端

package main

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

func hello(w http.ResponseWriter, r *http.Request) {
//先导入主文件 然后再导入嵌套文件
    t, err := template.ParseFiles("template/hello.tmpl", "template/b.tmpl")
    if err != nil {
        return
    }
    t.Execute(w, nil)
}

func main() {
    http.HandleFunc("/", hello)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        return
    }
}

block模板继承

利用根模板进行组合

根模板

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{ . }}</title>
    <style>
        *{
            margin: 0;
        }
        .nav{
            height: 50px;
            width: 100%;
            position: fixed;
            top: 0;

            background-color: yellow;
        }
        .main{
            margin-top: 50px;
        }
        .context{
            width: 20%;
            height: 100%;
            position: fixed;
            left: 0;
            background-color: blue;
        }
        .center {
         text-align: center;
        }

    </style>
</head>
<body>
<div class="nav"></div>
<div class="main">
    <div class="menu"></div>
    <div class="context center">
    {{block "content" .}}{{end}}
    </div>
</div>
</body>
</html>

继承模板

{{template "base.tmpl"}}
{{/*此处定义的模板要和上面的相同*/}}
{{define "content"}}
    <h1>home_page</h1>
{{end}}

服务端

先根模板后继承模板

func home2(w http.ResponseWriter, r *http.Request) {
    t, err := template.ParseFiles("template/base.tmpl", "template/test_home.tmpl")
    if err != nil {
        return
    }
    t.Execute(w, "home")
}

自定义标识符

防止和其他语言发生冲突

在解析模板的时候来定义

func test(w http.ResponseWriter, r *http.Request) {
    t,err := template.New("test.tmpl").
        Delims("{[", "]}").
        ParseFiles("template/test.tmpl")
    if err != nil{
        return
    }
    t.Execute(w,nil)
}
这样标识符就成了 {[ name ]}了

使用gin框架的template模板

package main

import (
    "github.com/gin-gonic/gin"
    "html/template"
    "net/http"
)

func main() {
    r := gin.Default()
    //静态文件 解析前添加路径
    r.Static("/name", "statics")
    //自定义函数 解析前添加
    r.SetFuncMap(template.FuncMap{
        "safe": func(str string) template.HTML {
            return template.HTML(str)
        },
    })
    //模板解析 ** -> 所有文件夹 * -> 所有文件
    r.LoadHTMLGlob("templates/**/*")
    //访问index的时候就执行后面的函数
    r.GET("/users/index", func(ctx *gin.Context) {
        //http请求
        ctx.HTML(http.StatusOK, "users/index.tmpl", gin.H{ //模板渲染
            "title": "moreac.top",
        })
    })
    r.GET("/posts/index", func(ctx *gin.Context) {
        //http请求
        ctx.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ //模板渲染
            "title": "moreac.top",
        })
    })

    err := r.Run(":9090")

    if err != nil {
        return
    }
}



{{define "posts/index.tmpl"}}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>posts/index</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}

静态文件处理

网页上面使用到的样式文件 .css .js pic等