Skip to content

Commit 9b813ff

Browse files
committed
完成了第七章,第八章的目录完成
1 parent d5e4d8c commit 9b813ff

File tree

10 files changed

+395
-6
lines changed

10 files changed

+395
-6
lines changed

7.4.md

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,336 @@
11
#7.4 模板处理
2+
##什么是模板
3+
我们一定听说过一种设计模式叫做MVC,Model处理数据,View展现结果,Controller控制用户的请求,至于View层的处理,很多动态语言里面都是通过在静态HTML里面插入动态语言的数据,例如JSP中通过插入`<%=....=%>`,PHP中通过插入`<?php.....?>`来实现这种机制。
24

5+
通过下面这个图可以说明模板的机制
6+
7+
![](images/7.4.template.png?raw=true)
8+
9+
当Web应用反馈信息到客户端的时候,其实很多内容都是不变的,也就是类似HTML的静态语言,而有一部分是需要根据用户的请求来实时读取计算的,例如要显示用户的访问记录列表,那么会根据每个用户的访问信息来展示每个人的用户列表,而整个列表的样式都是固定的,只有里面的数据是变化的,所以采用模板可以复用很多静态的代码。
10+
11+
##Go模板使用
12+
在Go语言中,我们使用`template`包来进行模板处理,使用类似`Parse``ParseFile``Execute`等方法从文件或者字符串加载模板,然后执行类似上面图片展示的模板的merge操作。请看下面的例子:
13+
14+
func handler(w http.ResponseWriter, r *http.Request) {
15+
t := template.New("some template") //创建一个模板
16+
t, _ = t.ParseFiles("tmpl/welcome.html", nil) //解析模板文件
17+
user := GetUser() //获取当前用户信息
18+
t.Execute(w, user) //执行模板的merger操作
19+
}
20+
21+
我们通过上面的例子可以看到Go语言的模板操作非常的简单方便,和其他语言的模板处理类似,都是先获取数据,然后渲染数据。
22+
23+
为了我们演示和测试代码方便,我们在接下来的例子中采用如下格式的代码
24+
25+
- 使用Parse代替ParseFiles,因为Parse可以直接测试一个字符串,而不需要额外的文件
26+
- 不使用handler来写演示代码,而是每个测试一个main,方便测试
27+
- 使用`os.Stdout`代替`http.ResponseWriter`,因为`os.Stdout`实现了`io.Writer`接口
28+
29+
##模板中如何插入数据?
30+
上面我们演示了如何解析模板、渲染模板,接下来让我们更加详细的来了解如何把数据渲染出来。一个模板都是应用在一个Go的对象之上,Go对象的字段如何插入到模板中呢?
31+
###字段操作
32+
Go语言的模板通过`{{}}`来包含需要展现的数据,`{{.}}`表示当前的对象,这和Java或者C++中的this类似,如果要访问当前对象的字段通过`{{.FieldName}}`,但是需要注意一点:这个字段必须是导出的(字段首字母必须是大写的),否则在渲染的时候就会报错,请看下面的这个例子:
33+
34+
package main
35+
36+
import (
37+
"html/template"
38+
"os"
39+
)
40+
41+
type Person struct {
42+
UserName string
43+
}
44+
45+
func main() {
46+
t := template.New("fieldname example")
47+
t, _ = t.Parse("hello {{.UserName}}!")
48+
p := Person{UserName: "Astaxie"}
49+
t.Execute(os.Stdout, p)
50+
}
51+
52+
上面的代码我们可以正确的输出`hello Astaxie`,但是如果我们稍微修改一下代码,在模板中含有了未导出的字段,那么就会报错
53+
54+
type Person struct {
55+
UserName string
56+
email string //未导出的字段,首字母是小写的
57+
}
58+
59+
t, _ = t.Parse("hello {{.UserName}}! {{.email}}")
60+
61+
上面的代码就会报错,因为我们调用了一个未导出的字段,但是如果我们调用了一个不存在的字段是不会报错的,而是输出为空。
62+
63+
如果模板中输出`{{.}}`,这个一般应用与字符串对象,默认会调用fmt包输出字符串的内容。
64+
65+
###输出嵌套字段内容
66+
上面我们例子展示了如何针对一个对象的字段输出,那么如果字段里面还有对象,如何来循环的输出这些内容呢?我们可以使用`{{with …}}…{{end}}``{{range …}}{{end}}`来进行数据的输出。详细的使用请看下面的例子:
67+
68+
package main
69+
70+
import (
71+
"html/template"
72+
"os"
73+
)
74+
75+
type Friend struct {
76+
Fname string
77+
}
78+
79+
type Person struct {
80+
UserName string
81+
Emails []string
82+
Friends []*Friend
83+
}
84+
85+
func main() {
86+
f1 := Friend{Fname: "minux.ma"}
87+
f2 := Friend{Fname: "xushiwei"}
88+
t := template.New("fieldname example")
89+
t, _ = t.Parse(`hello {{.UserName}}!
90+
{{range .Emails}}
91+
an emails {{.}}
92+
{{end}}
93+
{{with .Friends}}
94+
{{range .}}
95+
my friend name is {{.Fname}}
96+
{{end}}
97+
{{end}}
98+
`)
99+
p := Person{UserName: "Astaxie",
100+
Emails: []string{"[email protected]", "[email protected]"},
101+
Friends: []*Friend{&f1, &f2}}
102+
t.Execute(os.Stdout, p)
103+
}
104+
105+
###pipelines
106+
Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"name"的数据,他表达的意思就是前面的输出可以当做后面的输入,最后显示我们想要的数据,而Go语言模板最强大的一点就是支持pipe数据,在Go语言里面任何`{{}}`里面的都是pipelines数据,例如我们上面输出的email里面如果还有一些可能引起XSS注入的,那么我们任何来进行转化呢?
107+
108+
{{. | html}}
109+
110+
在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体,上面的这种方式和我们平常写Unix的方式是不是一模一样,操作起来相当的简便,调用其他的函数也是类似的方式。
111+
112+
###条件处理
113+
在Go模板里面如果需要进行条件判断,那么我们可以使用和Go语言的`if-else`语法类似的方式来咱先,如果pipeline为空,那么if就认为是false,下面的例子展示了如何使用`if-else`语法:
114+
115+
package main
116+
117+
import (
118+
"os"
119+
"text/template"
120+
)
121+
122+
func main() {
123+
tEmpty := template.New("template test")
124+
tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不会输出. {{end}}\n"))
125+
tEmpty.Execute(os.Stdout, nil)
126+
127+
tWithValue := template.New("template test")
128+
tWithValue = template.Must(tWithValue.Parse("不为空的 pipeline if demo: {{if `anything`}} 我有内容,我会输出. {{end}}\n"))
129+
tWithValue.Execute(os.Stdout, nil)
130+
131+
tIfElse := template.New("template test")
132+
tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n"))
133+
tIfElse.Execute(os.Stdout, nil)
134+
}
135+
136+
通过上面的演示代码我们知道`if-else`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。
137+
138+
###模板变量
139+
有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`with``range``if`过程中申明局部变量,这个变量的作用域是`{{end}}`之前,Go语言通过申明的局部变量格式如下所示:
140+
141+
$variable := pipeline
142+
143+
详细的例子看下面的:
144+
145+
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
146+
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
147+
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
148+
###模板函数
149+
模板在输出对象的字段值时,采用了`fmt`包把对象转化成了字符串。但是有时候我们的需求可能不是这样的,例如有时候我们为了防止垃圾邮件发送者通过采集网页的方式来发送给我们的邮箱信息,我们希望把`@`替换成`at`例如:`astaxie at beego.me`,如果要实现这样的功能,我们就需要自定义函数来做这个功能。
150+
151+
每一个模板函数都有一个唯一值的名字,然后与一个Go函数关联,通过如下的方式来关联
152+
153+
type FuncMap map[string]interface{}
154+
155+
例如,如果我们想要的email函数的模板函数名是`emailDeal`,它关联的Go函数名称是`EmailDealWith`,n那么我们可以通过下面的方式来注册这个函数
156+
157+
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
158+
159+
`EmailDealWith`这个函数的参数和返回值定义如下:
160+
161+
func EmailDealWith(args …interface{}) string
162+
163+
我们来看下面的实现例子:
164+
165+
package main
166+
167+
import (
168+
"fmt"
169+
"html/template"
170+
"os"
171+
"strings"
172+
)
173+
174+
type Friend struct {
175+
Fname string
176+
}
177+
178+
type Person struct {
179+
UserName string
180+
Emails []string
181+
Friends []*Friend
182+
}
183+
184+
func EmailDealWith(args ...interface{}) string {
185+
ok := false
186+
var s string
187+
if len(args) == 1 {
188+
s, ok = args[0].(string)
189+
}
190+
if !ok {
191+
s = fmt.Sprint(args...)
192+
}
193+
// find the @ symbol
194+
substrs := strings.Split(s, "@")
195+
if len(substrs) != 2 {
196+
return s
197+
}
198+
// replace the @ by " at "
199+
return (substrs[0] + " at " + substrs[1])
200+
}
201+
202+
func main() {
203+
f1 := Friend{Fname: "minux.ma"}
204+
f2 := Friend{Fname: "xushiwei"}
205+
t := template.New("fieldname example")
206+
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
207+
t, _ = t.Parse(`hello {{.UserName}}!
208+
{{range .Emails}}
209+
an emails {{.|emailDeal}}
210+
{{end}}
211+
{{with .Friends}}
212+
{{range .}}
213+
my friend name is {{.Fname}}
214+
{{end}}
215+
{{end}}
216+
`)
217+
p := Person{UserName: "Astaxie",
218+
Emails: []string{"[email protected]", "[email protected]"},
219+
Friends: []*Friend{&f1, &f2}}
220+
t.Execute(os.Stdout, p)
221+
}
222+
223+
224+
上面演示了如何自定义函数,其实,在模板包内部已经有内置的实现函数,下面代码截取自模板包里面
225+
226+
var builtins = FuncMap{
227+
"and": and,
228+
"call": call,
229+
"html": HTMLEscaper,
230+
"index": index,
231+
"js": JSEscaper,
232+
"len": length,
233+
"not": not,
234+
"or": or,
235+
"print": fmt.Sprint,
236+
"printf": fmt.Sprintf,
237+
"println": fmt.Sprintln,
238+
"urlquery": URLQueryEscaper,
239+
}
240+
241+
242+
##Must操作
243+
模板包里面有一个函数`Must`,它的作用是检测模板是否正确,例如大括号是否匹配,是否注释正确的关闭了,是否变量正确的书写。接下来我们演示一个例子,用Must来判断模板是否正确:
244+
245+
package main
246+
247+
import (
248+
"fmt"
249+
"text/template"
250+
)
251+
252+
func main() {
253+
tOk := template.New("first")
254+
template.Must(tOk.Parse(" some static text /* and a comment */"))
255+
fmt.Println("The first one parsed OK.")
256+
257+
template.Must(template.New("second").Parse("some static text {{ .Name }}"))
258+
fmt.Println("The second one parsed OK.")
259+
260+
fmt.Println("The next one ought to fail.")
261+
tErr := template.New("check parse error with Must")
262+
template.Must(tErr.Parse(" some static text {{ .Name }"))
263+
}
264+
265+
讲输出如下内容
266+
267+
The first one parsed OK.
268+
The second one parsed OK.
269+
The next one ought to fail.
270+
panic: template: check parse error with Must:1: unexpected "}" in command
271+
272+
##嵌套模板
273+
我们平常开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成`header``content``footer`三个部分。Go语言中通过如下的语法来申明
274+
275+
{{define "子模板名称"}}内容{{end}}
276+
277+
通过如下方式来调用:
278+
279+
{{template "子模板名称"}}
280+
281+
接下来我们演示如何使用嵌套模板,我们定义三个文件,`header.tmpl``content.tmpl``footer.tmpl`文件,里面的内容如下
282+
283+
//header.tmpl
284+
{{define "header"}}
285+
<html>
286+
<head>
287+
<title>演示信息</title>
288+
</head>
289+
<body>
290+
{{end}}
291+
292+
//content.tmpl
293+
{{define "content"}}
294+
{{template "header"}}
295+
<h1>演示嵌套</h1>
296+
<ul>
297+
<li>嵌套使用define定义子模板</li>
298+
<li>调用使用template</li>
299+
</ul>
300+
{{template "footer"}}
301+
{{end}}
302+
303+
//footer.tmpl
304+
{{define "footer"}}
305+
</body>
306+
</html>
307+
{{end}}
308+
309+
演示代码如下:
310+
311+
package main
312+
313+
import (
314+
"fmt"
315+
"os"
316+
"text/template"
317+
)
318+
319+
func main() {
320+
s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")
321+
s1.ExecuteTemplate(os.Stdout, "header", nil)
322+
fmt.Println()
323+
s1.ExecuteTemplate(os.Stdout, "content", nil)
324+
fmt.Println()
325+
s1.ExecuteTemplate(os.Stdout, "footer", nil)
326+
fmt.Println()
327+
s1.Execute(os.Stdout, nil)
328+
}
329+
330+
通过上面的例子我们可以看到通过`template.ParseFiles`把所有的嵌套模板全部解析到模板里面,其实每一个定义的{{define}}都是一个独立的模板,他们相互独立,是并行存在的关系,内部其实存储的是类似map的一种关系(key是模板的名称,value是模板的内容),然后我们通过`ExecuteTemplate`来执行相应的子模板内容,我们可以看到header、footer都是相对独立的,都能输出内容,contenrt中因为嵌套了header和footer的内容,就会同时输出三个的内容。但是当我们执行`s1.Execute`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。
331+
332+
##总结
333+
通过上面对模板的详细介绍,我们了解了如何把数据输出到模板、如何输出循环数据、如何自定义函数、如何嵌套模板,通过这些介绍我们可以把数据转化成模板数据输出到客户端。这样通过模板技术的应用,我们可以完成MVC模式中V的处理,接下来我们将介绍如何来处理M和C。
3334
## links
4335
* [目录](<preface.md>)
5336
* 上一节: [正则处理](<7.3.md>)

