template
เอกสารทางการ: template package - text/template - Go Packages
โดยปกติเรามักจะใช้ฟังก์ชัน fmt.Sprintf เพื่อจัดรูปแบบสตริง แต่ใช้ได้ดีเฉพาะกับสตริงขนาดเล็กเท่านั้น และต้องใช้ตัวแปรจัดรูปแบบเพื่อระบุประเภท ไม่สามารถทำพารามิเตอร์ที่มีชื่อ และไม่รองรับการจัดการสถานการณ์ที่ซับซ้อน นี่คือปัญหาที่เทมเพลตเอ็นจิ้นต้องแก้ไข เช่น ในหน้า HTML แบบคงที่ที่เชื่อมต่อกับแบ็กเอนด์โดยตรงจำเป็นต้องใช้เทมเพลตเอ็นจิ้น ในชุมชนมีไลบรารีเทมเพลตเอ็นจิ้นบุคคลที่สามที่ยอดเยี่ยมมากมาย เช่น pongo2, sprig, jet แต่บทความนี้จะอธิบายเทมเพลตเอ็นจิ้นในตัว text/template ของ Go โดยในการพัฒนาจริงมักใช้ html/template ซึ่งอิงจากเทมเพลตเอ็นจิ้นก่อนหน้าและทำการประมวลผลความปลอดภัยเกี่ยวกับ HTML มากมาย โดยทั่วไปใช้เทมเพลตเอ็นจิ้นก่อนหน้า แต่หากเกี่ยวข้องกับการประมวลผลเทมเพลต HTML แนะนำให้ใช้เทมเพลตเอ็นจิ้นหลังจะปลอดภัยกว่า
เริ่มต้นอย่างรวดเร็ว
ด้านล่างนี้เป็นตัวอย่างการใช้งานเทมเพลตเอ็นจิ้นอย่างง่าย ดังนี้
package main
import (
"fmt"
"os"
"text/template"
)
func main() {
tmpl := `This is the first template string, {{ .message }}`
te, err := template.New("texTmpl").Parse(tmpl)
if err != nil {
fmt.Println(err)
return
}
data := map[string]any{
"message": "hello world!",
}
execErr := te.Execute(os.Stdout, data)
if execErr != nil {
fmt.Println(err)
}
}เอาต์พุตของโค้ดข้างต้นคือ
This is the first template string, hello world!ในโค้ดกรณี tmpl เป็นสตริงเทมเพลต {{ .message }} ในสตริงเป็นพารามิเตอร์เทมเพลตของเทมเพลตเอ็นจิ้น ก่อนอื่นแยกวิเคราะห์สตริงเทมเพลตผ่านเมธอด *Template.Parse
func (t *Template) Parse(text string) (*Template, error)หลังจากแยกวิเคราะห์สำเร็จแล้ว ใช้เมธอด *Template.Execute เพื่อใช้ข้อมูล data กับเทมเพลต สุดท้ายส่งออกไปยัง Writer ที่ส่งเข้ามาซึ่งก็คือ os.Stdout
func (t *Template) Execute(wr io.Writer, data any) errorในการใช้เทมเพลตเอ็นจิ้นในอนาคต โดยพื้นฐานแล้วมีสามขั้นตอนนี้:
- รับเทมเพลต
- แยกวิเคราะห์เทมเพลต
- ใช้ข้อมูลกับเทมเพลต
จะเห็นว่าการใช้เทมเพลตเอ็นจิ้นนั้นง่ายมาก ส่วนที่ซับซ้อนกว่าเล็กน้อยคือไวยากรณ์เทมเพลตของเทมเพลตเอ็นจิ้น ซึ่งเป็นเนื้อหาหลักที่บทความนี้จะอธิบาย
ไวยากรณ์เทมเพลต
พารามิเตอร์
Go ใช้วงเล็บปีกกาสองคู่ {{ }} เพื่อแสดงว่าเป็นพารามิเตอร์เทมเพลตในเทมเพลต ใช้ . เพื่อแสดงอ็อบเจกต์รูท อ็อบเจกต์รูทคือ data ที่ส่งเข้ามา เหมือนกับการเข้าถึงตัวแปรสมาชิกของประเภท ผ่านสัญลักษณ์ . เชื่อมต่อชื่อตัวแปรเพื่อเข้าถึงค่าที่สอดคล้องกันในเทมเพลต เช่น
{{ .data }}ต้องมีตัวแปรสมาชิกที่มีชื่อเดียวกัน มิฉะนั้นจะเกิดข้อผิดพลาด สำหรับ data ที่ส่งเข้ามา โดยทั่วไปเป็นโครงสร้างหรือ map หรืออาจเป็นประเภทพื้นฐาน เช่น ตัวเลข สตริง ในกรณีนี้ . ที่แสดงอ็อบเจกต์รูทคือตัวมันเอง ในวงเล็บปีกกาไม่จำเป็นต้องเข้าถึงอ็อบเจกต์รูทเพื่อรับค่าเสมอไป อาจเป็นค่าคงที่ของประเภทพื้นฐาน เช่น
{{ 1 }}
{{ 3.14 }}
{{ "jack" }}ไม่ว่าจะเป็นประเภทใด สุดท้ายจะได้รูปแบบสตริงผ่าน fmt.Sprintf("%s", val) ดูตัวอย่างด้านล่าง
func main() {
out := os.Stdout
tmpl := "data-> {{ . }}\n"
datas := []any{
"hello world!",
6379,
3.1415926,
[]any{1, "2*2", 3.6},
map[string]any{"data": "hello world!"},
struct {
Data string
}{Data: "hello world!"},
}
for _, data := range datas {
err := ExecTmpl(out, tmpl, data)
if err != nil {
panic(err)
}
}
}
func ExecTmpl(writer io.Writer, tmpl string, data any) error {
parsedTmpl, err := template.New("template").Parse(tmpl)
if err != nil {
return err
}
return parsedTmpl.Execute(writer, data)
}เอาต์พุตดังนี้
data-> hello world!
data-> 6379
data-> 3.1415926
data-> [1 2*2 3.6]
data-> map[data:hello world!]
data-> {hello world!}จะเห็นว่าการเอาต์พุตเหมือนกับ直接使用 fmt.Sprintf สำหรับโครงสร้างและ map สามารถเข้าถึงค่าผ่านชื่อฟิลด์ ดังนี้
func main() {
out := os.Stdout
tmpl := "data-> {{ .Data }}\n"
datas := []any{
map[string]any{"Data": "hello world!"},
struct {
Data string
}{Data: "hello world!"},
}
for _, data := range datas {
err := ExecTmpl(out, tmpl, data)
if err != nil {
panic(err)
}
}
}เอาต์พุตดังนี้
data-> hello world!
data-> hello world!สำหรับสไลซ์และ map แม้ว่าจะไม่มีไวยากรณ์เฉพาะสำหรับเข้าถึงค่าดัชนีที่ระบุ แต่สามารถทำได้ผ่านการเรียกฟังก์ชัน ดังนี้
func main() {
out := os.Stdout
tmpl := "data-> {{ index . 1}}\n"
datas := []any{
[]any{"first", "second"},
map[int]any{1: "first"},
}
for _, data := range datas {
err := ExecTmpl(out, tmpl, data)
if err != nil {
panic(err)
}
}
}เอาต์พุต
data-> second
data-> firstหากเป็นสไลซ์หลายมิติ สามารถเข้าถึงค่าดัชนีที่สอดคล้องกันผ่านวิธีต่อไปนี้ เทียบเท่ากับ s[i][j][k]
{{ index . i j k }}สำหรับโครงสร้างหรือ map ที่ซ้อนกัน สามารถใช้ .k1.k2.k3 เพื่อเข้าถึง เช่น
{{ .person.father.name }}เมื่อใช้พารามิเตอร์เทมเพลต สามารถเพิ่มสัญลักษณ์ - หน้าและหลังพารามิเตอร์เพื่อลบช่องว่างหน้าและหลังพารามิเตอร์ ดูตัวอย่าง
func main() {
out := os.Stdout
tmpl := `{{ .x }} {{ - .op - }} {{ .y }}`
datas := []any{
map[string]any{"x": "10", "op": ">", "y": "2"},
}
for _, data := range datas {
err := ExecTmpl(out, tmpl, data)
if err != nil {
panic(err)
}
}
}โดยปกติแล้วเอาต์พุตควรเป็น 10 > 2 แต่เนื่องจากเพิ่มสัญลักษณ์ - หน้าและหลังพารามิเตอร์ op ดังนั้นช่องว่างหน้าและหลังจะถูกกำจัด ดังนั้นเอาต์พุตจริงคือ
10>2ควรสังเกตว่าในวงเล็บปีกกา สัญลักษณ์ - ต้องห่างจากพารามิเตอร์หนึ่งช่องว่าง กล่าวคือต้องเป็นรูปแบบ {{- . -}} ในตัวอย่าง之所以เพิ่มช่องว่างพิเศษทั้งสองข้างเขียนเป็นรูปแบบ {{ - . - }} ล้วนเป็นเพราะรู้สึกว่าดูสบายตาเท่านั้น ที่จริงแล้วไม่มีข้อจำกัดทางไวยากรณ์นี้
ความคิดเห็น
ไวยากรณ์เทมเพลตรองรับความคิดเห็น ความคิดเห็นจะไม่สร้างในเทมเพลตสุดท้าย ไวยากรณ์มีดังนี้
{{/* this is a comment */}}สัญลักษณ์ความคิดเห็น /* และ */ ต้องอยู่ติดกับวงเล็บปีกกา ระหว่างพวกมันต้องไม่มีอักขระอื่น มิฉะนั้นจะไม่สามารถแยกวิเคราะห์ได้ มีเพียงกรณีเดียวที่ข้อยกเว้น นั่นคือเมื่อกำจัดช่องว่าง
{{- /* this is a comment */ -}}ตัวแปร
ในเทมเพลตสามารถประกาศตัวแปรได้ ใช้สัญลักษณ์ $ เพื่อแสดงว่าเป็นตัวแปร และใช้ := เพื่อกำหนดค่า เหมือนกับโค้ด Go ตัวอย่างดังนี้
{{ $name := .Name }}
{{ $val := index . 1 }}
{{ $val := index .dict key }}
// กำหนดค่าจำนวนเต็ม
{{ $numer := 1 }}
// กำหนดค่าจุดลอยตัว
{{ $float := 1.234}}
// กำหนดค่าสตริง
{{ $name := "jack" }}ในภายหลังใช้ $ เชื่อมต่อชื่อตัวแปรเพื่อเข้าถึงค่าของตัวแปรนั้น เช่น
func main() {
out := os.Stdout
tmpl := `{{ $name := .name }} {{- $name }}`
datas := []any{
map[string]any{"name": "jack"},
}
for _, data := range datas {
err := ExecTmpl(out, tmpl, data)
if err != nil {
panic(err)
}
}
}เอาต์พุต
jackตัวแปรต้องประกาศก่อนใช้ มิฉะนั้นจะแสดงข้อผิดพลาด undefined variable และต้องใช้ภายในขอบเขตด้วย
ฟังก์ชัน
ไวยากรณ์ของเทมเพลตเองไม่มาก ส่วนใหญ่ฟังก์ชันใช้เพื่อใช้งานฟังก์ชัน รูปแบบการเรียกฟังก์ชันคือชื่อฟังก์ชันตามด้วยรายการพารามิเตอร์ ใช้ช่องว่างเป็นตัวคั่น ดังนี้
{{ funcname arg1 arg2 arg3 ... }}เช่น ฟังก์ชัน index ที่ใช้ก่อนหน้านี้
{{ index .s 1 }}ฟังก์ชัน eq ที่ใช้เปรียบเทียบว่าเท่ากันหรือไม่
{{ eq 1 2 }}แต่ละ *Template มี FuncsMap สำหรับบันทึกการแมปฟังก์ชัน
type FuncMap map[string]anyเมื่อสร้างเทมเพลต รับการแมปฟังก์ชันเริ่มต้นจาก text/template.builtins ด้านล่างนี้เป็นฟังก์ชันในตัวทั้งหมด
| ชื่อฟังก์ชัน | หน้าที่ | ตัวอย่าง |
|---|---|---|
and | การดำเนินการ AND | {{ and true false }} |
or | การดำเนินการ OR | {{ or true false }} |
not | การดำเนินการ NOT | {{ not true }} |
eq | เท่ากันหรือไม่ | {{ eq 1 2 }} |
ne | ไม่เท่ากันหรือไม่ | {{ ne 1 2 }} |
lt | น้อยกว่า | {{ lt 1 2 }} |
le | น้อยกว่าหรือเท่ากับ | {{ le 1 2 }} |
gt | มากกว่า | {{ gt 1 2 }} |
ge | มากกว่าหรือเท่ากับ | {{ ge 1 2 }} |
len | ส่งคืนความยาว | {{ len .slice }} |
index | รับองค์ประกอบดัชนีที่ระบุ | {{ index . 0 }} |
slice | สไลซ์ เทียบเท่ากับ s[1:2:3] | {{ slice . 1 2 3 }} |
html | หลีกหนี HTML | {{ html .name }} |
js | หลีกหนี js | {{ js .name }} |
print | fmt.Sprint | {{ print . }} |
printf | fmt.Sprintf | {{ printf "%s" .}} |
println | fmt.Sprintln | {{ println . }} |
urlquery | หลีกหนี url query | {{ urlquery .query }} |
นอกจากนี้ยังมีฟังก์ชันในตัวพิเศษ call ใช้สำหรับเรียกฟังก์ชันที่ส่งผ่านใน data ที่ส่งเข้ามาในช่วง Execute โดยตรง เช่น เทมเพลตต่อไปนี้
{{ call .string 1024 }}ข้อมูลที่ส่งเข้ามาดังนี้
map[string]any{
"string": func(val any) string { return fmt.Sprintf("%v: 2048", val) },
}那么在模板中就会生成
1024: 2048นี่คือหนึ่งในวิธีกำหนดฟังก์ชันเอง แต่โดยปกติแนะนำให้ใช้เมธอด *Template.Funcs เพื่อเพิ่มฟังก์ชันเอง เพราะหลังสามารถใช้งานทั่วโลกได้ ไม่จำเป็นต้องผูกกับอ็อบเจกต์รูท
func (t *Template) Funcs(funcMap FuncMap) *Templateค่าส่งกลับของฟังก์ชันที่กำหนดเองโดยทั่วไปมีสองค่า ค่าแรกคือค่าส่งกลับที่ต้องการใช้ และค่าที่สองคือ error เช่น มีฟังก์ชันที่กำหนดเองดังนี้
template.FuncMap{
"add": func(val any) (string, error) { return fmt.Sprintf("%v+1", val), nil },
}แล้วใช้ในเทมเพลตโดยตรง
{{ add 1024 }}ผลลัพธ์คือ
1024 + 1ไปป์ไลน์
ไปป์ไลน์นี้แตกต่างจาก chan ในเอกสารทางการเรียกว่า pipeline การดำเนินการใดๆ ที่สร้างข้อมูลเรียกว่า pipeline การดำเนินการเทมเพลตต่อไปนี้เป็นของไปป์ไลน์
{{ 1 }}
{{ eq 1 2 }}
{{ $name }}
{{ .name }}ผู้ที่คุ้นเคยกับ linux ควรทราบตัวดำเนินการไปป์ไลน์ | เทมเพลตก็รองรับการเขียนเช่นนี้ การดำเนินการไปป์ไลน์มักปรากฏในเทมเพลต เช่น
{{ $name := 1 }}{{ $name | print | printf "%s+1=?" }}ผลลัพธ์คือ
1+1=?ใน with, if, range ต่อไปก็จะใช้บ่อย
with
ผ่านคำสั่ง with สามารถควบคุมขอบเขตของตัวแปรและอ็อบเจกต์รูท รูปแบบมีดังนี้
{{ with pipeline }}
text
{{ end }}with จะตรวจสอบค่าที่ส่งกลับจากการดำเนินการไปป์ไลน์ หากค่าเป็นว่าง เทมเพลต text ตรงกลางจะไม่สร้าง หากต้องการจัดการกรณีว่าง สามารถใช้ with else รูปแบบมีดังนี้
{{ with pipeline }}
text1
{{ else }}
text2
{{ end }}หากค่าที่ส่งกลับจากการดำเนินการไปป์ไลน์เป็นว่าง ก็จะดำเนินการตรรกะของบล็อก else นี้ ตัวแปรที่ประกาศในคำสั่ง with ขอบเขตจำกัดอยู่ภายในคำสั่ง with เท่านั้น ดูตัวอย่างหนึ่ง
{{ $name := "mike" }}
{{ with $name := "jack" }}
{{- $name -}}
{{ end }}
{{- $name -}}เอาต์พุตมีดังนี้ เห็นได้ชัดว่าเนื่องจากขอบเขตต่างกัน พวกมันเป็นตัวแปรคนละตัว
jackmikeผ่านคำสั่ง with ยังสามารถเขียนอ็อบเจกต์รูทใหม่ภายในขอบเขตได้ ดังนี้
{{ with .name }}
name: {{- .second }}-{{ .first -}}
{{ end }}
age: {{ .age }}
address: {{ .address }}ส่งข้อมูลดังนี้
map[string]any{
"name": map[string]any{
"first": "jack",
"second": "bob",
},
"age": 1,
"address": "usa",
}เอาต์พุต
name:bob-jack
age: 1
address: usaจะเห็นได้ว่าภายในคำสั่ง with อ็อบเจกต์รูท . กลายเป็น .name แล้ว
เงื่อนไข
รูปแบบคำสั่งเงื่อนไขมีดังนี้
{{ if pipeline }}
text1
{{ else if pipeline }}
text2
{{ else }}
text3
{{ end }}เหมือนกับการเขียนโค้ดทั่วไป เข้าใจง่ายมาก ดูตัวอย่างง่ายๆ สองสามตัวอย่าง
{{ if eq .lang "en" }}
{{- .content.en -}}
{{ else if eq .lang "zh" }}
{{- .content.zh -}}
{{ else }}
{{- .content.fallback -}}
{{ end }}ข้อมูลที่ส่งเข้ามา
map[string]any{
"lang": "zh",
"content": map[string]any{
"en": "hello, world!",
"zh": "你好,世界!",
"fallback": "hello, world!",
},
}เทมเพลตในตัวอย่างตัดสินใจว่าจะแสดงเนื้อหาอย่างไรตาม lang ที่ส่งเข้ามา เอาต์พุต
你好,世界!การทำซ้ำ
รูปแบบคำสั่งการทำซ้ำมีดังนี้ range รองรับ pipeline ต้องเป็นอาร์เรย์ สไลซ์ map และ channel
{{ range pipeline }}
loop body
{{ end }}ใช้ร่วมกับ else เมื่อความยาวเป็น 0 ก็จะดำเนินการเนื้อหาของบล็อก else
{{ range pipeline }}
loop body
{{ else }}
fallback
{{ end }}นอกจากนี้ยังรองรับการดำเนินการเช่น break, continue เช่น
{{ range pipeline }}
{{ if pipeline }}
{{ break }}
{{ end }}
{{ if pipeline }}
{{ continue }}
{{ end }}
loop body
{{ end }}ดูตัวอย่างการทำซ้ำหนึ่งตัวอย่าง
{{ range $index, $val := . }}
{{- if eq $index 0 }}
{{- continue -}}
{{ end -}}
{{- $index}}: {{ $val }}
{{ end }}ส่งข้อมูล
[]any{1, "2", 3.14},เอาต์พุต
1: 2
2: 3.14การทำซ้ำ map ก็เช่นเดียวกัน
การซ้อนกัน
ในเทมเพลตหนึ่งสามารถกำหนดเทมเพลตได้หลายเทมเพลต เช่น
{{ define "t1" }} t1 {{ end }}
{{ define "t2" }} t2 {{ end }}เทมเพลตที่กำหนดเหล่านี้จะไม่สร้างในเทมเพลตสุดท้าย เว้นแต่จะระบุชื่อในช่วงโหลดหรือระบุด้วยตนเองผ่านคำสั่ง template
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) errorเช่น ตัวอย่างด้านล่าง
{{ define "t1" }}
{{- with .t1 }}
{{- .data -}}
{{ end -}}
{{ end }}
{{ define "t2" }}
{{- with .t2 }}
{{- .data -}}
{{ end}}
{{ end -}}ส่งข้อมูลดังนี้
map[string]any{
"t1": map[string]any{"data": "template body 1"},
"t2": map[string]any{"data": "template body 2"},
}โค้ด
func main() {
out := os.Stdout
tmpl :=
`{{ define "t1" }}
{{- with .t1 }}
{{- .data -}}
{{ end -}}
{{ end }}
{{ define "t2" }}
{{- with .t2 }}
{{- .data -}}
{{ end}}
{{ end -}}`
datas := []any{
map[string]any{
"t1": map[string]any{"data": "template body 1"},
"t2": map[string]any{"data": "template body 2"},
},
}
name := "t1"
for _, data := range datas {
err := ExecTmpl(out, tmpl, name, data)
if err != nil {
panic(err)
}
}
}
func ExecTmpl(writer io.Writer, tmpl string, name string, data any) error {
t := template.New("template")
parsedTmpl, err := t.Parse(tmpl)
if err != nil {
return err
}
return parsedTmpl.ExecuteTemplate(writer, name, data)
}เอาต์พุต
template body 1หรือสามารถระบุเทมเพลตด้วยตนเอง
{{ define "t1" }}
{{- with .t1 }}
{{- .data -}}
{{ end -}}
{{ end }}
{{ define "t2" }}
{{- with .t2 }}
{{- .data -}}
{{ end}}
{{ end -}}
{{ template "t2" .}}那么ในการแยกวิเคราะห์ไม่ว่าจะระบุชื่อเทมเพลตหรือไม่ t2 จะโหลด
การเชื่อมโยง
เทมเพลตย่อยเป็นเพียงการประกาศเทมเพลตที่มีชื่อหลายเทมเพลตภายในเทมเพลตเดียว การเชื่อมโยงคือการเชื่อมโยง *Template ที่มีชื่อหลายเทมเพลตจากภายนอก แล้วอ้างอิงเทมเพลตที่ระบุผ่านคำสั่ง template
{{ tempalte "templateName" pipeline}}pipeline สามารถระบุอ็อบเจกต์รูทของเทมเพลตที่เชื่อมโยงตามความต้องการของตนเอง หรือส่งอ็อบเจกต์รูทของเทมเพลตปัจจุบันโดยตรง ดูตัวอย่างโค้ดหนึ่งตัวอย่าง
func main() {
tmpl1 := `name: {{ .name }}`
tmpl2 := `age: {{ .age }}`
tmpl3 := `Person Info
{{template "t1" .}}
{{template "t2" .}}`
t1, err := template.New("t1").Parse(tmpl1)
if err != nil {
panic(err)
}
t2, err := template.New("t2").Parse(tmpl2)
if err != nil {
panic(err)
}
t3, err := template.New("t3").Parse(tmpl3)
if err != nil {
panic(err)
}
if err := associate(t3, t1, t2); err != nil {
panic(err)
}
err = t3.Execute(os.Stdout, map[string]any{
"name": "jack",
"age": 18,
})
if err != nil {
panic(err)
}
}
func associate(t *template.Template, ts ...*template.Template) error {
for _, tt := range ts {
_, err := t.AddParseTree(tt.Name(), tt.Tree)
if err != nil {
return err
}
}
return nil
}ในโค้ดข้างต้น t3 เชื่อมโยง t1 และ t2 ใช้เมธอด *Template.AddParseTree เพื่อเชื่อมโยง
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)ผลลัพธ์การสร้างเทมเพลตสุดท้ายคือ
Person Info
name: jack
age: 18สล็อต
ผ่านคำสั่ง block สามารถสร้างเอฟเฟกต์คล้ายสล็อตของ vue ได้ เพื่อใช้เทมเพลตหนึ่งซ้ำ ดูตัวอย่างการใช้งานหนึ่งตัวอย่างก็จะรู้ว่าใช้อย่างไร ในเทมเพลต t1 กำหนดสล็อต
Basic Person Info
name: {{ .name }}
age: {{ .age }}
address: {{ .address }}
{{ block "slot" . }} default content body {{ end }}คำสั่ง block สามารถเป็นเนื้อหาเริ่มต้นของสล็อต ในภายหลังเมื่อเทมเพลตอื่นใช้สล็อต จะแทนที่เนื้อหาเริ่มต้น ในเทมเพลต t2 อ้างอิงเทมเพลต t1 และใช้ define กำหนดเนื้อหาที่ฝัง
{{ template "person.txt" . }}
{{ define "slot" }}
school: {{ .school }}
{{ end }}หลังจากเชื่อมโยงสองเทมเพลต ส่งข้อมูลดังนี้
map[string]any{
"name": "jack",
"age": 18,
"address": "usa",
"company": "google",
"school": "mit",
}เอาต์พุตสุดท้ายคือ
Basic Person Info
name: jack
age: 18
address: usa
school: mitไฟล์เทมเพลต
ในกรณีไวยากรณ์เทมเพลต ใช้ค่าคงที่สตริงเป็นเทมเพลต ในสถานการณ์การใช้งานจริง ส่วนใหญ่มักวางเทมเพลตในไฟล์
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)เช่น template.ParseFs โหลดเทมเพลตที่ตรงกับ pattern จากระบบไฟล์ที่ระบุ ตัวอย่างด้านล่างใช้ embed.FS เป็นระบบไฟล์ เตรียมสามไฟล์
# person.txt
Basic Person Info
name: {{ .name }}
age: {{ .age }}
address: {{ .address }}
{{ block "slot" . }} {{ end }}
# student.txt
{{ template "person.txt" . }}
{{ define "slot" }}
school: {{ .school }}
{{ end }}
# employee.txt
{{ template "person.txt" . }}
{{ define "slot" }}
company: {{ .company }}
{{ end }}โค้ด
import (
"embed"
"os"
"text/template"
)
//go:embed *.txt
var fs embed.FS
func main() {
data := map[string]any{
"name": "jack",
"age": 18,
"address": "usa",
"company": "google",
"school": "mit",
}
t1, err := template.ParseFS(fs, "person.txt", "student.txt")
if err != nil {
panic(err)
}
t1.Execute(os.Stdout, data)
t2, err := template.ParseFS(fs, "person.txt", "employee.txt")
if err != nil {
panic(err)
}
t2.Execute(os.Stdout, data)
}เอาต์พุตคือ
Basic Person Info
name: jack
age: 18
address: usa
school: mit
Basic Person Info
name: jack
age: 18
address: usa
company: googleนี่เป็นตัวอย่างการใช้งานไฟล์เทมเพลตอย่างง่าย person.txt เป็นไฟล์สล็อต อีกสองไฟล์ใช้เนื้อหาซ้ำและฝังเนื้อหาใหม่ที่กำหนดเอง นอกจากนี้ยังสามารถใช้ฟังก์ชันสองฟังก์ชันต่อไปนี้
func ParseGlob(pattern string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)ParseGlob ใช้การจับคู่ไวด์การ์ด ParseFiles ใช้ชื่อไฟล์ ทั้งสองใช้ระบบไฟล์ท้องถิ่น หากใช้สำหรับแสดงไฟล์ html บนฟรอนต์เอนด์ แนะนำให้ใช้แพ็กเกจ html/template ซึ่งให้ API เหมือนกับ text/template แต่ทำการประมวลผลความปลอดภัยสำหรับ html, css, js
