模板是定义特定模式的文件,为自动化提供空间,无论是简单的文本文件还是网页的HTML文件。如果它提供了定制的空间,它就是一个模板。
你可以编写编程逻辑来解析简单的模板,但随着你在模板中想要的定制水平的提高,你需要定制的编程逻辑也会增加。在这一点上,编写模板解析逻辑变得不太可行。
编程语言和Web开发框架支持开箱即用的模板解析,或者提供模板解析的库。
在这篇文章中,我们将探讨Go编程语言中支持模板解析的功能。我们可以使用这些方法来解析任何文件扩展名,但在本文中我们将只使用文本文件。
本指南也将只显示Golang代码和理解Golang代码所需的最少的模板(文本)。
Golang模板教程的前提条件
在开始我们的教程之前,你应该具备以下条件。
- 对Go有一定的了解
- 在你的机器上安装了Go 1.x运行时间。
你也可以克隆 指南的资源库 来获取完整的模板文件,或者输入以下内容。
git clone https://github.com/Bamimore-Tomi/go-templates-guide.git
使用 Golang 模板工作
在本节中,我们将探索Go中的包的功能。 [text/template]([https://blog.logrocket.com/tag/go/](https://blog.logrocket.com/tag/go/))包 的功能。
使用ParseFiles
要使用模板,你必须把它们解析到你的 Golang 程序中。
text/template 标准库提供了解析我们程序所需的函数。
package main
import (
"log"
"os"
"text/template"
)
// Prints out the template without passing any value using the text/template package
func main() {
template, err := template.ParseFiles("template-01.txt")
// Capture any error
if err != nil {
log.Fatalln(err)
}
// Print out the template to std
template.Execute(os.Stdout, nil)
}
//OUTPUT
// Hi <no value>
// You are welcome to this tutorial
上面的程序打印了一个名为template-01.txt 的模板文件。template 变量持有该文件的内容。为了将文件打印出来,Stdout ,我们调用了Execute 方法。
使用ParseGlob
对于一次解析多个文件,ParseGlob 函数很有用。
package main
import (
"log"
"os"
"text/template"
)
// Parse all the files in a certain directory
func main() {
// This function takes a pattern. It can be a folder
temp, err := template.ParseGlob("templates/*")
if err != nil {
log.Fatalln(err)
}
// Simply calling execute parses the first file in the directory
err = temp.Execute(os.Stdout, nil)
if err != nil {
log.Fatalln(err)
}
// Or you can execute a particular template in the directory
err = temp.ExecuteTemplate(os.Stdout, "template-03.txt", nil)
if err != nil {
log.Fatalln(err)
}
// Calling a template not in the directory will produce an error
err = temp.ExecuteTemplate(os.Stdout, "template-04.txt", nil)
if err != nil {
log.Fatalln(err)
}
}
通过这段代码,我们解析了templates/ 目录中的所有文件到我们的程序中。要执行任何一个被解析的模板,我们对ParseGlob 的结果调用ExecuteTemplate 方法。
使用Execute 方法
Execute 方法是我们解析数据到模板(s)的地方。这里是template-04.txt 文件。
Hello {{.}}
You are doing great. Keep learning.
Do not stop {{.}}
{{.}} 告诉text/template 包在哪里放置传入模板的数据。在这个模板中,我们要在两个地方设置数据:第1行和第4行。
package main
import (
"log"
"os"
"text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-04.txt"))
}
func main() {
// Execute myName into the template and print to Stdout
myName := "Oluwatomisin"
err := temp.Execute(os.Stdout, myName)
if err != nil {
log.Fatalln(err)
}
}
// Hello Oluwatomisin
// You are doing great. Keep learning.
// Do not stop Oluwatomisin
在这里,我们使用稍微不同的语法来初始化模板。temp.Execute 需要一个io.writer 和data interface{} ,在这个例子中就是myName 。
你也可以在你的模板中传递更复杂的数据结构;这样做的唯一变化是你如何在模板文件内访问这些结构,我们将在下一节讨论。
在模板中声明变量
我们也可以在模板中直接初始化变量。请看template-05.txt 中的例子。
Hello {{.Name}}
{{$aTrait := .Trait}}
You are {{$aTrait}}
请注意我们在模板内使用数据的方式发生了变化。template.Execute 需要一个data interface{} 参数,这意味着我们可以在模板中执行一个 [struct](https://blog.logrocket.com/exploring-structs-and-interfaces-in-go/) 在模板中。
package main
import (
"log"
"os"
"text/template"
)
type PersonTrait struct {
Name string
Trait string
}
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-05.txt"))
}
func main() {
// Execute person into the template and print to Stdout
person := PersonTrait{Name: "Oluwatomisin", Trait: "a great writer"}
err := temp.Execute(os.Stdout, person)
if err != nil {
log.Fatalln(err)
}
}
// Hello Oluwatomisin
// You are a great writer
在这个例子中,我们在模板中执行PersonTrait 结构。这样,你可以在模板中执行任何数据类型。
在Go模板中使用循环
text/template 包也允许你在模板中运行循环。在template-06.txt ,我们将列出一些可爱的宠物。
Animals are cute; some cute animals are:
{{range .}}
{{.}}
{{end}}
在程序中,我们必须在模板中执行可爱宠物的片断。
package main
import (
"log"
"os"
"text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
// Execute cuteAnimals into the template and print to Stdout
cuteAnimals := []string{"Dogs", "Cats", "Mice", "Fish"}
err := temp.Execute(os.Stdout, cuteAnimals)
if err != nil {
log.Fatalln(err)
}
}
// Animals are cute, some cute animals are:
// Dogs
// Cats
// Mice
// Fish
如果需要的话,我们也可以在一个map 循环。
Animals are cute, some cute animals are:
{{range $key, $val := .}}
{{$key}} , {{$val}}
{{end}}
然后,我们可以在程序中创建并执行map 。
package main
import (
"log"
"os"
"text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
// Execute cuteAnimalsSpecies into the template and print to Stdout
cuteAnimalsSpecies := map[string]string{
"Dogs": "German Shepherd",
"Cats": "Ragdoll",
"Mice": "Deer Mouse",
"Fish": "Goldfish",
}
err := temp.Execute(os.Stdout, cuteAnimalsSpecies)
if err != nil {
log.Fatalln(err)
}
}
// Animals are cute, some cute animals are:
// Cats , Ragdoll
// Dogs , German Shepherd
// Fish , Goldfish
// Mice , Deer Mouse
Golang模板中的条件式
为了给我们的模板增加更多的自定义功能,我们可以使用条件语句。要使用条件语句,我们必须在模板内调用比较函数。在这个例子中,我们可以检查一个随机整数是否小于或大于200。
{{if (lt . 200)}}
Number {{.}} is less than 200
{{else}}
Number {{.}} is greater than 200
{{end}}
(lt . 200) 语法是我们使用lt 来比较随机整数值。其他运算符包括以下内容。
lt为小于运算符gt为大于运算符eq等于o的运算符ne用于不等于运算符le小于或等于操作符ge为大于或等于运算符。
现在,我们可以在我们的主程序中生成随机值。
package main
import (
"log"
"math/rand"
"os"
"text/template"
"time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
// Generate random number between 100 and 300
rand.Seed(time.Now().UnixNano())
min := 100
max := 300
// Execute myNumber into the template and print to Stdout
myNumber := rand.Intn((max-min)+1) + min
err := temp.Execute(os.Stdout, myNumber)
if err != nil {
log.Fatalln(err)
}
}
// Number 141 is less than 200
在模板中使用函数
text/template 包还提供了一种在模板中执行自定义函数的方法。一个著名的例子是将时间戳转换为其他日期格式。
Hi,
Time before formatting : {{.}}
Time After formatting : {{formatDate .}}
该模板显示了用formatDate 函数解析前后的时间。观察一下可以调用自定义函数的语法。
package main
import (
"log"
"os"
"text/template"
"time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.New("template-07.txt").Funcs(funcMap).ParseFiles("template-08.txt"))
}
// Custom function must have only 1 return value, or 1 return value and an error
func formatDate(timeStamp time.Time) string {
//Define layout for formatting timestamp to string
return timeStamp.Format("01-02-2006")
}
// Map name formatDate to formatDate function above
var funcMap = template.FuncMap{
"formatDate": formatDate,
}
func main() {
timeNow := time.Now()
err := temp.Execute(os.Stdout, timeNow)
if err != nil {
log.Fatalln(err)
}
}
// Hi,
// Time before formatting : 2021-10-04 18:01:59.6659258 +0100 WAT m=+0.004952101
// Time After formatting : 09-04-2021
布局格式必须遵循时间戳的格式,在这种情况下,Jan 2 15:04:05 2006 MST ;查看该方法的文档以了解更多信息。
然而,在这里,我们使用template.FuncMap 来映射一个string 到自定义函数。然后,该函数将使用模板内的字符串进行引用。
总结
我们已经看到了text/template 包中的一系列功能和使用实例。这里实现的所有功能对html/template 包来说都是一样的,然而,从htlm/template 产生的输出是安全的,不会被代码注入。
在网络环境中使用模板时,如果输出是HTML,请使用html/template 包。