7.5.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#7.5 小结
2+
这一章给大家介绍了一些文本处理的工具,包括XML、JSON、正则和模板技术,XML和JSON是数据交互的工具,通过XML和JSON你可以表达各种含义,通过正则你可以处理文本(搜索、替换、截取),通过模板技术你可以展现这些数据给用户。这些都是你开发Web应用过程中需要用到的技术,通过这个小节的介绍你能够了解如何处理文本、展现文本。
23

34
## links
45
* [目录](<preface.md>)

8.1.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#8.1 webSockets
2+
## links
3+
* [目录](<preface.md>)
4+
* 上一节: [Web服务](<8.md>)
5+
* 下一节: [Socket编程](<8.2.md>)
6+
7+
## LastModified
8+
* $Id$

8.2.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#8.2 Socket编程
2+
## links
3+
* [目录](<preface.md>)
4+
* 上一节: [webSockets](<8.1.md>)
5+
* 下一节: [REST](<8.3.md>)
6+
7+
## LastModified
8+
* $Id$

8.3.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#8.3 REST
2+
## links
3+
* [目录](<preface.md>)
4+
* 上一节: [Socket编程](<8.2.md>)
5+
* 下一节: [RPC](<8.4.md>)
6+
7+
## LastModified
8+
* $Id$

8.4.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#8.4 RPC
2+
## links
3+
* [目录](<preface.md>)
4+
* 上一节: [REST](<8.3.md>)
5+
* 下一节: [小结](<8.5.md>)
6+
7+
## LastModified
8+
* $Id$

8.5.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#8.5 小结
2+
## links
3+
* [目录](<preface.md>)
4+
* 上一节: [RPC](<8.4.md>)
5+
* 下一章: [安全与加密](<9.md>)
6+
7+
## LastModified
8+
* $Id$

0 commit comments

Comments
 (0)