在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ 7.4 模板處理
7 文本處理
3 Web基礎(chǔ)
14 擴展Web框架
10.4 小結(jié)
2.2 Go基礎(chǔ)
2.8 總結(jié)
6.1 session和cookie
5.5 使用beedb庫進行ORM開發(fā)
8.3 REST
13.6 小結(jié)
5.4 使用PostgreSQL數(shù)據(jù)庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結(jié)
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結(jié)
13 如何設(shè)計一個Web框架
14.3 表單及驗證支持
12 部署與維護
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫操作
6.5 小結(jié)
9.4 避免SQL注入
12.1 應(yīng)用日志
4.2 驗證表單的輸入
10.1 設(shè)置默認地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務(wù)
12.3 應(yīng)用部署
5.7 小結(jié)
12.5 小結(jié)
11 錯誤處理,調(diào)試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預(yù)防session劫持
12.4 備份和恢復
8.1 Socket編程
13.1 項目規(guī)劃
13.4 日志和配置設(shè)計
7.6 字符串處理
13.2 自定義路由器設(shè)計
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點
7.5 文件操作
7.4 模板處理
9.1 預(yù)防CSRF攻擊
13.3 controller設(shè)計
2.6 interface
14.4 用戶認證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯誤處理
6 session和數(shù)據(jù)存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結(jié)
13.5 實現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎(chǔ)
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結(jié)
4.3 預(yù)防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫
14.7 小結(jié)
3.2 Go搭建一個Web服務(wù)器
2.7 并發(fā)
5 訪問數(shù)據(jù)庫
4 表單
3.5 小結(jié)
1.4 Go開發(fā)工具
11.4 小結(jié)
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫
4.6 小結(jié)
8.4 RPC

7.4 模板處理

什么是模板

你一定聽說過一種叫做MVC的設(shè)計模式,Model處理數(shù)據(jù),View展現(xiàn)結(jié)果,Controller控制用戶的請求,至于View層的處理,在很多動態(tài)語言里面都是通過在靜態(tài)HTML中插入動態(tài)語言生成的數(shù)據(jù),例如JSP中通過插入<%=....=%>,PHP中通過插入<?php.....?>來實現(xiàn)的。

通過下面這個圖可以說明模板的機制

http://wiki.jikexueyuan.com/project/go-web-programming/images/7.4.template.png" alt="" />

圖7.1 模板機制圖

Web應(yīng)用反饋給客戶端的信息中的大部分內(nèi)容是靜態(tài)的,不變的,而另外少部分是根據(jù)用戶的請求來動態(tài)生成的,例如要顯示用戶的訪問記錄列表。用戶之間只有記錄數(shù)據(jù)是不同的,而列表的樣式則是固定的,此時采用模板可以復用很多靜態(tài)代碼。

Go模板使用

在Go語言中,我們使用template包來進行模板處理,使用類似Parse、ParseFile、Execute等方法從文件或者字符串加載模板,然后執(zhí)行類似上面圖片展示的模板的merge操作。請看下面的例子:

func handler(w http.ResponseWriter, r *http.Request) {
    t := template.New("some template") //創(chuàng)建一個模板
    t, _ = t.ParseFiles("tmpl/welcome.html", nil)  //解析模板文件
    user := GetUser() //獲取當前用戶信息
    t.Execute(w, user)  //執(zhí)行模板的merger操作
}

通過上面的例子我們可以看到Go語言的模板操作非常的簡單方便,和其他語言的模板處理類似,都是先獲取數(shù)據(jù),然后渲染數(shù)據(jù)。

為了演示和測試代碼的方便,我們在接下來的例子中采用如下格式的代碼

  • 使用Parse代替ParseFiles,因為Parse可以直接測試一個字符串,而不需要額外的文件
  • 不使用handler來寫演示代碼,而是每個測試一個main,方便測試
  • 使用os.Stdout代替http.ResponseWriter,因為os.Stdout實現(xiàn)了io.Writer接口

模板中如何插入數(shù)據(jù)?

