一些小修改
This commit is contained in:
25
7.3.md
25
7.3.md
@@ -1,11 +1,11 @@
|
||||
#7.3 正则处理
|
||||
正则表达式是进行模式匹配和文本操纵的一种复杂而强大的工具。虽然正则表达式没有纯粹的文本匹配速度那么快,但应用起来相当的灵活。正则表达式通过简单的语法(一些简单的符号)构造模式能够匹配几乎任何可以想得到的字符组合。如果你在Web开发中需要从一些文本数据源中获取数据,那么正则表达式就能够帮你从这些数据源中提取出有意义的信息。
|
||||
正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低,但是它却更灵活。按照它的语法规则,随需构造出的匹配模式就能够从原始文本中筛选出几乎任何想你要得到的字符组合。如果你在Web开发中需要从一些文本数据源中获取数据,那么你只需要按照它的语法规则,随需构造出正确的模式字符串就能够从原数据源提取出有意义的文本信息。
|
||||
|
||||
Go语言标准包里面已经包含有`regexp`,实现了正则表达式的搜索匹配,接受和python、perl或者其他语言一样的正则表达式语法,更准确的说,它实现了RE2标准,除了`\C`,详细的语法描述参考:http://code.google.com/p/re2/wiki/Syntax 如果你在其他语言里面使用过正则,Go实现的正则语法基本都一致,那么只需要了解一下`regexp`包里面的一些函数参数就可以了。
|
||||
Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果你已经使用过其他编程语言提供的正则相关功能,那么你应该对Go语言版本的不会太陌生,但是它们之间也有一些小的差异,因为Go实现的是RE2标准,除了\C,详细的语法描述参考:http://code.google.com/p/re2/wiki/Syntax。
|
||||
|
||||
其实字符串处理我们可以使用`strings`包来进行搜索(Contains、Index)、替换(Replace)和解析(Split、Join)等操作,但是这些都是简单的字符串操作,他们的搜索都是大小写敏感,而且固定的字符串,如果我们需要匹配可变的那种就没办法实现了,当然如果`strings`包能解决你的问题,那么就尽量使用它来解决。因为他们足够简单、而且性能和可读性都会比正则好。
|
||||
|
||||
我们在前面表单验证的小节里面已经接触过正则处理,我们利用了正则表达式来匹配输入的信息是否和相应的格式匹配。在使用中我们需要注意一点,所有的字符都是UTF-8编码的。接下来让我们更加深入的来理解Go语言的`regexp`包。
|
||||
如果你还记得,在前面表单验证的小节里,我们已经接触过正则处理,在那里我们利用了它来验证输入的信息是否满足某些预设的条件。在使用中需要注意的一点就是:所有的字符都是UTF-8编码的。接下来让我们更加深入的来学习Go语言的`regexp`包相关知识吧。
|
||||
|
||||
##通过正则判断是否匹配
|
||||
`regexp`包中含有三个函数用来判断是否匹配,如果匹配返回true,否则返回false
|
||||
@@ -25,7 +25,7 @@ Go语言标准包里面已经包含有`regexp`,实现了正则表达式的搜
|
||||
return true
|
||||
}
|
||||
|
||||
我们可以看到,`regexp`的pattern和我们平常使用的正则一模一样。接下来我们再来看一个例子,用户输入一个字符串,我们想知道是不是合法输入:
|
||||
可以看到,`regexp`的pattern和我们平常使用的正则一模一样。再来看一个例子:当用户输入一个字符串,我们想知道是不是一次合法的输入:
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
@@ -38,12 +38,12 @@ Go语言标准包里面已经包含有`regexp`,实现了正则表达式的搜
|
||||
}
|
||||
}
|
||||
|
||||
通过上面的一些例子,我们了解到如果要判断一些字符串是否符合我们的描述需求,我们采用Match(Reader|String)来进行判断,这个使用非常方便。
|
||||
在上面的两个小例子中,我们采用了Match(Reader|String)来判断一些字符串是否符合我们的描述需求,它们使用起来非常方便。
|
||||
|
||||
##通过正则获取内容
|
||||
我们发现采用上面Match模式只能用来作为做字符串的判断,而无法截取字符串的一部分,或者过滤字符串、或者符合条件的一批字符串。如果想要使用这些,那就需要使用正则表达式的复杂模式。
|
||||
Match模式只能用来对字符串的判断,而无法截取字符串的一部分、过滤字符串、或者提取出符合条件的一批字符串。如果想要满足这些需求,那就需要使用正则表达式的复杂模式。
|
||||
|
||||
我们经常需要一些爬虫程序,下面我们就以爬虫举例来说明如何过滤和截取抓取的数据:
|
||||
我们经常需要一些爬虫程序,下面就以爬虫为例来说明如何使用正则来过滤或截取抓取到的数据:
|
||||
|
||||
package main
|
||||
|
||||
@@ -64,6 +64,7 @@ Go语言标准包里面已经包含有`regexp`,实现了正则表达式的搜
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("http read error")
|
||||
return
|
||||
}
|
||||
|
||||
src := string(body)
|
||||
@@ -91,7 +92,7 @@ Go语言标准包里面已经包含有`regexp`,实现了正则表达式的搜
|
||||
fmt.Println(strings.TrimSpace(src))
|
||||
}
|
||||
|
||||
上面的演示代码我们可以看出来,使用复杂的正则是首先调用Compile,Compile会解析正则表达式是否合法,如果正确,那么就会返回一个Regexp,然后可以在任意的字符串上面应用相应的函数进行过滤、截取等操作。
|
||||
从这个示例可以看出,使用复杂的正则首先是Compile,它会解析正则表达式是否合法,如果正确,那么就会返回一个Regexp,然后就可以利用返回的Regexp在任意的字符串上面执行需要的操作。
|
||||
|
||||
解析正则表达式的有如下几个方法:
|
||||
|
||||
@@ -100,9 +101,9 @@ Go语言标准包里面已经包含有`regexp`,实现了正则表达式的搜
|
||||
func MustCompile(str string) *Regexp
|
||||
func MustCompilePOSIX(str string) *Regexp
|
||||
|
||||
Compile和CompilePOSIX不同就是POSIX必须使用POSIX语法,然后他使用最左最长方式搜索,而Compile是采用最左方式搜索(例如[a-z]{2,4}这样正则在搜索的时候可能搜索到任意的字母组合,那么他搜索满足条件的最左边最长的那个匹配,"aa09aaa88aaaa",当搜索这个字符串的时候,应该是返回aaaa,而不是第一个匹配的,而Compile的返回aa,最左边最先匹配的字符串)。前缀有Must的表示,在解析正则语法的时候,调用该函数如果解析正则语法出错直接抛出panic,而不加的则返回错误。
|
||||
CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用最左最长方式搜索,而Compile是采用的则只采用最左方式搜索(例如[a-z]{2,4}这样一个正则表达式,应用于"aa09aaa88aaaa"这个文本串时,CompilePOSIX返回了aaaa,而Compile的返回的是aa)。前缀有Must的函数表示,在解析正则语法的时候,如果匹配模式串不满足正确的语法则直接panic,而不加Must的则只是返回错误。
|
||||
|
||||
在了解了如何新建一个Regexp之后,我们来看看这个struct有哪些方法可以让我们来操作字符串,首先我们来看下面这写用来搜索的函数:
|
||||
在了解了如何新建一个Regexp之后,我们再来看一下这个struct提供了哪些方法来辅助我们操作字符串,首先我们来看下面这写用来搜索的函数:
|
||||
|
||||
func (re *Regexp) Find(b []byte) []byte
|
||||
func (re *Regexp) FindAll(b []byte, n int) [][]byte
|
||||
@@ -189,7 +190,7 @@ Compile和CompilePOSIX不同就是POSIX必须使用POSIX语法,然后他使用
|
||||
fmt.Println(submatchallindex)
|
||||
}
|
||||
|
||||
我们前面介绍过匹配函数,那么Regexp对象也有这三个函数,和上面外部的三个函数功能一模一样,上面三个外部的函数其实内部实现就是调用了这三个函数:
|
||||
前面介绍过匹配函数,Regexp也定义了三个函数,它们和同名的外部函数功能一模一样,其实外部函数就是调用了这Regexp的三个函数来实现的:
|
||||
|
||||
func (re *Regexp) Match(b []byte) bool
|
||||
func (re *Regexp) MatchReader(r io.RuneReader) bool
|
||||
@@ -227,7 +228,7 @@ Compile和CompilePOSIX不同就是POSIX必须使用POSIX语法,然后他使用
|
||||
fmt.Println(string(res))
|
||||
}
|
||||
|
||||
至此我们已经全部介绍完Go语言的`regexp`包,通过上面对他的每个函数的介绍以及通过例子来演示,大家应该能够通过Go语言的正则包进行基本一些正则的操作了。
|
||||
至此我们已经全部介绍完Go语言的`regexp`包,通过对它的主要函数介绍及演示,相信大家应该能够通过Go语言的正则包进行一些基本的正则的操作了。
|
||||
|
||||
|
||||
## links
|
||||
|
||||
682
7.4.md
682
7.4.md
@@ -1,340 +1,344 @@
|
||||
#7.4 模板处理
|
||||
##什么是模板
|
||||
我们一定听说过一种设计模式叫做MVC,Model处理数据,View展现结果,Controller控制用户的请求,至于View层的处理,很多动态语言里面都是通过在静态HTML里面插入动态语言的数据,例如JSP中通过插入`<%=....=%>`,PHP中通过插入`<?php.....?>`来实现这种机制。
|
||||
|
||||
通过下面这个图可以说明模板的机制
|
||||
|
||||

