Skip to content

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

go
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

go
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.

go
func (t *Template) Execute(wr io.Writer, data any) error

Şablon motoru kullanımında, temelde bu üç adım vardır:

  1. Şablonu al
  2. Şablonu ayrıştır
  3. 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.

go
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

go
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

go
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

go
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>2

Dikkat 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

go
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ı

jack

Değ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

go
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
andVE işlemi{{ and true false }}
orVEYA işlemi{{ or true false }}
notDeğilleme işlemi{{ not true }}
eqEşit mi{{ eq 1 2 }}
neEşit değil mi{{ ne 1 2 }}
ltKüçük mü{{ lt 1 2 }}
leKüçük eşit mi{{ le 1 2 }}
gtBüyük mü{{ gt 1 2 }}
geBüyük eşit mi{{ ge 1 2 }}
lenUzunluğu döndürür{{ len .slice }}
indexHedefin belirli indeksindeki elementi alır{{ index . 0 }}
sliceSlice, s[1:2:3] ile eşdeğer{{ slice . 1 2 3 }}
htmlHTML escape{{ html .name }}
jsjs escape{{ js .name }}
printfmt.Sprint{{ print . }}
printffmt.Sprintf{{ printf "%s" .}}
printlnfmt.Sprintln{{ println . }}
urlqueryurl 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

go
map[string]any{
    "string": func(val any) string { return fmt.Sprintf("%v: 2048", val) },
}

O zaman şablonda oluşturulur

1024: 2048

Bu ö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.

go
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

go
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 + 1

Pipeline

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.

jackmike

with 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

go
map[string]any{
    "name": map[string]any{
        "first":  "jack",
        "second": "bob",
    },
    "age":     1,
    "address": "usa",
}

Çıktısı

name:bob-jack
age: 1
address: usa

with 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

go
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

go
[]any{1, "2", 3.14},

Çıktı

1: 2
2: 3.14

map ü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) error

Aş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

go
map[string]any{
    "t1": map[string]any{"data": "şablon gövdesi 1"},
    "t2": map[string]any{"data": "şablon gövdesi 2"},
}

Kod

go
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 1

Veya ş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

go
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

go
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)

Nihai şablon oluşturma sonucu

Kişi Bilgisi
isim: jack
yaş: 18

Slot

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

go
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.

go
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

txt
# 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

go
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: google

Bu ç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

go
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.

Golang by www.golangdev.cn edit