上面我們演示了如何解析并渲染模板,接下來讓我們來更加詳細的了解如何把數(shù)據(jù)渲染出來。一個模板都是應(yīng)用在一個Go的對象之上,Go對象的字段如何插入到模板中呢?

字段操作

Go語言的模板通過{{}}來包含需要在渲染時被替換的字段,{{.}}表示當前的對象,這和Java或者C++中的this類似,如果要訪問當前對象的字段通過{{.FieldName}},但是需要注意一點:這個字段必須是導出的(字段首字母必須是大寫的),否則在渲染的時候就會報錯,請看下面的這個例子:

package main

import (
    "html/template"
    "os"
)

type Person struct {
    UserName string
}

func main() {
    t := template.New("fieldname example")
    t, _ = t.Parse("hello {{.UserName}}!")
    p := Person{UserName: "Astaxie"}
    t.Execute(os.Stdout, p)
}

上面的代碼我們可以正確的輸出hello Astaxie,但是如果我們稍微修改一下代碼,在模板中含有了未導出的字段,那么就會報錯

type Person struct {
    UserName string
    email   string  //未導出的字段,首字母是小寫的
}

t, _ = t.Parse("hello {{.UserName}}! {{.email}}")

上面的代碼就會報錯,因為我們調(diào)用了一個未導出的字段,但是如果我們調(diào)用了一個不存在的字段是不會報錯的,而是輸出為空。

如果模板中輸出{{.}},這個一般應(yīng)用于字符串對象,默認會調(diào)用fmt包輸出字符串的內(nèi)容。

輸出嵌套字段內(nèi)容

上面我們例子展示了如何針對一個對象的字段輸出,那么如果字段里面還有對象,如何來循環(huán)的輸出這些內(nèi)容呢?我們可以使用{{with …}}…{{end}}{{range …}}{{end}}來進行數(shù)據(jù)的輸出。

  • {{range}} 這個和Go語法里面的range類似,循環(huán)操作數(shù)據(jù)
  • {{with}}操作是指當前對象的值,類似上下文的概念

詳細的使用請看下面的例子:

package main

import (
    "html/template"
    "os"
)

type Friend struct {
    Fname string
}

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

func main() {
    f1 := Friend{Fname: "minux.ma"}
    f2 := Friend{Fname: "xushiwei"}
    t := template.New("fieldname example")
    t, _ = t.Parse(`hello {{.UserName}}!
            {{range .Emails}}
                an email {{.}}
            {{end}}
            {{with .Friends}}
            {{range .}}
                my friend name is {{.Fname}}
            {{end}}
            {{end}}
            `)
    p := Person{UserName: "Astaxie",
        Emails:  []string{"astaxie@beego.me", "astaxie@gmail.com"},
        Friends: []*Friend{&f1, &f2}}
    t.Execute(os.Stdout, p)
}

條件處理

在Go模板里面如果需要進行條件判斷,那么我們可以使用和Go語言的if-else語法類似的方式來處理,如果pipeline為空,那么if就認為是false,下面的例子展示了如何使用if-else語法:

package main

import (
    "os"
    "text/template"
)

func main() {
    tEmpty := template.New("template test")
    tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不會輸出. {{end}}\n"))
    tEmpty.Execute(os.Stdout, nil)

    tWithValue := template.New("template test")
    tWithValue = template.Must(tWithValue.Parse("不為空的 pipeline if demo: {{if `anything`}} 我有內(nèi)容,我會輸出. {{end}}\n"))
    tWithValue.Execute(os.Stdout, nil)

    tIfElse := template.New("template test")
    tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n"))
    tIfElse.Execute(os.Stdout, nil)
}

通過上面的演示代碼我們知道if-else語法相當?shù)暮唵?,在使用過程中很容易集成到我們的模板代碼中。

注意:if里面無法使用條件判斷,例如.Mail=="astaxie@gmail.com",這樣的判斷是不正確的,if里面只能是bool值

pipelines