|
||||
|
||||
当Web应用反馈信息到客户端的时候,其实很多内容都是不变的,也就是类似HTML的静态语言,而有一部分是需要根据用户的请求来实时读取计算的,例如要显示用户的访问记录列表,那么会根据每个用户的访问信息来展示每个人的用户列表,而整个列表的样式都是固定的,只有里面的数据是变化的,所以采用模板可以复用很多静态的代码。
|
||||
|
||||
##Go模板使用
|
||||
在Go语言中,我们使用`template`包来进行模板处理,使用类似`Parse`、`ParseFile`、`Execute`等方法从文件或者字符串加载模板,然后执行类似上面图片展示的模板的merge操作。请看下面的例子:
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
t := template.New("some template") //创建一个模板
|
||||
t, _ = t.ParseFiles("tmpl/welcome.html", nil) //解析模板文件
|
||||
user := GetUser() //获取当前用户信息
|
||||
t.Execute(w, user) //执行模板的merger操作
|
||||
}
|
||||
|
||||
我们通过上面的例子可以看到Go语言的模板操作非常的简单方便,和其他语言的模板处理类似,都是先获取数据,然后渲染数据。
|
||||
|
||||
为了我们演示和测试代码方便,我们在接下来的例子中采用如下格式的代码
|
||||
|
||||
- 使用Parse代替ParseFiles,因为Parse可以直接测试一个字符串,而不需要额外的文件
|
||||
- 不使用handler来写演示代码,而是每个测试一个main,方便测试
|
||||
- 使用`os.Stdout`代替`http.ResponseWriter`,因为`os.Stdout`实现了`io.Writer`接口
|
||||
|
||||
##模板中如何插入数据?
|
||||
上面我们演示了如何解析模板、渲染模板,接下来让我们更加详细的来了解如何把数据渲染出来。一个模板都是应用在一个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}}")
|
||||
|
||||
上面的代码就会报错,因为我们调用了一个未导出的字段,但是如果我们调用了一个不存在的字段是不会报错的,而是输出为空。
|
||||
|
||||
如果模板中输出`{{.}}`,这个一般应用与字符串对象,默认会调用fmt包输出字符串的内容。
|
||||
|
||||
###输出嵌套字段内容
|
||||
上面我们例子展示了如何针对一个对象的字段输出,那么如果字段里面还有对象,如何来循环的输出这些内容呢?我们可以使用`{{with …}}…{{end}}`和`{{range …}}{{end}}`来进行数据的输出。详细的使用请看下面的例子:
|
||||
|
||||
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 emails {{.}}
|
||||
{{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)
|
||||
}
|
||||
|
||||
###pipelines
|
||||
Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"name"的数据,他表达的意思就是前面的输出可以当做后面的输入,最后显示我们想要的数据,而Go语言模板最强大的一点就是支持pipe数据,在Go语言里面任何`{{}}`里面的都是pipelines数据,例如我们上面输出的email里面如果还有一些可能引起XSS注入的,那么我们任何来进行转化呢?
|
||||
|
||||
{{. | html}}
|
||||
|
||||
在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体,上面的这种方式和我们平常写Unix的方式是不是一模一样,操作起来相当的简便,调用其他的函数也是类似的方式。
|
||||
|
||||
###条件处理
|
||||
在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`}} 我有内容,我会输出. {{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`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。
|
||||
|
||||
###模板变量
|
||||
有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`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}}
|
||||
###模板函数
|
||||
模板在输出对象的字段值时,采用了`fmt`包把对象转化成了字符串。但是有时候我们的需求可能不是这样的,例如有时候我们为了防止垃圾邮件发送者通过采集网页的方式来发送给我们的邮箱信息,我们希望把`@`替换成`at`例如:`astaxie at beego.me`,如果要实现这样的功能,我们就需要自定义函数来做这个功能。
|
||||
|
||||
每一个模板函数都有一个唯一值的名字,然后与一个Go函数关联,通过如下的方式来关联
|
||||
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
例如,如果我们想要的email函数的模板函数名是`emailDeal`,它关联的Go函数名称是`EmailDealWith`,n那么我们可以通过下面的方式来注册这个函数
|
||||
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
|
||||
`EmailDealWith`这个函数的参数和返回值定义如下:
|
||||
|
||||
func EmailDealWith(args …interface{}) string
|
||||
|
||||
我们来看下面的实现例子:
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
上面演示了如何自定义函数,其实,在模板包内部已经有内置的实现函数,下面代码截取自模板包里面
|
||||
|
||||
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操作
|
||||
模板包里面有一个函数`Must`,它的作用是检测模板是否正确,例如大括号是否匹配,是否注释正确的关闭了,是否变量正确的书写。接下来我们演示一个例子,用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 }"))
|
||||
}
|
||||
|
||||
讲输出如下内容
|
||||
|
||||
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
|
||||
|
||||
##嵌套模板
|
||||
我们平常开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成`header`、`content`、`footer`三个部分。Go语言中通过如下的语法来申明
|
||||
|
||||
{{define "子模板名称"}}内容{{end}}
|
||||
|
||||
通过如下方式来调用:
|
||||
|
||||
{{template "子模板名称"}}
|
||||
|
||||
接下来我们演示如何使用嵌套模板,我们定义三个文件,`header.tmpl`、`content.tmpl`、`footer.tmpl`文件,里面的内容如下
|
||||
|
||||
//header.tmpl
|
||||
{{define "header"}}
|
||||
<html>
|
||||
<head>
|
||||
<title>演示信息</title>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
||||
|
||||
//content.tmpl
|
||||
{{define "content"}}
|
||||
{{template "header"}}
|
||||
<h1>演示嵌套</h1>
|
||||
<ul>
|
||||
<li>嵌套使用define定义子模板</li>
|
||||
<li>调用使用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}}都是一个独立的模板,他们相互独立,是并行存在的关系,内部其实存储的是类似map的一种关系(key是模板的名称,value是模板的内容),然后我们通过`ExecuteTemplate`来执行相应的子模板内容,我们可以看到header、footer都是相对独立的,都能输出内容,contenrt中因为嵌套了header和footer的内容,就会同时输出三个的内容。但是当我们执行`s1.Execute`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。
|
||||
|
||||
##总结
|
||||
通过上面对模板的详细介绍,我们了解了如何把数据输出到模板、如何输出循环数据、如何自定义函数、如何嵌套模板,通过这些介绍我们可以把数据转化成模板数据输出到客户端。这样通过模板技术的应用,我们可以完成MVC模式中V的处理,接下来我们将介绍如何来处理M和C。
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [正则处理](<7.3.md>)
|
||||
* 下一节: [小结](<7.5.md>)
|
||||
|
||||
## LastModified
|
||||
#7.4 模板处理
|
||||
##什么是模板
|
||||
你一定听说过一种叫做MVC的设计模式,Model处理数据,View展现结果,Controller控制用户的请求,至于View层的处理,在很多动态语言里面都是通过在静态HTML中插入动态语言生成的数据,例如JSP中通过插入`<%=....=%>`,PHP中通过插入`<?php.....?>`来实现的。
|
||||
|
||||
通过下面这个图可以说明模板的机制
|
||||
|
||||

|
||||
|
||||
Web应用反馈给客户端的信息中的大部分内容是静态的,不变的,而另外少部分是根据用户的请求来动态生成的,例如要显示用户的访问记录列表。用户之间只有记录数据是不同的,而列表的样式则是固定的,此时采用模板可以复用很多静态代码。
|
||||
|
||||
##Go模板使用
|
||||
在Go语言中,我们使用`template`包来进行模板处理,使用类似`Parse`、`ParseFile`、`Execute`等方法从文件或者字符串加载模板,然后执行类似上面图片展示的模板的merge操作。请看下面的例子:
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
t := template.New("some template") //创建一个模板
|
||||
t, _ = t.ParseFiles("tmpl/welcome.html", nil) //解析模板文件
|
||||
user := GetUser() //获取当前用户信息
|
||||
t.Execute(w, user) //执行模板的merger操作
|
||||
}
|
||||
|
||||
通过上面的例子我们可以看到Go语言的模板操作非常的简单方便,和其他语言的模板处理类似,都是先获取数据,然后渲染数据。
|
||||
|
||||
为了演示和测试代码的方便,我们在接下来的例子中采用如下格式的代码
|
||||
|
||||
- 使用Parse代替ParseFiles,因为Parse可以直接测试一个字符串,而不需要额外的文件
|
||||
- 不使用handler来写演示代码,而是每个测试一个main,方便测试
|
||||
- 使用`os.Stdout`代替`http.ResponseWriter`,因为`os.Stdout`实现了`io.Writer`接口
|
||||
|
||||
##模板中如何插入数据?
|
||||
上面我们演示了如何解析并渲染模板,接下来让我们来更加详细的了解如何把数据渲染出来。一个模板都是应用在一个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}}")
|
||||
|
||||
上面的代码就会报错,因为我们调用了一个未导出的字段,但是如果我们调用了一个不存在的字段是不会报错的,而是输出为空。
|
||||
|
||||
如果模板中输出`{{.}}`,这个一般应用与字符串对象,默认会调用fmt包输出字符串的内容。
|
||||
|
||||
###输出嵌套字段内容
|
||||
上面我们例子展示了如何针对一个对象的字段输出,那么如果字段里面还有对象,如何来循环的输出这些内容呢?我们可以使用`{{with …}}…{{end}}`和`{{range …}}{{end}}`来进行数据的输出。详细的使用请看下面的例子:
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
###pipelines
|
||||
Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"name"的数据,他表达的意思就是前面的输出可以当做后面的输入,最后显示我们想要的数据,而Go语言模板最强大的一点就是支持pipe数据,在Go语言里面任何`{{}}`里面的都是pipelines数据,例如我们上面输出的email里面如果还有一些可能引起XSS注入的,那么我们任何来进行转化呢?
|
||||
|
||||
{{. | html}}
|
||||
|
||||
在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体,上面的这种方式和我们平常写Unix的方式是不是一模一样,操作起来相当的简便,调用其他的函数也是类似的方式。
|
||||
|
||||
###条件处理
|
||||
在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`}} 我有内容,我会输出. {{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`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。
|
||||
|
||||
###模板变量
|
||||
有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`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}}
|
||||
###模板函数
|
||||
模板在输出对象的字段值时,采用了`fmt`包把对象转化成了字符串。但是有时候我们的需求可能不是这样的,例如有时候我们为了防止垃圾邮件发送者通过采集网页的方式来发送给我们的邮箱信息,我们希望把`@`替换成`at`例如:`astaxie at beego.me`,如果要实现这样的功能,我们就需要自定义函数来做这个功能。
|
||||
|
||||
每一个模板函数都有一个唯一值的名字,然后与一个Go函数关联,通过如下的方式来关联
|
||||
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
例如,如果我们想要的email函数的模板函数名是`emailDeal`,它关联的Go函数名称是`EmailDealWith`,n那么我们可以通过下面的方式来注册这个函数
|
||||
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
|
||||
`EmailDealWith`这个函数的参数和返回值定义如下:
|
||||
|
||||
func EmailDealWith(args …interface{}) string
|
||||
|
||||
我们来看下面的实现例子:
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
上面演示了如何自定义函数,其实,在模板包内部已经有内置的实现函数,下面代码截取自模板包里面
|
||||
|
||||
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操作
|
||||
模板包里面有一个函数`Must`,它的作用是检测模板是否正确,例如大括号是否匹配,注释是否正确的关闭,变量是否正确的书写。接下来我们演示一个例子,用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 }"))
|
||||
}
|
||||
|
||||
讲输出如下内容
|
||||
|
||||
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
|
||||
|
||||
##嵌套模板
|
||||
我们平常开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成`header`、`content`、`footer`三个部分。Go语言中通过如下的语法来申明
|
||||
|
||||
{{define "子模板名称"}}内容{{end}}
|
||||
|
||||
通过如下方式来调用:
|
||||
|
||||
{{template "子模板名称"}}
|
||||
|
||||
接下来我们演示如何使用嵌套模板,我们定义三个文件,`header.tmpl`、`content.tmpl`、`footer.tmpl`文件,里面的内容如下
|
||||
|
||||
//header.tmpl
|
||||
{{define "header"}}
|
||||
<html>
|
||||
<head>
|
||||
<title>演示信息</title>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
||||
|
||||
//content.tmpl
|
||||
{{define "content"}}
|
||||
{{template "header"}}
|
||||
<h1>演示嵌套</h1>
|
||||
<ul>
|
||||
<li>嵌套使用define定义子模板</li>
|
||||
<li>调用使用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}}都是一个独立的模板,他们相互独立,是并行存在的关系,内部其实存储的是类似map的一种关系(key是模板的名称,value是模板的内容),然后我们通过`ExecuteTemplate`来执行相应的子模板内容,我们可以看到header、footer都是相对独立的,都能输出内容,contenrt中因为嵌套了header和footer的内容,就会同时输出三个的内容。但是当我们执行`s1.Execute`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。
|
||||
|
||||
>同一个集合类的模板是互相知晓的,如果同一模板被多个集合使用,则它需要在多个集合中分别解析
|
||||
|
||||
##总结
|
||||
通过上面对模板的详细介绍,我们了解了如何把动态数据与模板融合:如何输出循环数据、如何自定义函数、如何嵌套模板等等。通过模板技术的应用,我们可以完成MVC模式中V的处理,接下来的章节我们将介绍如何来处理M和C。
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [正则处理](<7.3.md>)
|
||||
* 下一节: [小结](<7.5.md>)
|
||||
|
||||
## LastModified
|
||||
* $Id$
|
||||
Reference in New Issue
Block a user