template
Resmi dokümantasyon: template package - text/template - Go Packages
Günlük olarak sıkça fmt.Sprintf fonksiyonunu string biçimlendirme için kullanırız, ancak bu sadece küçük string'leri işlemek için uygundur ve tipi belirtmek için biçimlendirme belirteçleri kullanmak gerekir, parametre adlandırmayı desteklemez, karmaşık durumları işleyemez ve bu da şablon motorunun çözmesi gereken sorundur, örneğin doğrudan backend'e bağlanan statik HTML sayfalarında şablon motoru kullanılması gerekir. Toplulukta birçok mükemmel üçüncü taraf şablon motoru kütüphanesi vardır, örneğin pongo2, sprig, jet, ancak bu makalede anlatılacak ana karakter Go'nun yerleşik şablon motoru kütüphanesi text/template'dir, pratik geliştirmede genellikle html/template kullanılır, ikincisi birincisine dayanır ve HTML ile ilgili birçok güvenlik işleme yapar, genel durumda birincisi kullanılabilir, ancak HTML şablon işleme söz konusu olduğunda ikincisinin daha güvenli olması önerilir.
Hızlı Başlangıç
Aşağıda şablon motorunun basit bir kullanım örneği gösterilmiştir
package main
import (
"fmt"
"os"
"text/template"
)
func main() {
tmpl := `Bu ilk şablon string'i, {{ .message }}`
te, err := template.New("texTmpl").Parse(tmpl)
if err != nil {
fmt.Println(err)
return
}
data := map[string]any{
"message": "merhaba dünya!",
}
execErr := te.Execute(os.Stdout, data)
if execErr != nil {
fmt.Println(err)
}
}Yukarıdaki kodun çıktısı
Bu ilk şablon string'i, merhaba dünya!Örnek kodda, tmpl bir şablon string'idir, string içindeki {{ .message }} şablon motorunun şablon parametresidir. İlk olarak *Template.Parse metodu ile şablon string'i ayrıştırılır
func (t *Template) Parse(text string) (*Template, error)Ayrıştırma başarılı olduktan sonra *Template.Execute metodu ile data verisini şablona uygular ve son olarak传入 edilen Writer'a yani os.Stdout'a çıktı verir.
func (t *Template) Execute(wr io.Writer, data any) errorŞablon motoru kullanımında, temelde bu üç adım vardır:
- Şablonu al
- Şablonu ayrıştır
- Veriyi şablona uygula
Şablon motoru kullanımının oldukça basit olduğu görülmektedir, biraz daha karmaşık olan şablon motorunun şablon sözdizimidir, bu da bu makalenin ana içeriğidir.
Şablon Sözdizimi
Parametreler
Go, şablonda şablon parametresi olduğunu belirtmek için iki süslü parantez {{ }} kullanır, . ile kök nesneyi belirtir, kök nesne传入 edilen data'dır. Bir tipin üye değişkenine erişmek gibi, . sembolü ile değişken adını birleştirerek şablonda ilgili değere erişebilirsiniz, örneğin
{{ .data }}Ön koşul aynı ada sahip üye değişkenin var olmasıdır, aksi takdirde hata verir.传入 edilen data için, genellikle struct veya map'tir, temel tip de olabilir, örneğin sayı string, bu durumda . temsil ettiği kök nesne kendisidir. Süslü parantez içinde, değeri almak için kök nesneye erişmek zorunda değilsiniz, temel tip literal de olabilir, örneğin
{{ 1 }}
{{ 3.14 }}
{{ "jack" }}Herhangi bir tip için, sonunda fmt.Sprintf("%s", val) ile string gösterimi alınır, aşağıdaki örneğe bakın.
func main() {
out := os.Stdout
tmpl := `data-> {{ . }}
`
datas := []any{
"merhaba dünya!",
6379,
3.1415926,
[]any{1, "2*2", 3.6},
map[string]any{"data": "merhaba dünya!"},
struct {
Data string
}{Data: "merhaba dünya!"},
}
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)
}Çıktı aşağıdaki gibidir
data-> merhaba dünya!
data-> 6379
data-> 3.1415926
data-> [1 2*2 3.6]
data-> map[data:merhaba dünya!]
data-> {merhaba dünya!}Çıktının doğrudan fmt.Sprintf kullanımı ile aynı olduğu görülmektedir. Struct ve map için, alan adı ile değerine erişebilirsiniz, aşağıda gösterildiği gibi
func main() {
out := os.Stdout
tmpl := `data-> {{ .Data }}
`
datas := []any{
map[string]any{"Data": "merhaba dünya!"},
struct {
Data string
}{Data: "merhaba dünya!"},
}
for _, data := range datas {
err := ExecTmpl(out, tmpl, data)
if err != nil {
panic(err)
}
}
}Çıktı aşağıdaki gibidir
data-> merhaba dünya!
data-> merhaba dünya!Slice ve map için, belirli bir indekse erişmek için özel sözdizimi sağlanmasa da, fonksiyon çağrısı ile实现 edilebilir, aşağıda gösterildiği gibi
func main() {
out := os.Stdout
tmpl := `data-> {{ index . 1}}
`
datas := []any{
[]any{"first", "second"},
map[int]any{1: "first"},
}
for _, data := range datas {
err := ExecTmpl(out, tmpl, data)
if err != nil {
panic(err)
}
}
}Çıktı
data-> second
data-> firstÇok boyutlu slice ise, aşağıdaki şekilde ilgili indeks değerine erişebilirsiniz, s[i][j][k] ile eşdeğerdir
{{ index . i j k }}İç içe struct veya map için, .k1.k2.k3 şeklinde erişebilirsiniz, örneğin
{{ .person.father.name }}Şablon parametresi kullanırken, parametre önüne ve arkasına - sembolü ekleyerek parametrenin önündeki ve arkasındaki boşlukları kaldırabilirsiniz, bir örneğe bakalım
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)
}
}
}Normalde çıktı 10 > 2 olmalıdır, ancak op parametresinin önüne ve arkasına - sembolü eklendiğinden, önündeki ve arkasındaki boşluklar kaldırılır, bu nedenle实际 çıktı
10>2Dikkat edilmesi gereken, süslü parantez içinde - sembolü ile parametre arasında bir boşluk olması gerektiğidir, yani {{- . -}} formatında olmalıdır, örnekte iki tarafta ekstra boşluk eklenmesinin nedeni {{ - . - }} formatında yazılması tamamen kişisel tercihdir,实际上 bu sözdizimi kısıtlaması yoktur.
Yorumlar
Şablon sözdizimi yorumları destekler, yorumlar nihai şablonda oluşturulmaz, sözdizimi aşağıdaki gibidir
{{/* bu bir yorumdur */}}Yorum sembolleri /* ve */ süslü parantezlere bitişik olmalıdır, aralarında başka karakter olamaz, aksi takdirde doğru ayrıştırılamaz. Sadece bir durum istisnadır, boşlukları kaldırırken
{{- /* bu bir yorumdur */ -}}Değişkenler
Şablonda değişken de tanımlanabilir, $ sembolü ile bunun bir değişken olduğunu belirtir ve := ile atama yapılır, tıpkı Go kodu gibi, örnek aşağıdadır.
{{ $name := .Name }}
{{ $val := index . 1 }}
{{ $val := index .dict key }}
// Tamsayı ataması
{{ $numer := 1 }}
// Kayan nokta ataması
{{ $float := 1.234}}
// String ataması
{{ $name := "jack" }}Sonraki kullanımda, değişkenin değerine erişmek için $ ile değişken adını birleştirirsiniz, örneğin
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)
}
}
}Çıktı
jackDeğişken kullanılmadan önce tanımlanmalıdır, aksi takdirde undefined variable hatası verir ve ayrıca kapsam içinde kullanılmalıdır.
Fonksiyonlar
Şablonun kendi sözdizimi aslında çok azdır, çoğu işlev fonksiyonlarla实现 edilir, fonksiyon çağrısı formatı fonksiyon adı sonrası parametre listesidir, boşluk ayraç olarak kullanılır, aşağıda gösterildiği gibi
{{ funcname arg1 arg2 arg3 ... }}Örneğin daha önce kullanılan index fonksiyonu
{{ index .s 1 }}Eşit olup olmadığını karşılaştırmak için kullanılan eq fonksiyonu
{{ eq 1 2 }}Her *Template'in fonksiyon eşlemelerini kaydetmek için bir FuncsMap'i vardır
type FuncMap map[string]anyŞablon oluşturulurken text/template.builtins'tan varsayılan fonksiyon eşleme tablosu alınır, aşağıda tüm yerleşik fonksiyonlar listelenmiştir
| Fonksiyon Adı | İşlevi | Örnek |
|---|---|---|
and | VE işlemi | {{ and true false }} |
or | VEYA işlemi | {{ or true false }} |
not | Değilleme işlemi | {{ not true }} |
eq | Eşit mi | {{ eq 1 2 }} |
ne | Eşit değil mi | {{ ne 1 2 }} |
lt | Küçük mü | {{ lt 1 2 }} |
le | Küçük eşit mi | {{ le 1 2 }} |
gt | Büyük mü | {{ gt 1 2 }} |
ge | Büyük eşit mi | {{ ge 1 2 }} |
len | Uzunluğu döndürür | {{ len .slice }} |
index | Hedefin belirli indeksindeki elementi alır | {{ index . 0 }} |
slice | Slice, s[1:2:3] ile eşdeğer | {{ slice . 1 2 3 }} |
html | HTML escape | {{ html .name }} |
js | js escape | {{ js .name }} |
print | fmt.Sprint | {{ print . }} |
printf | fmt.Sprintf | {{ printf "%s" .}} |
println | fmt.Sprintln | {{ println . }} |
urlquery | url query escape | {{ urlquery .query }} |
Bunların dışında, oldukça özel bir yerleşik fonksiyon call vardır, doğrudan Execute sırasında传入 edilen data'daki fonksiyonu çağırmak için kullanılır, örneğin aşağıdaki şablon
{{ call .string 1024 }}传入 edilen veri aşağıdaki gibidir
map[string]any{
"string": func(val any) string { return fmt.Sprintf("%v: 2048", val) },
}O zaman şablonda oluşturulur
1024: 2048Bu özel fonksiyonların yollarından biridir, ancak genellikle özel fonksiyon eklemek için *Template.Funcs metodu kullanılması önerilir, çünkü ikincisi global olarak çalışır, kök nesneye bağlanmaya gerek yoktur.
func (t *Template) Funcs(funcMap FuncMap) *TemplateÖzel fonksiyonun dönüş değeri genellikle iki tanedir, birincisi kullanılan dönüş değeri, ikincisi error'dur. Örneğin aşağıdaki özel fonksiyon vardır
template.FuncMap{
"add": func(val any) (string, error) { return fmt.Sprintf("%v+1", val), nil },
}Sonra şablonda doğrudan kullanın
{{ add 1024 }}Sonucu
1024 + 1Pipeline
Bu pipeline chan ile aynı şey değildir, resmi dokümantasyonda pipeline olarak adlandırılır, veri üreten herhangi bir işlem pipeline olarak adlandırılır. Aşağıdaki şablon işlemlerinin tümü pipeline işlemidir
{{ 1 }}
{{ eq 1 2 }}
{{ $name }}
{{ .name }}Linux'a aşina olanlar pipeline operatörü |'ü bilmelidir, şablonda da bu şekilde yazım desteklenir. Pipeline işlemi şablonda sıkça görülür, örneğin
{{ $name := 1 }}{{ $name | print | printf "%s+1=?" }}Sonucu
1+1=?Sonraki with, if, range'de de sıkça kullanılır.
with
with deyimi ile değişken ve kök nesnenin kapsamı kontrol edilebilir, format aşağıdaki gibidir
{{ with pipeline }}
text
{{ end }}with, pipeline işleminin döndürdüğü değeri kontrol eder, eğer değer boşsa, ortadaki text şablonu oluşturulmaz. Boş durumu işlemek istiyorsanız with else kullanabilirsiniz, format aşağıdaki gibidir
{{ with pipeline }}
text1
{{ else }}
text2
{{ end }}Eğer pipeline işlemi boş değer döndürürse, o zaman else bloğundaki mantık yürütülür. with deyimi içinde tanımlanan değişkenlerin kapsamı sadece with deyimi içindedir, aşağıdaki örneğe bakalım
{{ $name := "mike" }}
{{ with $name := "jack" }}
{{- $name -}}
{{ end }}
{{- $name -}}Çıktısı aşağıdaki gibidir, açıkça farklı kapsamlar nedeniyle iki farklı değişkendir.
jackmikewith deyimi ile kapsam içinde kök nesne de değiştirilebilir, aşağıdaki gibi
{{ with .name }}
name: {{- .second }}-{{ .first -}}
{{ end }}
age: {{ .age }}
address: {{ .address }}Aşağıdaki veri传入 edilir
map[string]any{
"name": map[string]any{
"first": "jack",
"second": "bob",
},
"age": 1,
"address": "usa",
}Çıktısı
name:bob-jack
age: 1
address: usawith deyimi içinde kök nesne . zaten .name haline gelmiştir.
Koşul
Koşul deyiminin formatı aşağıdaki gibidir
{{ if pipeline }}
text1
{{ else if pipeline }}
text2
{{ else }}
text3
{{ end }}Tıpkı normal kod yazmak gibi, çok kolay anlaşılır. Aşağıda birkaç basit örneğe bakalım
{{ if eq .lang "en" }}
{{- .content.en -}}
{{ else if eq .lang "zh" }}
{{- .content.zh -}}
{{ else }}
{{- .content.fallback -}}
{{ end }}传入 edilen veri
map[string]any{
"lang": "zh",
"content": map[string]any{
"en": "hello, world!",
"zh": "Merhaba, dünya!",
"fallback": "hello, world!",
},
}Örnekteki şablon,传入 edilen dil lang'e göre içeriği hangi şekilde göstereceğine karar verir, çıktı sonucu
Merhaba, dünya!İterasyon
İterasyon deyiminin formatı aşağıdaki gibidir, range'in desteklediği pipeline dizi, slice, map ve channel olmalıdır.
{{ range pipeline }}
loop body
{{ end }}else ile birlikte kullanıldığında, uzunluk 0 olduğunda else bloğunun içeriği yürütülür.
{{ range pipeline }}
loop body
{{ else }}
fallback
{{ end }}Bunun dışında, break, continue gibi işlemleri de destekler, örneğin
{{ range pipeline }}
{{ if pipeline }}
{{ break }}
{{ end }}
{{ if pipeline }}
{{ continue }}
{{ end }}
loop body
{{ end }}Aşağıda bir iterasyon örneğine bakalım.
{{ range $index, $val := . }}
{{- if eq $index 0 }}
{{- continue -}}
{{ end -}}
{{- $index}}: {{ $val }}
{{ end }}传入 edilen veri
[]any{1, "2", 3.14},Çıktı
1: 2
2: 3.14map üzerinde iterasyon da aynı mantıktadır.
İç İçe
Bir şablonda birden fazla şablon tanımlanabilir, örneğin
{{ define "t1" }} t1 {{ end }}
{{ define "t2" }} t2 {{ end }}Bu tanımlanan şablonlar nihai şablonda oluşturulmaz, yüklenirken ad belirtilmedikçe veya template deyimi ile manuel olarak belirtilmedikçe.
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) errorAşağıdaki örnekte olduğu gibi
{{ define "t1" }}
{{- with .t1 }}
{{- .data -}}
{{ end -}}
{{ end }}
{{ define "t2" }}
{{- with .t2 }}
{{- .data -}}
{{ end}}
{{ end -}}Aşağıdaki veri传入 edilir
map[string]any{
"t1": map[string]any{"data": "şablon gövdesi 1"},
"t2": map[string]any{"data": "şablon gövdesi 2"},
}Kod
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": "şablon gövdesi 1"},
"t2": map[string]any{"data": "şablon gövdesi 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)
}Çıktı
şablon gövdesi 1Veya şablonu manuel olarak da belirtebilirsiniz
{{ define "t1" }}
{{- with .t1 }}
{{- .data -}}
{{ end -}}
{{ end }}
{{ define "t2" }}
{{- with .t2 }}
{{- .data -}}
{{ end}}
{{ end -}}
{{ template "t2" .}}O zaman ayrıştırma sırasında şablon adı belirtilse de belirtilmese de t2 yüklenecektir.
İlişkilendirme
Alt şablonlar sadece bir şablon içinde birden fazla adlandırılmış şablon tanımlamaktır, ilişkilendirme dışarıdaki birden fazla adlandırılmış *Template'i bir araya getirmektir. Sonra template deyimi ile belirtilen şablon referans alınır.
{{ template "templateName" pipeline}}pipeline, kendi ihtiyacınıza göre ilişkilendirilen şablonun kök nesnesini belirtebilir veya doğrudan mevcut şablonun kök nesnesini传入 edebilir. Aşağıdaki kod örneğine bakalım
func main() {
tmpl1 := `isim: {{ .name }}`
tmpl2 := `yaş: {{ .age }}`
tmpl3 := `Kişi Bilgisi
{{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
}Yukarıdaki kodda, t3 t1 ve t2 ile ilişkilendirilmiştir, *Template.AddParseTree metodu kullanılarak ilişkilendirilir
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)Nihai şablon oluşturma sonucu
Kişi Bilgisi
isim: jack
yaş: 18Slot
block deyimi ile vue slot'a benzer bir efekt实现 edilebilir, amacı belirli bir şablonu yeniden kullanmaktır. Bir kullanım örneğine bakarak nasıl kullanılacağını anlayabilirsiniz, t1 şablonunda slot tanımlanır
Temel Kişi Bilgisi
isim: {{ .name }}
yaş: {{ .age }}
adres: {{ .address }}
{{ block "slot" . }} varsayılan içerik gövdesi {{ end }}block deyimi slottaki varsayılan içeriği tanımlayabilir, diğer şablonlar slotu kullanırken varsayılan içeriğin üzerine yazılır. t2 şablonunda t1 şablonu referans alınır ve define ile gömülü içerik tanımlanır
{{ template "person.txt" . }}
{{ define "slot" }}
okul: {{ .school }}
{{ end }}İki şablon ilişkilendirildikten sonra, aşağıdaki veri传入 edilir
map[string]any{
"name": "jack",
"age": 18,
"address": "usa",
"company": "google",
"school": "mit",
}Nihai çıktı sonucu
Temel Kişi Bilgisi
isim: jack
yaş: 18
adres: usa
okul: mitŞablon Dosyaları
Şablon sözdizimi örneklerinde, şablon olarak string literal kullanılmıştır, pratik kullanımda çoğunlukla şablonlar dosyalara konur.
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)Örneğin template.ParseFs, belirtilen dosya sisteminden pattern ile eşleşen şablonları yükler. Aşağıdaki örnek embed.FS'yi dosya sistemi olarak kullanır, üç dosya hazırlanır
# person.txt
Temel Kişi Bilgisi
isim: {{ .name }}
yaş: {{ .age }}
adres: {{ .address }}
{{ block "slot" . }} {{ end }}
# student.txt
{{ template "person.txt" . }}
{{ define "slot" }}
okul: {{ .school }}
{{ end }}
# employee.txt
{{ template "person.txt" . }}
{{ define "slot" }}
şirket: {{ .company }}
{{ end }}Kod aşağıdaki gibidir
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)
}Çıktı
Temel Kişi Bilgisi
isim: jack
yaş: 18
adres: usa
okul: mit
Temel Kişi Bilgisi
isim: jack
yaş: 18
adres: usa
şirket: googleBu çok basit bir şablon dosyası kullanım örneğidir, person.txt slot dosyası olarak kullanılır, diğer ikisi içeriği yeniden kullanır ve özel yeni içerik ekler. Aşağıdaki iki fonksiyon da kullanılabilir
func ParseGlob(pattern string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)ParseGlob wildcard eşleştirmesine dayanır, ParseFiles dosya adına dayanır, her ikisi de yerel dosya sistemini kullanır. Eğer frontend'de gösterilen html dosyası için kullanılıyorsa, html/template paketi kullanılması önerilir, sağladığı API text/template ile tamamen aynıdır, ancak html, css, js için güvenlik işlemleri yapar.