Unix用戶已經(jīng)很熟悉什么是pipe了,ls | grep "beego"類似這樣的語法你是不是經(jīng)常使用,過濾當前目錄下面的文件,顯示含有"beego"的數(shù)據(jù),表達的意思就是前面的輸出可以當做后面的輸入,最后顯示我們想要的數(shù)據(jù),而Go語言模板最強大的一點就是支持pipe數(shù)據(jù),在Go語言里面任何{{}}里面的都是pipelines數(shù)據(jù),例如我們上面輸出的email里面如果還有一些可能引起XSS注入的,那么我們?nèi)绾蝸磉M行轉(zhuǎn)化呢?

{{. | html}}

在email輸出的地方我們可以采用如上方式可以把輸出全部轉(zhuǎn)化html的實體,上面的這種方式和我們平常寫Unix的方式是不是一模一樣,操作起來相當?shù)暮啽悖{(diào)用其他的函數(shù)也是類似的方式。

模板變量

有時候,我們在模板使用過程中需要定義一些局部變量,我們可以在一些操作中申明局部變量,例如with``range``if過程中申明局部變量,這個變量的作用域是{{end}}之前,Go語言通過申明的局部變量格式如下所示:

$variable := pipeline

詳細的例子看下面的:

{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}

模板函數(shù)

模板在輸出對象的字段值時,采用了fmt包把對象轉(zhuǎn)化成了字符串。但是有時候我們的需求可能不是這樣的,例如有時候我們?yōu)榱朔乐估]件發(fā)送者通過采集網(wǎng)頁的方式來發(fā)送給我們的郵箱信息,我們希望把@替換成at例如:astaxie at beego.me,如果要實現(xiàn)這樣的功能,我們就需要自定義函數(shù)來做這個功能。

每一個模板函數(shù)都有一個唯一值的名字,然后與一個Go函數(shù)關(guān)聯(lián),通過如下的方式來關(guān)聯(lián)

type FuncMap map[string]interface{}

例如,如果我們想要的email函數(shù)的模板函數(shù)名是emailDeal,它關(guān)聯(lián)的Go函數(shù)名稱是EmailDealWith,那么我們可以通過下面的方式來注冊這個函數(shù)

t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})

EmailDealWith這個函數(shù)的參數(shù)和返回值定義如下:

func EmailDealWith(args …interface{}) string

我們來看下面的實現(xiàn)例子:

package main

import (
    "fmt"
    "html/template"
    "os"
    "strings"
)

type Friend struct {
    Fname string
}

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

func EmailDealWith(args ...interface{}) string {
    ok := false
    var s string
    if len(args) == 1 {
        s, ok = args[0].(string)
    }
    if !ok {
        s = fmt.Sprint(args...)
    }
    // find the @ symbol
    substrs := strings.Split(s, "@")
    if len(substrs) != 2 {
        return s
    }
    // replace the @ by " at "
    return (substrs[0] + " at " + substrs[1])
}

func main() {
    f1 := Friend{Fname: "minux.ma"}
    f2 := Friend{Fname: "xushiwei"}
    t := template.New("fieldname example")
    t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
    t, _ = t.Parse(`hello {{.UserName}}!
                {{range .Emails}}
                    an emails {{.|emailDeal}}
                {{end}}
                {{with .Friends}}
                {{range .}}
                    my friend name is {{.Fname}}
                {{end}}
                {{end}}
                `)
    p := Person{UserName: "Astaxie",
        Emails:  []string{"astaxie@beego.me", "astaxie@gmail.com"},
        Friends: []*Friend{&f1, &f2}}
    t.Execute(os.Stdout, p)
}

上面演示了如何自定義函數(shù),其實,在模板包內(nèi)部已經(jīng)有內(nèi)置的實現(xiàn)函數(shù),下面代碼截取自模板包里面

var builtins = FuncMap{
    "and":      and,
    "call":     call,
    "html":     HTMLEscaper,
    "index":    index,
    "js":       JSEscaper,
    "len":      length,
    "not":      not,
    "or":       or,
    "print":    fmt.Sprint,
    "printf":   fmt.Sprintf,
    "println":  fmt.Sprintln,
    "urlquery": URLQueryEscaper,
}

