迷之信 發表於 2024-1-13 16:55:00

Go标准库:Go template用法详解

<p>本文只介绍template的语法和用法,关于template包的函数、方法、template的结构和原理,见:深入剖析Go template。</p>
<h2 id="入门示例">入门示例</h2>
<p>以下为test.html文件的内容,里面使用了一个template语法<code>{{.}}</code>。</p>
<pre><code class="language-xml">&lt;!DOCTYPE html&gt;
&lt;html&gt;
        &lt;head&gt;
                &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
                &lt;title&gt;Go Web&lt;/title&gt;
        &lt;/head&gt;
        &lt;body&gt;
                {{ . }}
        &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>以下是test.html同目录下的一个go web程序:</p>
<pre><code class="language-go">package main

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

func tmpl(w http.ResponseWriter, r *http.Request) {
        t1, err := template.ParseFiles("test.html")
        if err != nil {
                panic(err)
        }
        t1.Execute(w, "hello world")
}

func main() {
        server := http.Server{
                Addr: "127.0.0.1:8080",
        }
        http.HandleFunc("/tmpl", tmpl)
        server.ListenAndServe()
}
</code></pre>
<p>前面的html文件中使用了一个template的语法<code>{{.}}</code>,这部分是需要通过go的template引擎进行解析,然后替换成对应的内容。</p>
<p>在go程序中,handler函数中使用<code>template.ParseFiles("test.html")</code>,它会自动创建一个模板(关联到变量t1上),并解析一个或多个文本文件(不仅仅是html文件),解析之后就可以使用<code>Execute(w,"hello world")</code>去执行解析后的模板对象,执行过程是合并、替换的过程。例如上面的<code>{{.}}</code>中的<code>.</code>会替换成当前对象"hello world",并和其它纯字符串内容进行合并,最后写入w中,也就是发送到浏览器"hello world"。</p>
<p>本文不解释这些template包的函数、方法以及更底层的理论知识,本文只解释template的语法,如果觉得这些无法理解,或者看不懂官方手册,请看深入剖析Go template。</p>
<h2 id="关于点和作用域">关于点"."和作用域</h2>
<p>在写template的时候,会经常用到"."。比如<code>{{.}}</code>、<code>{{len .}}</code>、<code>{{.Name}}</code>、<code>{{$x.Name}}</code>等等。</p>
<p>在template中,点"."代表<strong>当前作用域的当前对象</strong>。它类似于java/c++的this关键字,类似于perl/python的self。如果了解perl,它更可以简单地理解为默认变量<code>$_</code>。</p>
<p>例如,前面示例test.html中<code>{{.}}</code>,这个点是顶级作用域范围内的,它代表<code>Execute(w,"hello worold")</code>的第二个参数"hello world"。也就是说它代表这个字符串对象。</p>
<p>再例如,有一个Person struct。</p>
<pre><code class="language-go">type Person struct {
        Name string
        Ageint
}

func main(){
        p := Person{"longshuai",23}
        tmpl, _ := template.New("test").Parse("Name: {{.Name}}, Age: {{.Age}}")
        _ = tmpl.Execute(os.Stdout, p)
}
</code></pre>
<p>这里<code>{{.Name}}</code>和<code>{{.Age}}</code>中的点"."代表的是顶级作用域的对象p,所以Execute()方法执行的时候,会将<code>{{.Name}}</code>替换成<code>p.Name</code>,同理<code>{{.Age}}</code>替换成<code>{{p.Age}}</code>。</p>
<p>但是并非只有一个顶级作用域,range、with、if等内置action都有自己的本地作用域。它们的用法后文解释,这里仅引入它们的作用域来解释"."。</p>
<p>例如下面的例子,如果看不懂也没关系,只要从中理解"."即可。</p>
<pre><code class="language-go">package main

import (
        "os"
        "text/template"
)

