第四章 复合数据类型
基础数据类型是Go语言世界的原子
复合数据类型包括四种:slice、map、 struct、array
数组和结构体是聚合类型,它们的值由许多元素或成员构成,数组和结构体都是固定内存大小的数据结构,,相比之下,slice和map则是动态的数据结构,它们将根据动态增长
4.6 文本和HTML模版
前面的例子,只是最简单的格式化,使用Printf是完全足够的
But,有时候需要复杂的打印格式,这时候一般需要将格式化带么分离出来以便更安全的修改,这些功能是由text/template和html/template等模版包提供的,它们提供了一个将变量填充到一个文本或HTML格式的模版机制
一个模版是一个字符串或一个文件,里面包含了一个或多个由双花括号包含的{{action}}对象。大部分的字符串只是按面值打印,但是对于actions部分将触发其它行为。每个actions都包含了一个用模版语言书写的表达式,一个action虽然简短但是可以输出复杂的打印值,模版语言包含通过选择结构体的成员、调用函数或方法、表达式控制流if-else语句和range循环语句,还有其它实例化模版等诸多特性,下面是一个简单的模版字符串
const temp1 = `{{.TotalCount}} issues:
{{range.Items}}----------------------------------------
Number:{{.Number}}
User: {{.User.Login}}
Title: {{.Title | printf "%.64s"}}
Age: {{.CreatedAt | daysAgo}} days
{{.end}}`
这个模版先打印匹配到的issues总数,然后再打印每个issue的编号、创建用户、标题还有存在的时间。对于每个action,都有一个当前值的概念。当前值 . 点操作符最初被初始化为调用模版时的参数,在当前例子中对应github.IssuesSearchResult类型的变量,模板中{{.TotalCount}}对应action将展开为结构体中TotalCount成员以默认的方式打印的值。模版中{{range.Items}}和{{end}}对应一个循环action,因此它们直接的内容可能会被展开多次,循环每次迭代的当前值对应当前的items元素的值
在一个action中,|操作符表示将前一个表达式的结果作为后一个函数的输入,类似于UNIX中管道的概念。在Title这一行的action中,第二个操作是Printf函数,是一个基于fmt.Sprintf实现的内置函数,所有的模版都可以直接使用。对于Age部分,第二个动作是一个叫daysAgo的函数,通过time.Since函数将CreatedAt成员转换为过去的时间长度:
func daysAgo(t time.Time) int{
return int(time.Since(t).Hours()/24)
}
需要注意的是createdAt的参数是time.Time,并不是字符串。以同样的方式,我们可以通过定义一些方法来控制字符串和格式化,一个类型同样可以定制自己的JSON编码和解码行为。time.Time类型对应的JSON值是一个标准时间格式的字符串
生成模版的输出需要两个处理步骤:第一,分析模版并转为内部表示,第二,基于指定的输入执行模板。分析模版部分一般只需要执行一次
如下代码创建并分析上面定义的模板templ。注意方法调用链的顺序:template.New先创建并返回一个模版;Funcs方法将daysAgo等自定义函数注册到模板中,并返回模版,最后调用parse函数分析模版
report,err := template.New("report").
Funcs(template.FuncMap{"daysAgo":daysAgo}).Parse(temp1)
if err != nil {
log.Fatal(err)
}
因为模板通常在编译时就测试好了,如果模板解析失败将是一个致命的错误。Template.Must 辅助函数可以化简这个致命错误的处理:它接受一个模版和一个error类型的参数,检测error是否为nil(如果不是nil则发出Panic异常),然后返回传入模版
一旦模板已经创建、注册了days Ago函数、并通过分析和检测,我们就可以使用github.issuesSearchResult作为输入源,os.Stdout作为输出源来执行模版:
var report = template.Must(template.New("issuelist").Funcs(template.FuncMap{"dayAgo":daysAgo}).Parse(temp1))
result,err := github.SearchIssues(os.Args[1:])
if err != nil{
log.Fatalf(err)
}
if err := report.Execute(os.Stdout,result);err != nil {
log.Fatal(err)
}
运行程序,程序会输出一个纯文本报告
现在让我们把目光转向html/template模版包,它使用和text/template包相同的API和模版语言,但是增加了一个将字符串自动转义特性,这可以避免输入字符串和HTML、JavaScript、CSS或URL语法产生冲突的问题,这个特性可以避免一些长期存在的安全问题,比如通过HTML注入攻击,通过构造一个含有恶意代码的问题标题,这些都可能让模版输出错误的输出,从而让他们控制页面
下面的模版以HTML格式输出issue列表:注意import语句的不同
var issuelist = template.Must(template.New("issuelist").Parse(`
<h1>{{.TotalCount}} issues</h1>
<table>
<tr style='text-align:left'>
<th>#</th>
<th>State</th>
<th>User</th>
<th>Title</th>
</tr>
{{range.Items}}]
<tr>
<td><a href= '{{.HTMLURL}}'>{{.Number}}</a></td>
<td>'{{.State}}'</td>
<td><a href= '{{.User.HTMLURL}}'>{{.User.login}}</a></td>
<td><a href= '{{.HTMLURL}}'>{{.Title}}</a></td>
`))
通过命令行去执行查询