zoukankan      html  css  js  c++  java
  • go template使用

    go template使用

    以text/template为例, 而html/template的接口与前者一样,不再缀述。

    模板文件一般由.tmpl.tpl为后缀。
    一些名词

    dot:用表示.,相当于一个变量,保存着传进来的值,可以改变
    pipeline:从字面上看,有点像管道|,但从文档上看,实际上指的是一切取值操作,包括{{ . }}{{ $name }},而|与unix中的一样:作为函数的最后一个参数
    {{ }}:相当于占位符,主要的逻辑都写在里面

    1 模板定义

    1.1 取值

    取值的作用主要是在页面中表示出来,或者使用一个变量保存

    类型 方式 解释
    当前值 {{ . }} 传什么值,就取什么值,假如直接在页面上输出的话,类似fmt.Println
    结构体 {{ .Field }} Field指的是字段名,假如结构体嵌套,还可以再使用.取值,注意:遵循可见性规则
    变量 {{ $varName }} $开头,取出变量的值,如何定义且看1.2
    字典 {{ .key }} 取字典key对应的值,不需要首字母大写,嵌套时,可以再使用.取值
    无参数方法 {{ .Method }} 执行Method这个方法,第一个返回值作为取出的值,注意:遵循可见性规则,而且返回值有要求,详细见xxx
    无参数函数 {{ func }} 执行func(),把返回值当做结果,详见xxx

    1.2 变量

    有些值,我们可能需要重复使用,最好的方法就是使用一个变量来保存值减少重复求值的过程。

    // 用到的数据
    name := "abcdef"

    假如我们把name传进来,那么假如要求其长度并将其保存起来,可以使用一个内置函数(见1.4):len
    在go template中,用$表示变量,有点类似shell,使用:

    {{ $lenght := len . }}
    <h1>长度:{{ $lenght }}</h1>

    实际上,还可以这样写:

    {{ $lenght := . | len }}
    <h1>长度:{{ $lenght }}</h1>

    利用|abcdef当做最后一个参数传给len

    1.3 动作

    go template的动作(action)有点像,django的模板引擎中的tag,不过两者之间还是有较大的不一样。

    1.3.1 注释

    注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止,就像这里表示的一样。
    {{/* 我是注释啊 */}}

    1.3.2 if判断

    有以下3种

    1. {{if pipeline}} T1 {{end}}
      如果pipeline的值为empty,不产生输出,否则输出T1执行结果。不改变dot的值。
      Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。
      如:
    <p>{{ if . }}welcome{{ end }}</p>

    在这里我传的是一个bool值,为true,因此p便签中的内容为welcome

    1. {{if pipeline}} T1 {{else}} T0 {{end}}
      如果pipeline的值为empty,输出T0执行结果,否则输出T1执行结果。不改变dot的值。
    <p>{{ if . }}welcome{{ else }}  Get out!{{ end }}</p>
    
    1. {{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
      用于简化if-else链条,else action可以直接包含另一个if;等价于:
      {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
    <p>{{ if eq . 1 }}count=1{{ else if eq . 2}}  count=2{{ else if eq . 3}}  count=3{{ end }}</p>

    这里的eq是一个内置函数,相当于==

    1.3.3 with

    这里的with与并不是Python的with。go template的with相当于可以暂时修改dot的if。

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

    {{ with gt . 18}} result:{{ . }}, 嘿嘿嘿 {{end}}

    这里的gt相当于>, 因此假如执行成功,那么.必然是true.

    形式二{{with pipeline}} T1 {{else}} T0 {{end}}
    如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。不修改外面的dot。
    实际上这和上面的一样,就是多了个{{ else }}

    {{ with gt . 18}} result:{{ . }}, 嘿嘿嘿 {{else}}{{ . }}岁,未成年 {{end}}

    1.3.4 遍历

    遍历的值必须是数组、切片、字典或者通道。

    1. 简单形式: {{range pipeline}} T1 {{end}}

    如果pipeline的值其长度为0,不会有任何输出;
    否则dot依次设为数组、切片、字典或者通道的每一个成员元素并执行T1;
    如果pipeline的值为字典,且键可排序的基本类型,元素也会按键的顺序排序。
    如,要遍历的数据如下:

    data := map[string]string{
    		"张三": "hello",
    		"李四": "word",
    	}

    在模板文件中定义:

    <div>
        {{ range . }}
        <p>{{ . }}</p>
        {{ end }}
    </div>

    所得到的结果:

    <div>
        <p>hello</p>
        <p>word</p>
    </div>
    1. 加else形式:{{range pipeline}} T1 {{else}} T0 {{end}}
      如果pipeline的值其长度为0,不改变dot的值并执行T0;否则会修改dot并执行T1。
      假如数据是一个空切片[]int{}
    <div>
        {{ range . }}
            <p>{{ . }}</p>
        {{ else }}
            <span>no data</span>
        {{ end }}
    </div>

    结果是<span>no data</span>

    1.3.5 嵌套与继承

    define

    当解析模板时,可以定义另一个模板,该模板会和当前解析的模板相关联。模板必须定义在当前模板的最顶层,就像go程序里的全局变量一样。
    这种定义模板的语法是将每一个子模板的声明放在"define"和"end" action内部。
    如:

    {{ define "rd"}}
        <div>
        {{ range . }}
            <p>{{ . }}</p>
        {{ else }}
            <span>v2 no data</span>
        {{ end }}
    </div>
    {{ end }}

    注意:结尾{{ end }}define后面的是字符串

    template

    template就是对define定义的模板或其他模板文件的引用。
    template的形式

    • {{template "name"}}
      执行名为name的模板,提供给模板的参数为nil,如模板不存在输出为""
    • {{template "name" pipeline}}
      执行名为name的模板,提供给模板的参数为pipeline的值。

    如,在当前文件中引用:

    {{ define "rd"}}
        <div>
        {{ range . }}
            <p>{{ . }}</p>
        {{ else }}
            <span>v2 no data</span>
        {{ end }}
    </div>
    {{ end }}
    
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <title>template</title>
    </head>
    
    <body>
    {{/*引用*/}}
    {{ template "rd"}}
    </body>
    </html>

    template引用其他文件注意:

    1. 千万注意,要在代码中把文件读进来。
      t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl")
      也可以使用其他函数
    2. template后面跟的是完整的文件名
      在h1.tpl中:{{ template "h2.tpl" }}

    {{template "name" pipeline}}形式
    就是把define中或文件中的.替换成template传进去的值,假如不指定的话,使用当前文件的.

    {{ define "say"}}
        <h1>say {{ . }}</h1>
    {{ end }}
    {{ template "say" "hi"}}

    结果:<h1>say hi</h1>

    block

    block是定义模板{{define "name"}} T1 {{end}}和执行{{template "name" pipeline}}缩写,典型的用法是定义一组根模板,然后通过在其中重新定义块模板进行自定义。
    如,在./templates/base.tpl中,定义:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <title>Go Templates</title>
    </head>
    <body>
    <div class="container-fluid">
        {{block "content" . }}{{end}}
    </div>
    </body>
    </html>

    而在其他的模板文件中:

    {{template "base.tpl"}}
    
    {{/* 使用 */}}
    {{define "content"}}
    	<!-- 写入自己的代码 -->
        <div>Hello world!</div>
    {{end}}

    同样要注意,在解析文件时把多个模板文件传进来

    1.3.6 去空

    {{- . -}}
    使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。

    1.4 函数

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

    1.4.1 一般函数

    • and
      函数返回它的第一个empty参数或者最后一个参数;
      就是说"and x y"等价于"if x then y else x";所有参数都会执行;
      和上面一样:Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。下面再重复

    如:

    {{ and 1 0 }}
    {{/* 返回0 */}}
    
    {{ and 1 1 1}}
    {{/* 返回1 */}}
    • or
      返回第一个非empty参数或者最后一个参数;
      亦即"or x y"等价于"if x then x else y";所有参数都会执行;

    如:

    {{ or 1 0 }}
    {{/* 返回1 */}}
    
    {{ or 0 2 1}}
    {{/* 返回2 */}}
    • not
      返回它的单个参数的布尔值的否定

    如:

    {{ not 1 }}
    {{/* 返回false */}}
    
    {{ not 0 }}
    {{/* 返回true */}}
    • len
      返回它的参数的整数类型长度

    如:

    {{/*  . 为"abcdef"  */}}
    {{ len . }}
    
    {{/*  返回6  */}}
    • index
      执行结果为第一个参数以剩下的参数为索引/键指向的值;
      如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。

    假如数据为:

    	data := [][]int{
    		{1, 2, 3, 4, 5,},
    		{6, 7, 8, 9, 10,},
    	}
    {{ index . 0 1}}
    
    {{/* 结果为2 */
    • print
      即fmt.Sprint
      S系列函数会把传入的数据生成并返回一个字符串。以下两个相同。

    • 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,模板执行会中断并返回给调用模板执行者该错误;

    1.4.2 布尔函数

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

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

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

    arg1 == arg2 || arg1 ==arg3 || arg1==arg4 ...
    (和go的||不一样,不做惰性运算,所有参数都会执行)

    比较函数只适用于基本类型(或重定义的基本类型,如"type Celsius float32")。它们实现了go语言规则的值的比较,但具体的类型和大小会忽略掉,因此任意类型有符号整数值都可以互相比较;任意类型无符号整数值都可以互相比较;等等。但是,整数和浮点数不能互相比较。

    1.4.3 自定义函数

    使用Funcs方法,可以将自定义好的函数放入到模板中。
    Funcs的签名:
    func (t *Template) Funcs(funcMap FuncMap) *Template
    Funcs方法向模板t的函数字典里加入参数funcMap内的键值对。
    如果funcMap某个键值对的值不是函数类型或者返回值不符合要求会panic。
    但是,可以对t函数列表的成员进行重写。方法返回t以便进行链式调用。

    例子:
    例子中的使用的一些方法,见第2部分

    // 1. 定义函数,首字母可以小写,注意返回值
    func SayHi(char string) (string, error) {
    	return "Hi" + char, nil
    
    }
    
    func indexFunc(w http.ResponseWriter, r *http.Request) {
    	// 2. new
    	t := template.New("hello.tpl")
    	// 3. 加入t的函数列表,需要替换掉t
    	t = t.Funcs(template.FuncMap{"sayHi": SayHi})
    	// 4. Parse 可以是文件也可以是字符串
    	t, _ = t.ParseFiles("./hello.tpl")
    
    	userName := "xxx"
    	// 5. 渲染
    	_ = t.Execute(w, userName)
    }

    上面代码的2-4步,可以使用一段链式调用完成:

    	t, _ := template.New("hello.tpl").Funcs(template.FuncMap{"sayHi": SayHi}).ParseFiles("./hello.tpl")

    注意事项
    template.New的文件名应该和要渲染的文件名一样
    自定义函数有1-2个返回值,第一个值当做正式返回值。假如有第二个返回值:用来panic,其类型必须是error,当对应的值非nil时,panic

    2 一些常用的方法

    模板引擎的使用,一般有如下三步:

    1. 定义模板文件
    2. 解析模板文件
    3. 模板渲染

    其中,第2、3步都要用到一些template的方法(这里用的是text/template)

    2.1 解析模板文件的方法

    // 解析字符串
    func (t *Template) Parse(src string) (*Template, error)
    
    // 解析1个或多个文件
    func ParseFiles(filenames ...string) (*Template, error)
    
    // 解析用正则匹配到的文件
    func ParseGlob(pattern string) (*Template, error)

    使用

    1. Parse

    这里使用New函数:
    func New(name string) *Template
    其作用是创建一个名为name的模板。

    t, _ := template.New("test.tpl").Parse("<h1>{{ . }}</h1>")

    Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。
    如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;
    如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板。

    2. ParseFiles

    t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl", "./h3.tpl")

    解析匹配参数中的文件里的模板定义并将解析结果与t关联。
    如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要存在一个匹配的文件。

    3. ParseGlob

    t, _ := template.ParseGlob("./*.tpl")

    解析当前目录下,所有以.tpl结尾的文件,假如有专门的文件夹存放模板文件,可以使用templates/*.tmpl(1层目录时)和templates/**/*.tmpl(2层目录时)

    匹配时,和ParseFiles一样。

    2.2 模板渲染的方法

    func (t *Template) Execute(wr io.Writer, data interface{}) error
    // Execute方法将解析好的模板应用到data上,并将输出写入wr。
    // 如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。
    // 模板可以安全的并发执行。
    
    func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
    // 类似Execute,但是使用名为namet关联的模板产生输出。

    Execute渲染的是ParseFilesParseGlob得到的第一个文件,假如要读取多个文件时,就有可能渲染的不是想要的文件,所以需要使用ExecuteTemplate指定一个已经解析的文件。

    如:

    t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl")
    userName := "xxx"
    _ = t.Execute(w, userName)

    怎么样都是渲染h1.tpl,假如要渲染h2.tpl:

    t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl")
    userName := "xxx"
    _ = t.ExecuteTemplate(w, "h2.tpl", userName)

    注意
    ExecuteTemplate的name可以是define的模块名
    如:

    t, _ := template.New("test").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
    _ = t.ExecuteTemplate(out, "T", "word")

    当然,用其他解析方法也可以。

    3 html/template的不同之处

    由于html/template的API和text/template的API是一样的,解析和渲染没有什么不一样,但是在定义模板时,考虑到网站的安全性,会对一些风险内容进行转义,因此会有text/template有点差别。

    如:

    t, _ := template.New("test").Parse("{{ . }}")
    char := "<script>alert('you have been pwned')</script>!"
    _ = t.Execute(w, char)

    得到的结果是:&lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
    与预期不符,为此,html/template有一个函数可以专门处理这些我们认为安全的字符串:template.HTML
    再使用时,我们可以自定义一个safe函数,和其他模板引擎一样,不对一些字符串转义。

    func safe(s string) template.HTML {
    	return template.HTML(s)
    }

    然后使用:

    t, _ := template.New("test").Funcs(template.FuncMap{"safe": safe}).Parse("{{ . | safe }}")
    
    _ = t.Execute(w, char)

    补充

    如果{{.}}是非字符串类型的值,可以用于JavaScript上下文环境里:
    struct{A,B string}{ "foo", "bar" }
    将该值应用在在转义后的模板里:

    <script>var pair = {{.}};</script>
    模板输出为:
    <script>var pair = {"A": "foo", "B": "bar"};</script>

    参考:

    1. https://studygolang.com/static/pkgdoc/pkg/text_template.htm
    2. https://www.liwenzhou.com/posts/Go/go_template/

    我的github
    我的博客
    我的笔记

  • 相关阅读:
    版本管理工具:linux下svn的基本使用
    驱动: 中断【3】为什么可能导致睡眠的函数都不能在中断上下文中使用呢?
    驱动:中断【2】中断处理程序、中断上下文中处理延时及一些函数的调用规则(调IIC中断驱动有感)
    驱动: 中断【1】linux中断流程
    驱动: oops
    嵌入式:nfs挂载开发板的几个陷阱
    字符设备驱动: register_chrdev和register_chrdev_region
    likely() and unlikely()
    windows desktop.ini
    高通电源管理qpnp-vm-bms驱动
  • 原文地址:https://www.cnblogs.com/lczmx/p/13268081.html
Copyright © 2011-2022 走看看