Must操作

模板包里面有一個函數(shù)Must,它的作用是檢測模板是否正確,例如大括號是否匹配,注釋是否正確的關(guān)閉,變量是否正確的書寫。接下來我們演示一個例子,用Must來判斷模板是否正確:

package main

import (
    "fmt"
    "text/template"
)

func main() {
    tOk := template.New("first")
    template.Must(tOk.Parse(" some static text /* and a comment */"))
    fmt.Println("The first one parsed OK.")

    template.Must(template.New("second").Parse("some static text {{ .Name }}"))
    fmt.Println("The second one parsed OK.")

    fmt.Println("The next one ought to fail.")
    tErr := template.New("check parse error with Must")
    template.Must(tErr.Parse(" some static text {{ .Name }"))
}

講輸出如下內(nèi)容

The first one parsed OK.
The second one parsed OK.
The next one ought to fail.
panic: template: check parse error with Must:1: unexpected "}" in command

嵌套模板

我們平常開發(fā)Web應(yīng)用的時候,經(jīng)常會遇到一些模板有些部分是固定不變的,然后可以抽取出來作為一個獨立的部分,例如一個博客的頭部和尾部是不變的,而唯一改變的是中間的內(nèi)容部分。所以我們可以定義成header、content、footer三個部分。Go語言中通過如下的語法來申明

{{define "子模板名稱"}}內(nèi)容{{end}}

通過如下方式來調(diào)用:

{{template "子模板名稱"}}

接下來我們演示如何使用嵌套模板,我們定義三個文件,header.tmplcontent.tmpl、footer.tmpl文件,里面的內(nèi)容如下

//header.tmpl
{{define "header"}}
<html>
<head>
    <title>演示信息</title>
</head>
<body>
{{end}}

//content.tmpl
{{define "content"}}
{{template "header"}}
<h1>演示嵌套</h1>
<ul>
    <li>嵌套使用define定義子模板</li>
    <li>調(diào)用使用template</li>
</ul>
{{template "footer"}}
{{end}}

//footer.tmpl
{{define "footer"}}
</body>
</html>
{{end}}

演示代碼如下:

package main

import (
    "fmt"
    "os"
    "text/template"
)

func main() {
    s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")
    s1.ExecuteTemplate(os.Stdout, "header", nil)
    fmt.Println()
    s1.ExecuteTemplate(os.Stdout, "content", nil)
    fmt.Println()
    s1.ExecuteTemplate(os.Stdout, "footer", nil)
    fmt.Println()
    s1.Execute(os.Stdout, nil)
}

通過上面的例子我們可以看到通過template.ParseFiles把所有的嵌套模板全部解析到模板里面,其實每一個定義的{{define}}都是一個獨立的模板,他們相互獨立,是并行存在的關(guān)系,內(nèi)部其實存儲的是類似map的一種關(guān)系(key是模板的名稱,value是模板的內(nèi)容),然后我們通過ExecuteTemplate來執(zhí)行相應(yīng)的子模板內(nèi)容,我們可以看到header、footer都是相對獨立的,都能輸出內(nèi)容,content 中因為嵌套了header和footer的內(nèi)容,就會同時輸出三個的內(nèi)容。但是當我們執(zhí)行s1.Execute,沒有任何的輸出,因為在默認的情況下沒有默認的子模板,所以不會輸出任何的東西。

同一個集合類的模板是互相知曉的,如果同一模板被多個集合使用,則它需要在多個集合中分別解析

總結(jié)

通過上面對模板的詳細介紹,我們了解了如何把動態(tài)數(shù)據(jù)與模板融合:如何輸出循環(huán)數(shù)據(jù)、如何自定義函數(shù)、如何嵌套模板等等。通過模板技術(shù)的應(yīng)用,我們可以完成MVC模式中V的處理,接下來的章節(jié)我們將介紹如何來處理M和C。