Go语言圣经 - 第4章 复合数据类型 - 4.6 文本和HTML模版

第四章 复合数据类型

基础数据类型是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>
	`))

通过命令行去执行查询

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值