Go 模板

原文连接 php

不少语言都有不少方式将字符串从一只形式转换成另外一种形式。Go 使用模板的方法经过提供一个对象做为参数来转换字符串。这个通常来说是用来将对象插入到HTML中的,不过它一样能够用在其余的状况下。注意这部分跟网络编程毫无关系,不过对于网络编程来讲颇有用。html

介绍

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

插入对象

   一个模板应用到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的输出前加一个名字做为前缀,入下: spa

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的模板对于一些将对象插入到模板的文本转换颇有帮助。他没有使用功能强大的正则表达式,可是相比正则表达式它更快更易用。

相关文章
相关标签/搜索