ZetCode

Go 模板

最后修改时间 2024 年 4 月 11 日

在本文中,我们将展示如何使用标准库在 Golang 中创建模板。

模板引擎或模板处理器是一种旨在将模板与数据模型相结合以生成文档的库。模板引擎通常用于生成大量电子邮件、在源代码预处理中,或生成动态 HTML 页面。

我们创建一个模板引擎,其中定义了静态部分和动态部分。动态部分稍后会被数据替换。渲染函数稍后会将模板与数据结合起来。模板引擎用于将模板与数据模型相结合以生成文档。

Go 包含两个模板包:text/templatehtml/template。两者共享相同的接口。html/template 会自动保护 HTML 输出免受某些攻击。

在模板 API 中,Parse 函数解析程序中存在的模板字符串,ParseFiles 加载并解析模板文件,而 Execute 使用特定的数据字段将模板渲染到输出。New 函数使用给定的名称分配一个新的、未定义的模板。

与许多其他模板引擎类似,数据求值和控制结构由 {{}} 分隔。

Go 模板 Parse

Parse 函数解析 Go 程序中的模板字符串。

main.go
package main

import (
    "log"
    "os"
    "text/template"
)

type User struct {
    Name       string
    Occupation string
}

func main() {

    user := User{"John Doe", "gardener"}

    tmp := template.New("simple")
    tmp, err := tmp.Parse("{{.Name}} is a {{.Occupation}}")

    if err != nil {
        log.Fatal(err)
    }

    err2 := tmp.Execute(os.Stdout, user)

    if err2 != nil {
        log.Fatal(err2)
    }
}

示例创建一个简单的文本消息。

type User struct {
    Name       string
    Occupation string
}

这是模板中使用的数据类型;字段必须是导出的,即大写。

tmp := template.New("simple")

创建一个新模板。

tmp, err := tmp.Parse("{{.Name}} is a {{.Occupation}}")

我们使用 Parse 解析模板字符串。使用点运算符,我们访问传递给模板引擎的字段。

$ go run main.go
John Doe is a gardener

Go 模板 Must

Must 函数是一个辅助函数,负责错误检查。

main.go
package main

import (
    "log"
    "os"
    "text/template"
)

type User struct {
    Name       string
    Occupation string
}

func main() {

    user := User{"John Doe", "gardener"}

    tmp := template.Must(template.New("simple").Parse("{{.Name}} is a {{.Occupation}}"))

    f, err := os.Create("output.txt")

    if err != nil {
        log.Fatal(err)
    }

    defer f.Close()

    err2 := tmp.Execute(f, user)

    if err2 != nil {
        log.Fatal(err2)
    }
}

在示例中,我们使用模板将消息写入文件。我们还使用 Must 函数。

$ go run main.go
$ cat output.txt
John Doe is a gardener

Go 模板 ParseFiles

ParseFiles 函数创建一个新模板,并解析给定文件名的模板定义。

message.txt
{{.Name}} is a {{.Occupation}}

这是模板文件。

main.go
package main

import (
    "log"
    "os"
    "text/template"
)

type User struct {
    Name       string
    Occupation string
}

func main() {

    user := User{"John Doe", "gardener"}

    tmp, err := template.ParseFiles("message.txt")

    if err != nil {
        log.Fatal(err)
    }

    err2 := tmp.Execute(os.Stdout, user)

    if err2 != nil {
        log.Fatal(err2)
    }
}

在示例中,我们从模板文件创建一个简单的消息。

Go 模板 range

range 指令在模板中遍历数组、切片、映射或通道的项。

words.txt
{{range .Words -}}
    {{ .}}
{{end}}

在模板中,我们使用 range 指令遍历 Words 数据结构的元素。- 字符会删除空格字符。

main.go
package main

import (
    "log"
    "os"
    "text/template"
)

type Data struct {
    Words []string
}

func main() {

    data := Data{Words: []string{"sky", "blue", "forest", "tavern", "cup", "cloud"}}

    tmp, err := template.ParseFiles("words.txt")

    if err != nil {
        log.Fatal(err)
    }

    err2 := tmp.Execute(os.Stdout, data)

    if err2 != nil {
        log.Fatal(err2)
    }
}

在程序中,我们将单词切片传递给模板引擎。我们得到一个单词列表作为输出。

$ go run main.go
sky
blue
forest
tavern
cup
cloud

Go 模板条件

可以使用 if/else if/else 指令创建条件。

data.txt
{{- range .Todos -}}
    {{if .Done}}
        {{- .Title -}}
    {{end}}
{{end}}

在模板文件中,我们使用 if 指令仅输出已完成的任务。

main.go
package main

import (
    "log"
    "os"
    "text/template"
)

type Todo struct {
    Title string
    Done  bool
}

type Data struct {
    Todos []Todo
}

func main() {

    data := Data{Todos: []Todo{
        {Title: "Task 1", Done: false},
        {Title: "Task 2", Done: true},
        {Title: "Task 3", Done: true},
        {Title: "Task 4", Done: false},
        {Title: "Task 5", Done: true},
    }}

    tmp := template.Must(template.ParseFiles("data.txt"))

    err2 := tmp.Execute(os.Stdout, data)

    if err2 != nil {
        log.Fatal(err2)
    }
}

