功能简介:
html模板生成:
- html/template包实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用text/template包。
下面语法是知识的一个总结版。模板的使用方法可以需要其他博客~~~~~
例子:
下面这个完整可运行的。访问GitHub获取仓库内容、评论、星级(需要换成对于api)
1、GitHub访问的实体类
IssuesSearchResult
package github
import "time"
const IssuesURL = "https://api.github.com/search/issues"
type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issue
}
type Issue struct {
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string // in Markdown format
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
2、发起http请求获取数据,并解析json数据为 对象
package github
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// SearchIssues queries the GitHub issue tracker.
func SearchIssues(terms []string) (*IssuesSearchResult, error) {
q := url.QueryEscape(strings.Join(terms, " "))
//resp, err := http.Get(IssuesURL + "?q=" + q)
//if err != nil {
// return nil, err
//}
//!-
// For long-term stability, instead of http.Get, use the
// variant below which adds an HTTP request header indicating
// that only version 3 of the GitHub API is acceptable.
//
req, err := http.NewRequest("GET", IssuesURL+"?q="+q, nil)
if err != nil {
return nil, err
}
req.Header.Set(
"Accept", "application/vnd.github.v3.text-match+json")
resp, err := http.DefaultClient.Do(req)
//!+
// We must close resp.Body on all execution paths.
// (Chapter 5 presents 'defer', which makes this simpler.)
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
return &result, nil
}
3、将数据对象 渲染成模板输出
package main
import (
"log"
"os"
"gopl.io/ch4/github"
)
//!+template
import "html/template"
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>
</tr>
{{end}}
</table>
`))
//!-template
//!+
func main() {
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
// 可以输出到 html 上。这个我相信你会 使用 log.Fatal(http.ListenAndServe("0.0.0.0:20201", nil))即可
if err := issueList.Execute(os.Stdout, result); err != nil {
log.Fatal(err)
}
}
模板语法
模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象。如果有学过 thinkphp(TP)或者 thymeleaf ,那么学习这个就很快了。
{{.}}
- {{.}}中的点表示当前对象
-
当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如:
参数对象:
var user = struct { name string age int }{ name:"zhaohaiyu", age:18, }
模板参数:
// main.go
func sayHello(w http.ResponseWriter, r *http.Request) {
// 解析指定文件生成模板对象
tmpl, err := template.ParseFiles("./hello.html")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
var user = struct {
name string
age int
}{
name:"zhaoshuai",
age:18,
}
// 利用给定数据渲染模板,并将结果写入w
tmpl.Execute(w, user)
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("HTTP server failed,err:", err)
return
}
}
// hello.html
Hello
姓名 {{.Name}}
年龄:{{.Age}}
同理,当我们传入的变量是map时,也可以在模板文件中通过.根据key来取值。
移除空格
- 有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。
例如:{{- .Name -}}
注意:-要紧挨{{和}},同时与模板值之间需要使用空格分隔。
变量
- 我们还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下:
$obj := {{.}}
其中$obj是变量的名字,在后续的代码中就可以使用该变量了。
例如:
var categoryGroupList = template.Must(template.New("categoryGroupList").Parse(`
<h1>公司名称:{{.CompanyName}}</h1>
<h2>公司Id:{{.CompanyId}}</h2>
<h2>分组Id:{{.Id}}</h2>
{{$obj := . }}
<table>
<tr style='text-align: left'>
<th>#</th>
<th>分组名称</th>
<th>CategoryCount</th>
<th>DisableCount</th>
<th>业务分类</th>
<th>子分组</th>
</tr>
{{range $i, $v := .Children}}
<tr>
<td>{{$i}}</td>
<td>{{$v.Name}}</td>
<td>{{$v.CategoryCount}}</td>
<td>{{$v.DisableCount}}</td>
{{if .CategoryList}}
<td><a href='/cs?companyName={{$obj.CompanyName}}&companyId={{$obj.CompanyId}}&groupId={{$v.Id}}'>业务分类len:{{len .CategoryList}}</a></td>
{{end}}
{{if .Children}}
<td><a href='/cgs?companyName={{$obj.CompanyName}}&companyId={{$obj.CompanyId}}&groupId={{$v.Id}}'>子分组len:{{len .Children}}</a></td>
{{end}}
</tr>
{{end}}
</table>
`))
pipeline
- pipeline是指产生数据的操作。比如{{.}}、{{.Name}}等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
注意:并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。
条件判断
- Go模板语法中的条件判断有以下几种:
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
range
Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。
{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T0 {{end}}
// 指定内容
{{range v := .List}}
<p>value:{{v}}</p>
{{end}}
//例如 指定下标 和 内容
{{range $i, $v := .List}}
<p>index:{{$i}}</p>
<p>value:{{$v}}</p>
// $v 等价于.
<p>value:{{$v}}</p>
{{end}}
//range中使用 其他非range的对象。这里和vue函数处理参数一样。需要定义参数
{{$obj := . }}
{{range $i, $v := .List}}
<p>index:{{$i}}</p>
<p>value:{{$v}}</p>
// $v 等价于.
<p>value:{{$v}}</p>
// 父级内容
<p>pValue:{{$obj.Name}}</p>
{{end}}
预定义函数
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。
预定义的全局函数如下:
- and
- 函数返回它的第一个empty参数或者最后一个参数;
- 就是说"and x y"等价于"if x then y else x";所有参数都会执行;
- or
- 返回第一个非empty参数或者最后一个参数;
- 亦即"or x y"等价于"if x then x else y";所有参数都会执行;
- not
- 返回它的单个参数的布尔值的否定
- len
- 返回它的参数的整数类型长度
- index
- 执行结果为第一个参数以剩下的参数为索引/键指向的值;
- 如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
- 假设场景,取第一个集合对象的某个属性。使用方法:(index .List 0).Name
- print
- 即fmt.Sprint
- printf
- 即fmt.Sprintf
- println
- 即fmt.Sprintln
- html
- 返回与其参数的文本表示形式等效的转义HTML。
- 这个函数在html/template中不可用。
- urlquery
- 以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
- 这个函数在html/template中不可用。
- js
- 返回与其参数的文本表示形式等效的转义JavaScript。
- call
- 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
- 如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
其中Y是函数类型的字段或者字典的值,或者其他类似情况;
call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
比较函数
- 布尔函数会将任何类型的零值视为假,其余视为真。
下面是定义为函数的二元比较运算的集合:
eq 如果arg1 == arg2则返回真
ne 如果arg1 != arg2则返回真
lt 如果arg1 < arg2则返回真
le 如果arg1 <= arg2则返回真
gt 如果arg1 > arg2则返回真
ge 如果arg1 >= arg2则返回真
为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:
{{eq arg1 arg2 arg3}}
比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。但是,整数和浮点数不能互相比较。
自定义函数
Go的模板支持自定义函数。
func sayHello(w http.ResponseWriter, r *http.Request) {
htmlByte, err := ioutil.ReadFile("./hello.tmpl")
if err != nil {
fmt.Println("read html failed, err:", err)
return
}
// 自定义一个夸人的模板函数
kua := func(arg string) (string, error) {
return arg + "真帅", nil
}
// 采用链式操作在Parse之前调用Funcs添加自定义的kua函数
tmpl, err := template.New("hello").Funcs(template.FuncMap{"kua": kua}).Parse(string(htmlByte))
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfo{
Name: "小王子",
Gender: "男",
Age: 18,
}
// 使用user渲染模板,并将结果写入w
tmpl.Execute(w, user)
}
我们可以在模板文件hello.tmpl中按照如下方式使用我们自定义的kua函数了。
{{kua .Name}}
嵌套template
我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。
举个例子:t.tmpl文件内容如下:tmpl test
测试嵌套template语法 {{template "ul.tmpl"}}
{{template "ol.tmpl"}}
{{ define "ol.tmpl"}}
吃饭
睡觉
打豆豆
{{end}}
ul.tmpl文件内容如下:
注释
日志
测试
我们注册一个templDemo路由处理函数.
http.HandleFunc("/tmpl", tmplDemo)
tmplDemo函数的具体内容如下:
func tmplDemo(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfo{
Name: "小王子",
Gender: "男",
Age: 18,
}
tmpl.Execute(w, user)
}
注意:在解析模板时,被嵌套的模板一定要在后面解析,例如上面的示例中t.tmpl模板中嵌套了ul.tmpl,所以ul.tmpl要在t.tmpl后进行解析。
block
{{block "name" pipeline}} T1 {{end}}
block是定义模板{{define "name"}} T1 {{end}}和执行{{template "name" pipeline}}缩写,典型的用法是定义一组根模板,然后通过在其中重新定义块模板进行自定义。
定义一个根模板templates/base.tmpl,内容如下:Go Templates
{{block "content" . }}{{end}}
然后定义一个templates/index.tmpl,”继承”base.tmpl:
{{template "base.tmpl"}}
{{define "content"}}
Hello world!
{{end}}
修改默认的标识符
Go标准库的模板引擎使用的花括号{{和}}作为标识,而许多前端框架(如Vue和AngularJS)也使用{{和}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:
template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")