type Friend struct {
        Fname string
}
type Person struct {
        UserName string
        Emails   []string
        Friends[]*Friend
}

func main() {
        f1 := Friend{Fname: "xiaofang"}
        f2 := Friend{Fname: "wugui"}
        t := template.New("test")
        t = template.Must(t.Parse(
`hello {{.UserName}}!
{{ range .Emails }}
an email {{ . }}
{{- end }}
{{ with .Friends }}
{{- range . }}
my friend name is {{.Fname}}
{{- end }}
{{ end }}`))
        p := Person{UserName: "longshuai",
                Emails:[]string{"a1@qq.com", "a2@gmail.com"},
                Friends: []*Friend{&amp;f1, &amp;f2}}
        t.Execute(os.Stdout, p)
}
</code></pre>
<p>输出结果:</p>
<pre><code class="language-kotlin">hello longshuai!

an email a1@qq.com
an email a2@gmail.com

my friend name is xiaofang
my friend name is wugui
</code></pre>
<p>这里定义了一个Person结构,它有两个slice结构的字段。在Parse()方法中:</p>
<ul>
<li>顶级作用域的<code>{{.UserName}}</code>、<code>{{.Emails}}</code>、<code>{{.Friends}}</code>中的点都代表Execute()的第二个参数,也就是Person对象p,它们在执行的时候会分别被替换成p.UserName、p.Emails、p.Friends。</li>
<li>因为Emails和Friend字段都是可迭代的,在<code>{{range .Emails}}...{{end}}</code>这一段结构内部<code>an email {{.}}</code>,这个"."代表的是range迭代时的每个元素对象,也就是p.Emails这个slice中的每个元素。</li>
<li>同理,with结构内部<code>{{range .}}</code>的"."代表的是p.Friends,也就是各个,再此range中又有一层迭代,此内层<code>{{.Fname}}</code>的点代表Friend结构的实例,分别是<code>&amp;f1</code>和<code>&amp;f2</code>,所以<code>{{.Fname}}</code>代表实例对象的Fname字段。</li>
</ul>
<h2 id="去除空白">去除空白</h2>
<p>template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,<strong>对于要解析的内容,不要随意缩进、随意换行</strong>。</p>
<p>可以在<code>{{</code>符号的后面加上短横线并保留一个或多个空格"- "来去除它前面的空白(包括换行符、制表符、空格等),即<code>{{- xxxx</code>。</p>
<p>在<code>}}</code>的前面加上一个或多个空格以及一个短横线"-"来去除它后面的空白,即<code>xxxx -}}</code>。</p>
<p>例如:</p>
<pre><code class="language-rust">{{23}} &lt; {{45}}      -&gt; 23 &lt; 45
{{23}} &lt; {{- 45}}      -&gt;23 &lt;45
{{23 -}} &lt; {{45}}      -&gt;23&lt; 45
{{23 -}} &lt; {{- 45}}    -&gt;23&lt;45
</code></pre>
<p>其中<code>{{23 -}}</code>中的短横线去除了这个替换结构后面的空格,即<code>}} &lt;</code>中间的空白。同理<code>{{- 45}}</code>的短横线去除了<code>&lt; {{</code>中间的空白。</p>
<p>再看上一节的例子中:</p>
<pre><code class="language-fsharp">t.Parse(
`hello {{.UserName}}!
{{ range .Emails }}
an email {{ . }}
{{- end }}
{{ with .Friends }}
{{- range . }}
my friend name is {{.Fname}}
{{- end }}
{{ end }}`)
</code></pre>
<p>注意,上面没有进行缩进。因为缩进的制表符或空格在替换的时候会保留。</p>
<p>第一行和第二行之间输出时会换行输出,不仅如此,<code>range {{.Emails}}</code>自身也占一行,在替换的时候它会被保留为空行。除非range前面没加<code>{{-</code>。由于range的<code>{{- end</code>加上了去除前缀空白,所以每次迭代的时候,每个元素之间都换行输出但却不多一空行,如果这里的end去掉<code>{{-</code>,则每个迭代的元素之间输出的时候都会有空行。同理后面的with和range。</p>
<h2 id="注释">注释</h2>
<p>注释方式:<code>{{/* a comment */}}</code>。</p>
<p>注释后的内容不会被引擎进行替换。但需要注意,注释行在替换的时候也会占用行,所以应该去除前缀和后缀空白,否则会多一空行。</p>
<pre><code class="language-cpp">{{- /* a comment without prefix/suffix space */}}
{{/* a comment without prefix/suffix space */ -}}
{{- /* a comment without prefix/suffix space */ -}}
</code></pre>
<p>注意,应该只去除前缀或后缀空白,不要同时都去除,否则会破坏原有的格式。例如:</p>
<pre><code class="language-less">t.Parse(
`hello {{.UserName}}!
{{- /* this line is a comment */}}
{{ range .Emails }}
an email {{ . }}
{{- end }}
</code></pre>
<h2 id="管道pipeline">管道pipeline</h2>
<p>pipeline是指产生数据的操作。比如<code>{{.}}</code>、<code>{{.Name}}</code>、<code>funcname args</code>等。</p>
<p>可以使用管道符号<code>|</code>链接多个命令,用法和unix下的管道类似:<code>|</code>前面的命令将运算结果(或返回值)传递给后一个命令的最后一个位置。</p>
<p>例如:</p>
<pre><code class="language-perl">{{.}} | printf "%s\n" "abcd"
</code></pre>
<p><code>{{.}}</code>的结果将传递给printf,且传递的参数位置是"abcd"之后。</p>
<p>命令可以有超过1个的返回值,这时第二个返回值必须为err类型。</p>
<p>需要注意的是,并非只有使用了<code>|</code>才是pipeline。Go template中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。这使得某些操作可以作为另一些操作内部的表达式先运行得到结果,就像是Unix下的命令替换一样。</p>
<p>例如,下面的<code>(len "output")</code>是pipeline,它整体先运行。</p>
<pre><code class="language-go">{{println (len "output")}}
</code></pre>
<p>下面是Pipeline的几种示例,它们都输出<code>"output"</code>:</p>
<pre><code class="language-perl">{{`"output"`}}
{{printf "%q" "output"}}
{{"output" | printf "%q"}}
{{printf "%q" (print "out" "put")}}
{{"put" | printf "%s%s" "out" | printf "%q"}}
{{"output" | printf "%s" | printf "%q"}}
</code></pre>
<h2 id="变量">变量</h2>
<p>可以在template中定义变量:</p>
<pre><code class="language-go">// 未定义过的变量
$var := pipeline

// 已定义过的变量
$var = pipeline
</code></pre>
<p>例如:</p>
<pre><code class="language-go">{{- $how_long :=(len "output")}}
{{- println $how_long}}   // 输出6
</code></pre>
<p>再例如:</p>
<pre><code class="language-powershell">tx := template.Must(template.New("hh").Parse(
`{{range $x := . -}}
{{$y := 333}}
{{- if (gt $x 33)}}{{println $x $y ($z := 444)}}{{- end}}
{{- end}}
`))
s := []int{11, 22, 33, 44, 55}
_ = tx.Execute(os.Stdout, s)
</code></pre>
<p>输出结果:</p>
<pre><code class="language-basic">44 333 444
55 333 444
</code></pre>
<p>上面的示例中,使用range迭代slice,每个元素都被赋值给变量<code>$x</code>,每次迭代过程中,都新设置一个变量<code>$y</code>,在内层嵌套的if结构中,可以使用这个两个外层的变量。在if的条件表达式中,使用了一个内置的比较函数gt,如果<code>$x</code>大于33,则为true。在println的参数中还定义了一个<code>$z</code>,之所以能定义,是因为<code>($z := 444)</code>的过程是一个Pipeline,可以先运行。</p>
<p>需要注意三点:</p>
<ol>
<li><strong>变量有作用域,只要出现end,则当前层次的作用域结束。内层可以访问外层变量,但外层不能访问内层变量</strong>。</li>
<li><strong>有一个特殊变量<code>$</code>,它代表模板的最顶级作用域对象(通俗地理解,是以模板为全局作用域的全局变量),在Execute()执行的时候进行赋值,且一直不变</strong>。例如上面的示例中,<code>$ = </code>。再例如,define定义了一个模板t1,则t1中的<code>$</code>作用域只属于这个t1。</li>
<li><strong>变量不可在模板之间继承</strong>。普通变量可能比较容易理解,但对于特殊变量"."和"$",比较容易搞混。见下面的例子。</li>
</ol>
<p>例如:</p>
<pre><code class="language-cpp">func main() {
        t1 := template.New("test1")
        tmpl, _ := t1.Parse(
`
{{- define "T1"}}ONE {{println .}}{{end}}
{{- define "T2"}}{{template "T1" $}}{{end}}
{{- template "T2" . -}}
`)
        _ = tmpl.Execute(os.Stdout, "hello world")
}
</code></pre>
<p>上面使用define额外定义了T1和T2两个模板,T2中嵌套了T1。<code>{{template "T2" .}}</code>的点代表顶级作用域的"hello world"对象。在T2中使用了特殊变量<code>$</code>,这个<code>$</code>的范围是T2的,不会继承顶级作用域"hello world"。但因为执行T2的时候,传递的是".",所以这里的<code>$</code>的值仍然是"hello world"。</p>
<p>不仅<code>$</code>不会在模板之间继承,<code>.</code>也不会在模板之间继承(其它所有变量都不会继承)。实际上,template可以看作是一个函数,它的执行过程是<code>template("T2",.)</code>。如果把上面的<code>$</code>换成".",结果是一样的。如果换成<code>{{template "T2"}}</code>,则<code>$=nil</code></p>
<p>如果看不懂这些,后文有解释。</p>
<h2 id="条件判断">条件判断</h2>
<p>有以下几种if条件判断语句,其中第三和第四是等价的。</p>
<pre><code class="language-lua">{{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}}
</code></pre>
<p>需要注意的是,pipeline为false的情况是各种数据对象的0值:数值0,指针或接口是nil,数组、slice、map或string则是len为0。</p>
<h2 id="rangeend迭代">range...end迭代</h2>
<p>有两种迭代表达式类型:</p>
<pre><code class="language-go">{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T0 {{end}}
</code></pre>
<p>range可以迭代slice、数组、map或channel。迭代的时候,会设置"."为当前正在迭代的元素。</p>
<p>对于第一个表达式,当迭代对象的值为0值时,则range直接跳过,就像if一样。对于第二个表达式,则在迭代到0值时执行else语句。</p>
<pre><code class="language-go">tx := template.Must(template.New("hh").Parse(
`{{range $x := . -}}
{{println $x}}
{{- end}}
`))
s := []int{11, 22, 33, 44, 55}
_ = tx.Execute(os.Stdout, s)
</code></pre>
<p>需注意的是,range的参数部分是pipeline,所以在迭代的过程中是可以进行赋值的。但有两种赋值情况:</p>
<pre><code class="language-scss">{{range $value := .}}
{{range $key,$value := .}}
</code></pre>
<p>如果range中只赋值给一个变量,则这个变量是当前正在迭代元素的值。如果赋值给两个变量,则第一个变量是索引值(map/slice是数值,map是key),第二个变量是当前正在迭代元素的值。</p>
<p>下面是在html中使用range的一个示例。test.html文件内容如下:</p>
<pre><code class="language-xml">&lt;html&gt;
        &lt;head&gt;
                &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
                &lt;title&gt;Go Web&lt;/title&gt;
        &lt;/head&gt;
        &lt;body&gt;
                &lt;ul&gt;
                        {{ range . }}
                                &lt;li&gt;{{ . }}&lt;/li&gt;
                        {{ else }}
                                &lt;li&gt; Nothing to show &lt;/li&gt;
                        {{ end}}
                &lt;/ul&gt;
        &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>以下是test.html同目录下的go程序文件:</p>
<pre><code class="language-go">package main

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

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

func process(w http.ResponseWriter, r *http.Request) {
        t1 := template.Must(template.ParseFiles("test.html"))
        s := []string{
                "星期一",
                "星期二",
                "星期三",
                "星期四",
                "星期五",
                "星期六",
                "星期日",}
        t1.Execute(w, s)
}
</code></pre>
<h2 id="withend">with...end</h2>
<p><strong>with用来设置"."的值</strong>。两种格式:</p>
<pre><code class="language-fsharp">{{with pipeline}} T1 {{end}}
{{with pipeline}} T1 {{else}} T0 {{end}}
</code></pre>
<p>对于第一种格式,当pipeline不为0值的时候,点"."设置为pipeline运算的值,否则跳过。对于第二种格式,当pipeline为0值时,执行else语句块,否则"."设置为pipeline运算的值,并执行T1。</p>
<p>例如:</p>
<pre><code class="language-fsharp">{{with "xx"}}{{println .}}{{end}}
</code></pre>
<p>上面将输出<code>xx</code>,因为"."已经设置为"xx"。</p>
<h2 id="内置函数和自定义函数">内置函数和自定义函数</h2>
<p>template定义了一些内置函数,也支持自定义函数。关于如何自定义函数,见深入剖析Go template。</p>
<p>以下是内置的函数列表:</p>
<pre><code class="language-go">and
    返回第一个为空的参数或最后一个参数。可以有任意多个参数。
    and x y等价于if x then y else x

not
    布尔取反。只能一个参数。

or
    返回第一个不为空的参数或最后一个参数。可以有任意多个参数。
    "or x y"等价于"if x then x else y"。

print
printf
println
    分别等价于fmt包中的Sprint、Sprintf、Sprintln

len
    返回参数的length。

index
    对可索引对象进行索引取值。第一个参数是索引对象,后面的参数是索引位。
    "index x 1 2 3"代表的是x。
    可索引对象包括map、slice、array。

call
    显式调用函数。第一个参数必须是函数类型,且不是template中的函数,而是外部函数。
    例如一个struct中的某个字段是func类型的。
    "call .X.Y 1 2"表示调用dot.X.Y(1, 2),Y必须是func类型,函数参数是1和2。
    函数必须只能有一个或2个返回值,如果有第二个返回值,则必须为error类型。
</code></pre>
<p>除此之外,还内置一些用于比较的函数:</p>
<pre><code class="language-bash">eq arg1 arg2:
    arg1 == arg2时为true
ne arg1 arg2:
    arg1 != arg2时为true
lt arg1 arg2:
    arg1 &lt; arg2时为true
le arg1 arg2:
    arg1 &lt;= arg2时为true
gt arg1 arg2:
    arg1 &gt; arg2时为true
ge arg1 arg2:
    arg1 &gt;= arg2时为true
</code></pre>
<p>对于eq函数,支持多个参数:</p>
<pre><code class="language-erlang">eq arg1 arg2 arg3 arg4...
</code></pre>
<p>它们都和第一个参数arg1进行比较。它等价于:</p>
<pre><code class="language-ini">arg1==arg2 || arg1==arg3 || arg1==arg4
</code></pre>
<p>示例:</p>
<pre><code class="language-perl">{{ if (gt $x 33) }}{{println $x}}{{ end }}
</code></pre>
<h2 id="嵌套templatedefine和template">嵌套template:define和template</h2>
<p>define可以直接在待解析内容中定义一个模板,这个模板会加入到common结构组中,并关联到关联名称上。如果不理解,还是建议阅读深入剖析Go template。</p>
<p>定义了模板之后,可以使用template这个action来执行模板。template有两种格式:</p>
<pre><code class="language-cpp">{{template "name"}}
{{template "name" pipeline}}
</code></pre>
<p>第一种是直接执行名为name的template,点设置为nil。第二种是点"."设置为pipeline的值,并执行名为name的template。可以将template看作是函数:</p>
<pre><code class="language-scss">template("name)
template("name",pipeline)
</code></pre>
<p>例如:</p>
<pre><code class="language-cpp">func main() {
        t1 := template.New("test1")
        tmpl, _ := t1.Parse(
`{{- define "T1"}}ONE {{println .}}{{end}}
{{- define "T2"}}TWO {{println .}}{{end}}
{{- define "T3"}}{{template "T1"}}{{template "T2" "haha"}}{{end}}
{{- template "T3" -}}
`)
        _ = tmpl.Execute(os.Stdout, "hello world")
}
</code></pre>
<p>输出结果:</p>
<pre><code class="language-go">ONE &lt;nil&gt;
TWO haha
</code></pre>
<p>上面定义了4个模板,一个是test1,另外三个是使用define来定义的T1、T2、T3,其中t1是test1模板的关联名称。T1、T2、T3和test1共享一个common结构。其中T3中包含了执行T1和T2的语句。最后只要<code>{{template T3}}</code>就可以执行T3,执行T3又会执行T1和T2。也就是实现了嵌套。此外,执行<code>{{template "T1"}}</code>时,点设置为nil,而<code>{{temlate "T2" "haha"}}</code>的点设置为了"haha"。</p>
<p>注意,<strong>模板之间的变量是不会继承的</strong>。</p>
<p>下面是html文件中嵌套模板的几个示例。</p>
<p>t1.html文件内容如下:</p>
<pre><code class="language-xml">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;

&lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta http-equiv="X-UA-Compatible" content="IE=9"&gt;
        &lt;title&gt;Go Web Programming&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
        &lt;div&gt; This is t1.html before&lt;/div&gt;
        &lt;div&gt;This is the value of the dot in t1.html - [{{ . }}]&lt;/div&gt;
        &lt;hr /&gt;
        {{ template "t2.html" }}
        &lt;hr /&gt;
        &lt;div&gt; This is t1.html after&lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>因为内部有<code>{{template "t2.html"}}</code>,且此处没有使用define去定义名为"t2.html"的模板,所以需要加载解析名为t2.html的文件。t2.html文件内容如下:</p>
<pre><code class="language-xml">&lt;div style="background-color: yellow;"&gt;
        This is t2.html&lt;br/&gt;
        This is the value of the dot in t2.html - [{{ . }}]
&lt;/div&gt;
</code></pre>
<p>处理这两个文件的handler函数如下:</p>
<pre><code class="language-css">func process(w http.ResponseWriter, r *http.Request) {
        t, _ := template.ParseFiles("t1.html", "t2.html")
        t.Execute(w, "Hello World!")
}
</code></pre>
<p>上面也可以不额外定义t2.html文件,而是直接在t1.html文件中使用define定义一个模板。修改t1.html文件如下:</p>
<pre><code class="language-xml">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;

&lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta http-equiv="X-UA-Compatible" content="IE=9"&gt;
        &lt;title&gt;Go Web Programming&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
        &lt;div&gt; This is t1.html before&lt;/div&gt;
        &lt;div&gt;This is the value of the dot in t1.html - [{{ . }}]&lt;/div&gt;
        &lt;hr /&gt;
        {{ template "t2.html" }}
        &lt;hr /&gt;
        &lt;div&gt; This is t1.html after&lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;

{{define "t2.html"}}
&lt;div style="background-color: yellow;"&gt;
        This is t2.html&lt;br/&gt;
        This is the value of the dot in t2.html - [{{ . }}]
&lt;/div&gt;
{{end}}
</code></pre>
<p>然后在handler中,只需解析t1.html一个文件即可。</p>
<pre><code class="language-css">func process(w http.ResponseWriter, r *http.Request) {
        t, _ := template.ParseFiles("t1.html")
        t.Execute(w, "Hello World!")
}
</code></pre>
<h2 id="block块">block块</h2>
<pre><code class="language-mipsasm">{{block "name" pipeline}} T1 {{end}}
        A block is shorthand for defining a template
                {{define "name"}} T1 {{end}}
        and then executing it in place
                {{template "name" pipeline}}
        The typical use is to define a set of root templates that are
        then customized by redefining the block templates within.
</code></pre>
<p>根据官方文档的解释:block等价于define定义一个名为name的模板,并在"有需要"的地方执行这个模板,执行时将"."设置为pipeline的值。</p>
<p>但应该注意,<strong>block的第一个动作是执行名为name的模板,如果不存在,则在此处自动定义这个模板,并执行这个临时定义的模板。换句话说,block可以认为是设置一个默认模板</strong>。</p>
<p>例如:</p>
<pre><code class="language-fsharp">{{block "T1" .}} one {{end}}
</code></pre>
<p>它首先表示<code>{{template "T1" .}}</code>,也就是说先找到T1模板,如果T1存在,则执行找到的T1,如果没找到T1,则临时定义一个<code>{{define "T1"}} one {{end}}</code>,并执行它。</p>
<p>下面是正常情况下不使用block的示例。</p>
<p>home.html文件内容如下:</p>
<pre><code class="language-xml">&lt;html&gt;
        &lt;head&gt;
                &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
                &lt;title&gt;Go Web Programming&lt;/title&gt;
        &lt;/head&gt;
        &lt;body&gt;
                {{ template "content" }}
        &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>在此文件中指定了要执行一个名为"content"的模板,但此文件中没有使用define定义该模板,所以需要在其它文件中定义名为content的模板。现在分别在两个文件中定义两个content模板:</p>
<p>red.html文件内容如下:</p>
<pre><code class="language-bash">{{ define "content" }}
        &lt;h1 style="color: red;"&gt;Hello World!&lt;/h1&gt;
{{ end }}
</code></pre>
<p>blue.html文件内容如下:</p>
<pre><code class="language-bash">{{ define "content" }}
        &lt;h1 style="color: blue;"&gt;Hello World!&lt;/h1&gt;
{{ end }}
</code></pre>
<p>在handler中,除了解析home.html,还根据需要解析red.html或blue.html:</p>
<pre><code class="language-cpp">func process(w http.ResponseWriter, r *http.Request) {
        rand.Seed(time.Now().Unix())
        t := template.New("test")
        if rand.Intn(10) &gt; 5 {
                t, _ = template.ParseFiles("home.html", "red.html")
        } else {
                t, _ = template.ParseFiles("home.html", "blue.html")
        }
        t.Execute(w,"")
}
</code></pre>
<p>如果使用block,那么可以设置默认的content模板。例如将原本定义在blue.html中的content设置为默认模板。</p>
<p>修改home.html:</p>
<pre><code class="language-xml">&lt;html&gt;
    &lt;head&gt;
      &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
      &lt;title&gt;Go Web Programming&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
      {{ block "content" . }}
            &lt;h1 style="color: blue;"&gt;Hello World!&lt;/h1&gt;
      {{ end }}
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>然后修改handler:</p>
<pre><code class="language-cpp">func process(w http.ResponseWriter, r *http.Request) {
        rand.Seed(time.Now().Unix())
        t := template.New("test")
        if rand.Intn(10) &gt; 5 {
                t, _ = template.ParseFiles("home.html", "red.html")
        } else {
                t, _ = template.ParseFiles("home.html")
        }
        t.Execute(w,"")
}
</code></pre>
<p>当执行else语句块的时候,发现home.html中要执行名为content的模板,但在ParseFiles()中并没有解析包含content模板的文件。于是执行block定义的content模板。而执行非else语句的时候,因为red.html中定义了content,会直接执行red.html中的content。</p>
<p>block通常设置在顶级的根文件中,例如上面的home.html中。</p>
<h2 id="htmltemplate的上下文感知">html/template的上下文感知</h2>
<p>对于html/template包,有一个很好用的功能:上下文感知。text/template没有该功能。</p>
<p>上下文感知具体指的是根据所处环境css、js、html、url的path、url的query,自动进行不同格式的转义。</p>
<p>例如,一个handler函数的代码如下:</p>
<pre><code class="language-go">func process(w http.ResponseWriter, r *http.Request) {
        t, _ := template.ParseFiles("test.html")
        content := `I asked: &lt;i&gt;"What's up?"&lt;/i&gt;`
        t.Execute(w, content)
}
</code></pre>
<p>上面content是Execute的第二个参数,它的内容是包含了特殊符号的字符串。</p>
<p>下面是test.html文件的内容:</p>
<pre><code class="language-xml">&lt;html&gt;
        &lt;head&gt;
                &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
                &lt;title&gt;Go Web Programming&lt;/title&gt;
        &lt;/head&gt;
        &lt;body&gt;
                &lt;div&gt;{{ . }}&lt;/div&gt;
                &lt;div&gt;&lt;a href="/{{ . }}"&gt;Path&lt;/a&gt;&lt;/div&gt;
                &lt;div&gt;&lt;a href="/?q={{ . }}"&gt;Query&lt;/a&gt;&lt;/div&gt;
                &lt;div&gt;&lt;a onclick="f('{{ . }}')"&gt;Onclick&lt;/a&gt;&lt;/div&gt;
        &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>上面test.html中有4个不同的环境,分别是html环境、url的path环境、url的query环境以及js环境。虽然对象都是<code>{{.}}</code>,但解析执行后的值是不一样的。如果使用curl获取源代码,结果将如下:</p>
<pre><code class="language-xml">&lt;html&gt;

&lt;head&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
        &lt;title&gt;Go Web Programming&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
        &lt;div&gt;I asked: &amp;lt;i&amp;gt;&amp;#34;What&amp;#39;s up?&amp;#34;&amp;lt;/i&amp;gt;&lt;/div&gt;
        &lt;div&gt;
                &lt;a href="/I%20asked:%20%3ci%3e%22What%27s%20up?%22%3c/i%3e"&gt;
                        Path
                &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
                &lt;a href="/?q=I%20asked%3a%20%3ci%3e%22What%27s%20up%3f%22%3c%2fi%3e"&gt;
                        Query
                &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
                &lt;a onclick="f('I asked: \x3ci\x3e\x22What\x27s up?\x22\x3c\/i\x3e')"&gt;
                        Onclick
                &lt;/a&gt;
        &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<h3 id="不转义">不转义</h3>
<p>上下文感知的自动转义能让程序更加安全,比如防止XSS攻击(例如在表单中输入带有<code>&lt;script&gt;...&lt;/script&gt;</code>的内容并提交,会使得用户提交的这部分script被执行)。</p>
<p>如果确实不想转义,可以进行类型转换。</p>
<pre><code class="language-haskell">type CSS
type HTML
type JS
type URL
</code></pre>
<p>转换成指定个时候,字符都将是字面意义。</p>
<p>例如:</p>
<pre><code class="language-css">func process(w http.ResponseWriter, r *http.Request) {
        t, _ := template.ParseFiles("tmpl.html")
        t.Execute(w, template.HTML(r.FormValue("comment")))
}
</code></pre><br><br>
来源:https://www.cnblogs.com/CxAgoni/p/17962581
頁: [1]
查看完整版本: Go标准库:Go template用法详解