我们从待办事项切片生成输出。输出仅包含已完成的任务。

Go 模板服务器示例

下面的示例在服务器应用程序中使用模板。

layout.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Users</title>
</head>

<body>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Occupation</th>
            </tr>
        </thead>
        <tbody>
            {{ range .Users}}
            <tr>
                <td>{{.Name}}</td>
                <td>{{.Occupation}}</td>
            </tr>
            {{ end }}
        </tbody>
    </table>
</body>
</html>

输出是 HTML 文件。数据被插入到 HTML 表中。

main.go
package main

import (
    "html/template"
    "log"
    "net/http"
)

type User struct {
    Name       string
    Occupation string
}

type Data struct {
    Users []User
}

func main() {
    tmp := template.Must(template.ParseFiles("layout.html"))
    http.HandleFunc("/users", func(w http.ResponseWriter, _ *http.Request) {

        data := Data{
            Users: []User{
                {Name: "John Doe", Occupation: "gardener"},
                {Name: "Roger Roe", Occupation: "driver"},
                {Name: "Peter Smith", Occupation: "teacher"},
            },
        }
        tmp.Execute(w, data)
    })

    log.Println("Listening...")
    http.ListenAndServe(":8080", nil)
}

Web 服务器为 /users URL 路径返回一个带有用户表的 HTML 页面。

Go 邮件模板

在下面的示例中,我们使用邮件模板为多个用户生成邮件。我们使用 Mailtrap 邮件测试服务。

$ mkdir template
$ cd template 
$ go mod init com/zetcode/TemplateEmail
$ go get github.com/shopspring/decimal 

我们初始化项目并添加外部 github.com/shopspring/decimal 包。

template.go
package main

import (
    "bytes"
    "fmt"
    "log"
    "net/smtp"
    "text/template"

    "github.com/shopspring/decimal"
)

type Mail struct {
    Sender  string
    To      string
    Subject string
    Body    bytes.Buffer
}

type User struct {
    Name  string
    Email string
    Debt  decimal.Decimal
}

func main() {

    sender := "john.doe@example.com"

    var users = []User{
        {"Roger Roe", "roger.roe@example.com", decimal.NewFromFloat(890.50)},
        {"Peter Smith", "peter.smith@example.com", decimal.NewFromFloat(350)},
        {"Lucia Green", "lucia.green@example.com", decimal.NewFromFloat(120.80)},
    }

    my_user := "username"
    my_password := "password"
    addr := "smtp.mailtrap.io:2525"
    host := "smtp.mailtrap.io"

    subject := "Amount due"

    var template_data = `
    Dear {{ .Name }}, your debt amount is ${{ .Debt }}.`

    for _, user := range users {

        t := template.Must(template.New("template_data").Parse(template_data))
        var body bytes.Buffer

        err := t.Execute(&body, user)
        if err != nil {
            log.Fatal(err)
        }

        request := Mail{
            Sender:  sender,
            To:      user.Email,
            Subject: subject,
            Body:    body,
        }

        msg := BuildMessage(request)
        auth := smtp.PlainAuth("", my_user, my_password, host)
        err2 := smtp.SendMail(addr, auth, sender, []string{user.Email}, []byte(msg))

        if err2 != nil {
            log.Fatal(err)
        }
    }

    fmt.Println("Emails sent successfully")
}

func BuildMessage(mail Mail) string {
    msg := ""
    msg += fmt.Sprintf("From: %s\r\n", mail.Sender)
    msg += fmt.Sprintf("To: %s\r\n", mail.To)
    msg += fmt.Sprintf("Subject: %s\r\n", mail.Subject)
    msg += fmt.Sprintf("\r\n%s\r\n", mail.Body.String())

    return msg
}

在示例中,我们向多个用户发送邮件,提醒他们还款。

var users = []User{
    {"Roger Roe", "roger.roe@example.com", decimal.NewFromFloat(890.50)},
    {"Peter Smith", "peter.smith@example.com", decimal.NewFromFloat(350)},
    {"Lucia Green", "lucia.green@example.com", decimal.NewFromFloat(120.80)},
}

这些是借款人。

var template_data = `
    Dear {{ .Name }}, your debt amount is ${{ .Debt }}.`

这是模板;它包含一个通用消息,其中 .Name.Debt 占位符被替换为实际值。

for _, user := range users {

    t := template.Must(template.New("template_data").Parse(template_data))
    var body bytes.Buffer

    err := t.Execute(&body, user)
    if err != nil {
        log.Fatal(err)
    }

    request := Mail{
        Sender:  sender,
        To:      user.Email,
        Subject: subject,
        Body:    body,
    }

    msg := BuildMessage(request)
    auth := smtp.PlainAuth("", my_user, my_password, host)
    err2 := smtp.SendMail(addr, auth, sender, []string{user.Email}, []byte(msg))

    if err2 != nil {
        log.Fatal(err)
    }
}

我们遍历借款人切片,为每个人生成一封邮件。Execute 函数将解析后的模板应用于指定的数据对象。生成消息后,它会通过 SendMail 发送。

来源

Go text/template - 参考

在本文中,我们使用内置的模板包创建了动态文档。

作者

我的名字是 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面有十多年的经验。

列出所有 Go 教程