Obsah
1. Standardní šablonovací systém programovacího jazyka Go (dokončení)
2. Naformátování hodnot funkcí fmt.Sprintf volanou přímo ze šablony
4. Praktické otestování podmínky v šabloně
5. Plná podmínka typu if-then-else v šabloně
7. Pokus o přístup k privátní metodě z šablony
8. Kolony (pipeline) v šabloně
9. Blok with a proměnné v šablonách
10. Definice pojmenované šablony
11. Předání funkce do šablony, vyvolání funkce v šabloně
12. Kombinace předchozích možností – blok with a předání i použití funkcí v šabloně
13. Dvourozměrná pole v šablonách
14. Vytištění tabulky malé násobilky
15. Vylepšení předchozího demonstračního příkladu
16. Přístup k poli, které je uloženo jako prvek datové struktury
18. Další šablonovací systémy dostupné pro jazyk Go
19. Repositář s demonstračními příklady
1. Standardní šablonovací systém programovacího jazyka Go (dokončení)
V závěru úvodního článku o standardním šablonovacím systému programovacího jazyka Go jsme si ukázali, že přímo v šabloně je možné definovat oblast, která se má opakovat pro všechny prvky získané ze vstupních dat. Tato v praxi velmi užitečná funkcionalita je založena na použití značek „{{range selektor}}“ a „{{end}}“. Selektorem je přitom myšleno určení opakujících se prvků ve vstupních datech. Prozatím zde využijeme tečku (dot), ale v navazujících kapitolách si ukážeme, že lze iterovat i přes všechny hodnoty vybraného prvku apod. Vše, co je zapsáno mezi značkami „{{range}}“ a „{{end}}“ (tedy jak běžný text, tak i značky šablony), bude opakováno tolikrát, kolik prvků je nalezeno ve vstupních datech. Přístup k hodnotám těchto prvků je opět proveden s využitím nám již dobře známé tečkové notace, tedy například:
{{range .}}Jméno {{.Name}} {{.Surname}} Popularita {{.Popularity}} --- {{end}}
Tato šablona byla použita v následujícím demonstračním příkladu, s nímž jsme se taktéž seznámili již minule:
package main import ( "os" "text/template" ) const ( templateFilename = "template15.txt" ) // datový typ, jehož prvky budou vypisovány v šabloně type Role struct { Name string Surname string Popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.ParseFiles(templateFilename)) // tyto hodnoty budou použity při aplikaci šablony roles := []Role{ Role{"Eliška", "Najbrtová", 4}, Role{"Jenny", "Suk", 3}, Role{"Anička", "Šafářová", 1}, Role{"Sváťa", "Pulec", 3}, Role{"Blažej", "Motyčka", 8}, Role{"Eda", "Wasserfall", 3}, Role{"Přemysl", "Hájek", 10}, } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles) if err != nil { panic(err) } }
S výsledkem:
Jméno Eliška Najbrtová Popularita 4 --- Jméno Jenny Suk Popularita 3 --- Jméno Anička Šafářová Popularita 1 --- Jméno Sváťa Pulec Popularita 3 --- Jméno Blažej Motyčka Popularita 8 --- Jméno Eda Wasserfall Popularita 3 --- Jméno Přemysl Hájek Popularita 10 ---
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template15.go.
2. Naformátování hodnot funkcí fmt.Sprintf volanou přímo ze šablony
V mnoha případech by se hodilo hodnoty, které se vkládají do šablony, nějakým způsobem naformátovat, což se týká například numerických hodnot, řetězců s různou délkou vstupu apod. Autoři šablonovacích systémů přistupují k tomuto problému z různých stran, většinou přidáním dalších znaků se speciálním významem do doménově specifického šablonovacího jazyka. V případě standardního šablonovacího systému programovacího jazyka Go je tomu ovšem jinak, protože pro naformátování se většinou používá standardní funkce fmt.Printf přesněji řečeno resp. fmt.Sprintf. Volání této funkce v šabloně se však provádí bez závorek okolo parametrů, například následovně:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} Popularita {{printf "%2d" .Popularity}} {{end}} --------------------------------------------------------------------
To například znamená, že následující část šablony:
{{printf "%-15s" .Name}}
Vlastně odpovídá volání:
fmt.Sprintf("%-15s", item.Name)
V případě, že výše uvedenou šablonu použijeme v demonstračním příkladu, bude výstup naformátován tímto způsobem:
-------------------------------------------------------------------- Jméno Eliška Najbrtová Popularita 4 Jméno Jenny Suk Popularita 3 Jméno Anička Šafářová Popularita 1 Jméno Sváťa Pulec Popularita 3 Jméno Blažej Motyčka Popularita 8 Jméno Eda Wasserfall Popularita 3 Jméno Přemysl Hájek Popularita 10 --------------------------------------------------------------------
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template16.go.
Jméno v šabloně | Volaná funkce |
---|---|
fmt.Sprint | |
printf | fmt.Sprintf |
println | fmt.Sprintln |
urlquery | přeformátování řetězce tak, aby ho bylo možné použít v URL |
3. Podmínky v šablonách
Mnohdy se taktéž setkáme s nutností použít v šablonách podmínku, což znamená, že určitá část textu bude ve výsledku použita pouze při splnění nějaké podmínky (a popř. jiná při nesplnění té samé podmínky). Příkladem může být například šablona, která je součástí dokumentace/nápovědy ke standardnímu šablonovacímu systému jazyka Go:
Dear {{.Name}}, {{if .Attended}} It was a pleasure to see you at the wedding. {{else}} It is a shame you couldn't make it to the wedding. {{end}} {{end}} Best wishes, Josie
V podmínce se mohou volat funkce, které odpovídají standardním relačním operátorům:
Funkce | Odpovídá výrazu |
---|---|
eq | arg1 == arg2 |
ne | arg1 != arg2 |
lt | arg1 < arg2 |
le | arg1 <= arg2 |
gt | arg1 > arg2 |
ge | arg1 >= arg2 |
4. Praktické otestování podmínky v šabloně
Podívejme se nyní na praktický způsob použití šablony s podmínkou. Budeme rozlišovat, zda je popularita role v Cimrmanovských hrách známá či nikoli. Použijeme přitom explicitní test, zda je zapsaná hodnota ostře větší než nula:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} {{if gt .Popularity 0}} Popularita {{printf "%2d" .Popularity}} {{end}} {{end}} --------------------------------------------------------------------
Zdrojový kód demonstračního příkladu se změní jen nepatrně – pozměníme hodnoty prvků Popularity:
package main import ( "os" "text/template" ) const ( templateFilename = "template17.txt" ) // datový typ, jehož prvky budou vypisovány v šabloně type Role struct { Name string Surname string Popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.ParseFiles(templateFilename)) // tyto hodnoty budou použity při aplikaci šablony roles := []Role{ Role{"Eliška", "Najbrtová", 4}, Role{"Jenny", "Suk", 3}, Role{"Anička", "Šafářová", 0}, Role{"Sváťa", "Pulec", 3}, Role{"Blažej", "Motyčka", 8}, Role{"Eda", "Wasserfall", 0}, Role{"Přemysl", "Hájek", 10}, } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles) if err != nil { panic(err) } }
Výsledek by měl vypadat následovně:
-------------------------------------------------------------------- Jméno Eliška Najbrtová Popularita 4 Jméno Jenny Suk Popularita 3 Jméno Anička Šafářová Jméno Sváťa Pulec Popularita 3 Jméno Blažej Motyčka Popularita 8 Jméno Eda Wasserfall Jméno Přemysl Hájek Popularita 10 --------------------------------------------------------------------
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template17.go.
5. Plná podmínka typu if-then-else v šabloně
V šabloně lze dále použít úplnou podmínku, tj. určit, který text se má přidat do výsledku ve chvíli, kdy nějaká podmínka je splněna a který text se má naopak přidat v případě nesplnění podmínky. V praxi to může vypadat například následovně – rozlišíme, zda je popularita role známá či neznámá:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} Popularita {{if gt .Popularity 0}} {{printf "%2d" .Popularity}} {{else}} neznámá {{end}} {{end}} --------------------------------------------------------------------
Výsledek běhu předchozího demonstračního příkladu ve chvíli, kdy se mu předá upravená šablona s plnou podmínkou typu if-then-else:
-------------------------------------------------------------------- Jméno Eliška Najbrtová Popularita 4 Jméno Jenny Suk Popularita 3 Jméno Anička Šafářová Popularita neznámá Jméno Sváťa Pulec Popularita 3 Jméno Blažej Motyčka Popularita 8 Jméno Eda Wasserfall Popularita neznámá Jméno Přemysl Hájek Popularita 10 --------------------------------------------------------------------
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template18.go.
6. Volání metod z šablony
Připomeňme si, že data, která jsou předána ve chvíli, kdy je nutné šablonu aplikovat, jsou z šablony přístupná přes „tečku“ a že je možné přistupovat k atributům či prvkům vstupní datové struktury. V případě polí, řezů či map lze tedy použít iteraci přes všechny prvky:
-------------------------------------------------------------------- {{range .}}... {{end}} --------------------------------------------------------------------
Uvnitř této (de facto) smyčky se k prvkům a jejich atributům opět přistupuje přes tečku:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} {{end}} --------------------------------------------------------------------
Můžeme však zavolat i metodu definovanou pro danou strukturu, a to následovně:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} Popularita {{.GetPopularity}} {{end}} --------------------------------------------------------------------
Podívejme se nyní na příklad použití v případě, že pro nám již známou datovou strukturu Role definujeme metodu nazvanou GetPopularity, která je bez parametrů a vrací řetězec:
package main import ( "fmt" "os" "text/template" ) const ( templateFilename = "template19.txt" ) // datový typ, jehož prvky budou vypisovány v šabloně type Role struct { Name string Surname string Popularity int } func (role Role) GetPopularity() string { if role.Popularity <= 0 { return "Nezadáno" } else { return fmt.Sprintf("%d", role.Popularity) } } func main() { // vytvoření nové šablony tmpl := template.Must(template.ParseFiles(templateFilename)) // tyto hodnoty budou použity při aplikaci šablony roles := []Role{ Role{"Eliška", "Najbrtová", 4}, Role{"Jenny", "Suk", 3}, Role{"Anička", "Šafářová", 0}, Role{"Sváťa", "Pulec", 3}, Role{"Blažej", "Motyčka", 8}, Role{"Eda", "Wasserfall", 0}, Role{"Přemysl", "Hájek", 10}, } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles) if err != nil { panic(err) } }
Výsledek aplikace této šablony by měl vypadat následovně:
-------------------------------------------------------------------- Jméno Eliška Najbrtová Popularita 4 Jméno Jenny Suk Popularita 3 Jméno Anička Šafářová Popularita Nezadáno Jméno Sváťa Pulec Popularita 3 Jméno Blažej Motyčka Popularita 8 Jméno Eda Wasserfall Popularita Nezadáno Jméno Přemysl Hájek Popularita 10 --------------------------------------------------------------------
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template19.go.
7. Pokus o přístup k privátní metodě z šablony
Minule jsme si kromě dalších informací řekli i to, že v šabloně není možné přistupovat k privátním atributům, resp. k prvkům datové struktury, tj. k takovým položkám, jejichž jména začínají malým písmenem. Totéž ovšem platí i pro metody – pokud je metoda privátní, tj. když její jméno začíná malým písmenem, nebude možné takovou metodu přímo z šablony zavolat.
Toto chování si můžeme snadno otestovat změnou šablony:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} Popularita {{.getPopularity}} {{end}} --------------------------------------------------------------------
A nepatrnou modifikací zdrojového kódu demonstračního příkladu:
package main import ( "fmt" "os" "text/template" ) const ( templateFilename = "template20.txt" ) // datový typ, jehož prvky budou vypisovány v šabloně type Role struct { Name string Surname string Popularity int } func (role Role) getPopularity() string { if role.Popularity <= 0 { return "Nezadáno" } else { return fmt.Sprintf("%d", role.Popularity) } } func main() { // vytvoření nové šablony tmpl := template.Must(template.ParseFiles(templateFilename)) // tyto hodnoty budou použity při aplikaci šablony roles := []Role{ Role{"Eliška", "Najbrtová", 4}, Role{"Jenny", "Suk", 3}, Role{"Anička", "Šafářová", 0}, Role{"Sváťa", "Pulec", 3}, Role{"Blažej", "Motyčka", 8}, Role{"Eda", "Wasserfall", 0}, Role{"Přemysl", "Hájek", 10}, } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles) if err != nil { panic(err) } }
Tento problém nebude zjištěn při překladu (compile time), ale až po spuštění programu (runtime):
-------------------------------------------------------------------- Jméno Eliška Najbrtová Popularita panic: template: template20.txt:2:84: executing "template20.txt" at <.getPopularity>: can't evaluate field getPopularity in type main.Role goroutine 1 [running]: main.main() /home/ptisnovs/temp/y/template20.go:46 +0xeb exit status 2
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template20.go.
8. Kolony (pipeline) v šabloně
Ve chvíli, kdy je nutné nějakým způsobem zpracovat určitou datovou položku větším množstvím funkcí, lze využít další velmi zajímavou vlastnost šablonovacího systému programovacího jazyka Go – pro postupné zpracovávání dat je totiž možné definovat kolony (pipeline), které se způsobem zápisu a vlastně i svým chováním podobají klasickým Unixovým kolonám. Kolony se zapisují znakem „|“ a umožňují výsledek jedné operace převést do operace další (což je typicky volání nějaké funkce).
Podívejme se nyní na to, jak může vypadat jednoduchá kolona, v níž hodnotu získanou metodou Role.GetPopularily() (která vrací řetězec) necháme naformátovat funkcí fmt.Sprintf:
.GetPopularity | printf "%10s"
Výše uvedenou kolonu lze snadno zakomponovat do šablony, a to takto:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} Popularita {{.GetPopularity | printf "%10s"}} {{end}} --------------------------------------------------------------------
Výsledek aplikace šablony na vstupní data, včetně naformátování posledního sloupce funkcí fmt.Sprintf:
-------------------------------------------------------------------- Jméno Eliška Najbrtová Popularita 4 Jméno Jenny Suk Popularita 3 Jméno Anička Šafářová Popularita Nezadáno Jméno Sváťa Pulec Popularita 3 Jméno Blažej Motyčka Popularita 8 Jméno Eda Wasserfall Popularita Nezadáno Jméno Přemysl Hájek Popularita 10 --------------------------------------------------------------------
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template21.go.
9. Blok with a proměnné v šablonách
Další zajímavou a pro složitější šablony i užitečnou vlastností je podpora pro definici bloků v šabloně, přičemž v rámci bloku je možné použít (a to přímo v šabloně) proměnné. Blok začíná značkou obsahující slovo with a končí značkou {{end}}, tedy například následovně:
{{with ...}} ... ... ... {{end}}
V bloku with lze definovat proměnné, například jim přiřadit hodnotu nějakého datového prvku, hodnotu výsledku volání metody atd.:
{{with $x := .VolanáMetoda ...}} ... ... ... {{end}}
Podívejme se nyní, jak lze upravit předchozí příklad, v němž byla použita kolona (pipeline). Úprava bude spočívat v tom, že hodnotu vrácenou metodou Role.GetPopularity uložíme do proměnné x platné v rámci bloku a posléze ji předáme funkci printf (tedy ve skutečnosti funkci fmt.Sprintf). Výsledná šablona bude vypadat takto:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} Popularita {{with $x := .GetPopularity}}{{printf "%10s" $x}}{{end}} {{end}} --------------------------------------------------------------------
Výsledek aplikace této šablony by měl být totožný s předchozím demonstračním příkladem, o čemž se ostatně můžeme velmi snadno přesvědčit:
-------------------------------------------------------------------- Jméno Eliška Najbrtová Popularita 4 Jméno Jenny Suk Popularita 3 Jméno Anička Šafářová Popularita Nezadáno Jméno Sváťa Pulec Popularita 3 Jméno Blažej Motyčka Popularita 8 Jméno Eda Wasserfall Popularita Nezadáno Jméno Přemysl Hájek Popularita 10 --------------------------------------------------------------------
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template22.go.
Alternativně je možné šablonu napsat s využitím kolony v bloku, tedy následovně:
-------------------------------------------------------------------- {{range .}}Jméno {{printf "%-15s" .Name}} {{printf "%-15s" .Surname}} Popularita {{with $x := .GetPopularity}}{{$x | printf "%10s"}}{{end}} {{end}} --------------------------------------------------------------------
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template23.go.
10. Definice pojmenované šablony
Další vlastností standardního šablonovacího systému programovacího jazyka Go je podpora pro definici pojmenované šablony, kterou je posléze možné použít v jiné šabloně. Pojmenovaná šablona se vytváří s využitím značky block; nutné je přitom uvést jméno šablony i celou kolonu určující, jaká data se budou zpracovávat. Přitom každé vyvolání šablony může teoreticky pracovat s různými daty:
{{block "jméno šablony" kolona/pipeline}} libovolný obsah {{end}}
Tento zápis se rozloží na definici šablony a na její zavolání:
{{define "jméno šablony"}} libovolný obsah {{end}} {{template "jméno šablony" kolona/pipeline}}
Ukažme si nyní základní způsob použití pojmenované šablony v nepatrně upraveném demonstračním příkladu:
package main import ( "os" "text/template" ) const ( templateValue = `Roles:{{block "roles" .}}{{"\n"}}{{range .}}{{println "-" .Name "\t" .Surname}}{{end}}{{end}}` ) // datový typ, jehož prvky budou vypisovány v šabloně type Role struct { Name string Surname string Popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.New("template").Parse(templateValue)) // tyto hodnoty budou použity při aplikaci šablony roles := []Role{ Role{"Eliška", "Najbrtová", 4}, Role{"Jenny", "Suk", 3}, Role{"Anička", "Šafářová", 0}, Role{"Sváťa", "Pulec", 3}, Role{"Blažej", "Motyčka", 8}, Role{"Eda", "Wasserfall", 0}, Role{"Přemysl", "Hájek", 10}, } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles) if err != nil { panic(err) } }
Výsledek po spuštění je nepatrně odlišný od předchozích příkladů:
Roles: - Eliška Najbrtová - Jenny Suk - Anička Šafářová - Sváťa Pulec - Blažej Motyčka - Eda Wasserfall - Přemysl Hájek
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template24.go.
11. Předání funkce do šablony, vyvolání funkce v šabloně
Představme si, že je požadováno, aby se ve výsledném dokumentu tvořeném šablonou vypsala všechna jména předaná v řezu:
roles := []string{ "Eliška Najbrtová", "Jenny Suk", "Anička Šafářová", "Sváťa Pulec", "Blažej Motyčka", "Eda Wasserfall", "Přemysl Hájek", }
Přitom by se jména měla vypsat na jediný řádek a oddělena by měla být čárkou (navíc by šablona měla být funkční i pro prázdný vstup):
Names: Eliška Najbrtová, Jenny Suk, Anička Šafářová, Sváťa Pulec, Blažej Motyčka, Eda Wasserfall, Přemysl Hájek
Samozřejmě je možné použít značku range a vhodným způsobem zařídit, aby se za posledním prvkem již čárka nevypisovala, ovšem existuje i jednodušší a především hotové a ověřené řešení – použít funkci strings.join, která je určena přesně pro provedení této operace. Chceme být tedy schopni zavolat funkci strings.join stejně, jako již umíme volat funkci fmt.Sprintf:
templateValue = `Names: {{join . ", "}}`
I to je ve standardním šablonovacím systému jazyka Go možné, protože při konstrukci šablony je možné předat mapu (libovolných) funkcí, včetně jejich jmen – ty se mohou lišit od skutečných jmen funkcí, protože jména v šablonách jsou vyhodnocována v době běhu (runtime) a nikoli v době překladu:
// mapa funkcí použitých v šabloně funcs := template.FuncMap{"join": strings.Join}
Předání jmen funkcí při konstrukci šablony:
tmpl := template.Must(template.New("template").Funcs(funcs).Parse(templateValue))
Samotná šablona se nyní zredukuje na volání příslušné funkce s předáním dat:
templateValue = `Names: {{join . ", "}}`
Pro úplnost si ukažme úplný zdrojový kód takto upraveného demonstračního příkladu:
package main import ( "os" "strings" "text/template" ) const ( templateValue = `Names: {{join . ", "}}` ) func main() { // mapa funkcí použitých v šabloně funcs := template.FuncMap{"join": strings.Join} // vytvoření nové šablony tmpl := template.Must(template.New("template").Funcs(funcs).Parse(templateValue)) // tyto hodnoty budou použity při aplikaci šablony roles := []string{ "Eliška Najbrtová", "Jenny Suk", "Anička Šafářová", "Sváťa Pulec", "Blažej Motyčka", "Eda Wasserfall", "Přemysl Hájek", } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles) if err != nil { panic(err) } }
Po spuštění si můžeme velmi snadno ověřit, že výsledky odpovídají zadání (a to i pro prázdný vstup):
Names: Eliška Najbrtová, Jenny Suk, Anička Šafářová, Sváťa Pulec, Blažej Motyčka, Eda Wasserfall, Přemysl Hájek
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template25.go.
12. Kombinace předchozích možností – blok with a předání i použití funkcí v šabloně
V dalším demonstračním příkladu zkombinujeme některé možnosti, které byly popsány v předchozích kapitolách. Zejména budeme z šablony volat dvojici funkcí (konkrétně funkce asNames a standardní funkci strings.Join a navíc použijeme i blok with, ve kterém vytvoříme a naplníme proměnnou names. Jedná se skutečně o kombinaci již známých postupů, takže si bez dalšího podrobnějšího popisu ukážeme, jak bude vypadat šablona i zdrojový kód demonstračního příkladu:
package main import ( "os" "strings" "text/template" ) const ( templateValue = `Names: {{with $names := asNames .}}{{join $names ", "}}{{end}}` ) // datový typ, jehož prvky budou vypisovány v šabloně type Role struct { Name string Surname string Popularity int } // převod rolí na řez se jmény rolí func asNames(roles []Role) []string { var r []string for _, role := range roles { r = append(r, role.Name) } return r } func main() { // mapa funkcí použitých v šabloně funcs := template.FuncMap{ "asNames": asNames, "join": strings.Join} // vytvoření nové šablony tmpl := template.Must(template.New("template").Funcs(funcs).Parse(templateValue)) // tyto hodnoty budou použity při aplikaci šablony roles := []Role{ Role{"Eliška", "Najbrtová", 4}, Role{"Jenny", "Suk", 3}, Role{"Anička", "Šafářová", 0}, Role{"Sváťa", "Pulec", 3}, Role{"Blažej", "Motyčka", 8}, Role{"Eda", "Wasserfall", 0}, Role{"Přemysl", "Hájek", 10}, } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles) if err != nil { panic(err) } }
Výsledkem běhu tohoto příkladu bude stejný výstup, jaký byl ukázán v předchozí kapitole, tedy:
Names: Eliška, Jenny, Anička, Sváťa, Blažej, Eda, Přemysl
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template26.go.
13. Dvourozměrná pole v šablonách
Doposud jsme do šablon předávali buď jednoduché (skalární) hodnoty, záznamy (struct) nebo pole resp. řezy (slice). Pochopitelně nám však nic nebrání v použití dvourozměrných polí, která jsou v určitých oblastech dominantním datovým typem (ostatně viz paralelně běžící miniseriál o „array programmingu“). Práce dvourozměrnými poli typicky vede k použití zanořených značek „{{range}}“, což vlastně znamená, že se interně pracuje s dvojicí vnořených programových smyček. To si ostatně ukážeme v navazující trojici kapitol.
14. Vytištění tabulky malé násobilky
V dalším demonstračním příkladu se pokusíme s využitím šablony vytisknout tabulku malé násobilky. Vstupními daty je v tomto případě dvourozměrná tabulka s malou násobilkou, která je v jazyce Go reprezentována běžným dvourozměrným polem:
// tabulka s malou násobilkou var multiplyTable [N][N]int
V případě, že nejsou zadány žádné speciální požadavky na formát jednotlivých řádků tabulky, může být šablona zredukována na pouhou jednu smyčku:
templateValue = `{{range .}}{{.}} {{end}}`
Je tomu tak z toho důvodu, že v rámci šablony lze vytisknout obsah jednorozměrného pole (neboli vektoru) – uvnitř smyčky je v „tečce“ uložen vždy právě celý řádek tabulky.
Úplný zdrojový kód tohoto příkladu by mohl vypadat následovně:
package main import ( "os" "text/template" ) const ( templateValue = `{{range .}}{{.}} {{end}}` ) func main() { const N = 10 // tabulka s malou násobilkou var multiplyTable [N][N]int // naplnění tabulky for j := 0; j < N; j++ { for i := 0; i < N; i++ { multiplyTable[j][i] = (i + 1) * (j + 1) } } // vytvoření nové šablony tmpl := template.Must(template.New("multiply_table").Parse(templateValue)) // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, multiplyTable) if err != nil { panic(err) } }
A výsledek získaný po spuštění sice není příliš pěkný, ovšem alespoň získáme všechny prvky tabulky s malou násobilkou:
[1 2 3 4 5 6 7 8 9 10] [2 4 6 8 10 12 14 16 18 20] [3 6 9 12 15 18 21 24 27 30] [4 8 12 16 20 24 28 32 36 40] [5 10 15 20 25 30 35 40 45 50] [6 12 18 24 30 36 42 48 54 60] [7 14 21 28 35 42 49 56 63 70] [8 16 24 32 40 48 56 64 72 80] [9 18 27 36 45 54 63 72 81 90] [10 20 30 40 50 60 70 80 90 100]
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template27.go.
15. Vylepšení předchozího demonstračního příkladu
Výsledek předchozího příkladu ve skutečnosti nebyl příliš pěkný, protože hodnoty ve sloupcích nebyly zarovnány. Z tohoto důvodu je nutné tabulku vytisknout poněkud odlišným způsobem – pomocí vnořených smyček. Nejprve budeme pro totožná vstupní data, jako tomu bylo v předchozím příkladu, procházet všemi řádky:
{{range .}} ... ... ... {{end}}
Uvnitř vnější smyčky odpovídá „tečka“ celému řádku, tedy jednorozměrnému vektoru. Nic nám tedy nezabraňuje postupně procházet i prvky tohoto vektoru (nyní ovšem bez odřádkování):
{{range .}}{{range .}}...{{end}} {{end}}`
Nyní nám již zbývá hodnoty vektoru vhodným způsobem naformátovat a oddělit od sebe (mezerou). Pro malou násobilku postačuje naformátování hodnot na tři místa:
{{range .}}{{range .}}{{printf "%3d" .}} {{end}} {{end}}`
Celý postup je použit v dalším, dnes již předposledním, demonstračním příkladu, jehož zdrojový kód vypadá následovně:
package main import ( "os" "text/template" ) const ( templateValue = `{{range .}}{{range .}}{{printf "%3d" .}} {{end}} {{end}}` ) func main() { const N = 10 // tabulka s malou násobilkou var multiplyTable [N][N]int // naplnění tabulky for j := 0; j < N; j++ { for i := 0; i < N; i++ { multiplyTable[j][i] = (i + 1) * (j + 1) } } // vytvoření nové šablony tmpl := template.Must(template.New("multiply_table").Parse(templateValue)) // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, multiplyTable) if err != nil { panic(err) } }
Nyní bude výsledek odlišný od předchozího příkladu, protože všechny hodnoty v jednotlivých sloupcích budou zarovnány na čtyři znaky:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template28.go.
16. Přístup k poli, které je uloženo jako prvek datové struktury
Pole se nemusí do šablony předávat přímo, ale může být součástí složitější datové struktury – typicky záznamu (struct, record). Podívejme se nyní na jednoduchou variantu, v níž je použit záznam obsahující jako svůj (jediný) prvek právě dvourozměrné pole:
type MultiplyTable struct { Values [N][N]int }
Práce s touto strukturou je triviální, pouze postačuje změnit šablonu z této podoby:
{{range .}}{{range .}}{{printf "%3d" .}} {{end}} {{end}}`
Na:
{{range .Values}}{{range .}}{{printf "%3d" .}} {{end}} {{end}}`
Nic dalšího není v šabloně nutné modifikovat:
package main import ( "os" "text/template" ) const ( templateValue = `{{range .Values}}{{range .}}{{printf "%3d" .}} {{end}} {{end}}` ) const N = 10 type MultiplyTable struct { Values [N][N]int } func main() { // tabulka s malou násobilkou var multiplyTable MultiplyTable // naplnění tabulky for j := 0; j < N; j++ { for i := 0; i < N; i++ { multiplyTable.Values[j][i] = (i + 1) * (j + 1) } } // vytvoření nové šablony tmpl := template.Must(template.New("multiply_table").Parse(templateValue)) // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, multiplyTable) if err != nil { panic(err) } }
Výsledky získané po spuštění tohoto příkladu by měly být totožné s příkladem předchozím, tedy:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
Úplný zdrojový kód demonstračního příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article80/template29.go.
17. Závěr
Minule a dnes jsme si popsali velkou část funkcionality standardního balíčku text/template. Jak (doufejme) bylo z příkladů patrné, jedná se o poměrně propracovaný šablonovací systém, který je navíc rozšiřitelný díky tomu, že je možné přímo volat metody datové struktury předané do šablony a taktéž je možné zaregistrovat prakticky libovolné množství uživatelských funkcí, které je možné ze šablony přímo volat. Zapomenout nesmíme na podporu proměnných, kolon (pipeline), podmínek, smyček atd. Jednou z nevýhod tohoto systému je fakt, že všechny kontroly struktury šablony jsou provedeny až v čase běhu, takže zde není možné využít kontroly prováděné překladačem jazyka Go (a jeho typovým systémem). Samotný balíček text/template je ještě více rozpracován v dalším standardním balíčku html/template, kterému bude věnován samostatný článek.
18. Další šablonovací systémy dostupné pro jazyk Go
Pro programovací jazyk Go vzniklo i poměrně velké množství dalších šablonovacích systémů, které se od sebe odlišují funkcionalitou, podporou různých výstupních formátů, použitým značkovacím jazykem, mírou NIH syndromu atd. V následující tabulce jsou uvedeny ty nejznámější šablonovací systémy, tj. systémy s největším množstvím „hvězdiček“ na GitHubu:
# | Název šablonovacího systému |
---|---|
0 | ace |
1 | amber |
2 | damsel |
3 | ego |
4 | extemplate |
5 | fasttemplate |
6 | gofpdf |
7 | gospin |
8 | goview |
9 | hero |
10 | jet |
11 | kasia |
12 | liquid |
13 | maroto |
14 | mustache |
15 | pongo2 |
16 | quicktemplate |
17 | raymond |
18 | Razor |
19 | Soy |
20 | sprig |
21 | velvet |
K vybraným šablonovacím systémům se ještě vrátíme v některém z dalších dílů seriálu o programovacím jazyce Go.
19. Repositář s demonstračními příklady
Zdrojové kódy všech minule i dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:
# | Příklad/soubor | Stručný popis | Cesta |
---|---|---|---|
1 | template01.go | vytvoření a aplikace šablony obsahující pouze statický text, kontrola chyby při Parse | https://github.com/tisnik/go-root/blob/master/article79/template01.go |
2 | template02.go | zavolání metody ExecuteTemplate namísto Execute | https://github.com/tisnik/go-root/blob/master/article79/template02.go |
3 | template03.go | zápis výsledného textu do bufferu převedeného na řetězec přes buffer | https://github.com/tisnik/go-root/blob/master/article79/template03.go |
4 | template04.go | konstrukce šablony pomocí template.Must s automatickou kontrolou chyby | https://github.com/tisnik/go-root/blob/master/article79/template04.go |
5 | template05.go | skutečná šablona produkující text na základě předaných dat – jednoduchý text | https://github.com/tisnik/go-root/blob/master/article79/template05.go |
6 | template06.go | vícenásobné použití vstupních dat v šabloně | https://github.com/tisnik/go-root/blob/master/article79/template06.go |
7 | template07.go | skutečná šablona produkující text na základě předaných dat, předání datové struktury | https://github.com/tisnik/go-root/blob/master/article79/template07.go |
8 | template08.go | šablona, na kterou se aplikuje nekompatibilní datová struktura | https://github.com/tisnik/go-root/blob/master/article79/template08.go |
9 | template09.go | textová data, kontrola korektního použití Unicode | https://github.com/tisnik/go-root/blob/master/article79/template09.go |
10 | template10.go | postupná aplikace šablony na data uložená v řezu | https://github.com/tisnik/go-root/blob/master/article79/template10.go |
11 | template11.go | pokus o přístup k prvkům šablony, které jsou privátní | https://github.com/tisnik/go-root/blob/master/article79/template11.go |
12 | template12.go | opakování (range) v šabloně a práce s poli | https://github.com/tisnik/go-root/blob/master/article79/template12.go |
13 | template13.go | šablona uložená v souboru – problém s pojmenováním šablony | https://github.com/tisnik/go-root/blob/master/article79/template13.go |
14 | template14.go | šablona uložená v souboru – korektní příklad | https://github.com/tisnik/go-root/blob/master/article79/template14.go |
15 | template15.go | šablona uložená v souboru – korektní příklad, přímé volání ParseFiles | https://github.com/tisnik/go-root/blob/master/article79/template15.go |
16 | template16.go | naformátování hodnot funkcí fmt.Printf volanou přímo ze šablony | https://github.com/tisnik/go-root/blob/master/article80/template16.go |
17 | template17.go | praktické otestování podmínky v šabloně | https://github.com/tisnik/go-root/blob/master/article80/template17.go |
18 | template18.go | plná podmínka typu if-then-else v šabloně | https://github.com/tisnik/go-root/blob/master/article80/template18.go |
19 | template19.go | volání metod z šablony | https://github.com/tisnik/go-root/blob/master/article80/template19.go |
20 | template20.go | pokus o přístup k privátní metodě z šablony | https://github.com/tisnik/go-root/blob/master/article80/template20.go |
21 | template21.go | kolony (pipeline) v šabloně | https://github.com/tisnik/go-root/blob/master/article80/template21.go |
22 | template22.go | blok with a proměnné v šablonách | https://github.com/tisnik/go-root/blob/master/article80/template22.go |
23 | template23.go | alternativa k předchozímu demonstračnímu příkladu | https://github.com/tisnik/go-root/blob/master/article80/template23.go |
24 | template24.go | definice pojmenované šablony | https://github.com/tisnik/go-root/blob/master/article80/template24.go |
25 | template25.go | předání funkce do šablony, vyvolání funkce v šabloně | https://github.com/tisnik/go-root/blob/master/article80/template25.go |
26 | template26.go | kombinace předchozích možností – blok with a předání i použití funkcí v šabloně | https://github.com/tisnik/go-root/blob/master/article80/template26.go |
27 | template27.go | vytištění tabulky malé násobilky | https://github.com/tisnik/go-root/blob/master/article80/template27.go |
28 | template28.go | vylepšení předchozího demonstračního příkladu | https://github.com/tisnik/go-root/blob/master/article80/template28.go |
29 | template29.go | přístup k poli, které je uloženo jako prvek datové struktury | https://github.com/tisnik/go-root/blob/master/article80/template29.go |
20. Odkazy na Internetu
- Awesome Go
https://awesome-go.com/ - Template Engines for Go
https://awesome-go.com/#template-engines - Mail merge
https://en.wikipedia.org/wiki/Mail_merge - Template processor
https://en.wikipedia.org/wiki/Template_processor - Text/template
https://pkg.go.dev/text/template - Go Template Engines
https://go.libhunt.com/categories/556-template-engines - Template Engines
https://reposhub.com/go/template-engines - GoLang Templating Made Easy
https://awkwardferny.medium.com/golang-templating-made-easy-4d69d663c558 - Templates in GoLang
https://golangdocs.com/templates-in-golang - What are the best template engines for Go apart from „html/template“?
https://www.quora.com/What-are-the-best-template-engines-for-Go-apart-from-html-template?share=1 - Ace – HTML template engine for Go
https://github.com/yosssi/ace - amber
https://github.com/eknkc/amber - quicktemplate
https://github.com/valyala/quicktemplate - Šablonovací systém ace
https://github.com/yosssi/ace - Šablonovací systém amber
https://github.com/eknkc/amber - Šablonovací systém damsel
https://github.com/dskinner/damsel - Šablonovací systém ego
https://github.com/benbjohnson/ego - Šablonovací systém extemplate
https://github.com/dannyvankooten/extemplate - Šablonovací systém fasttemplate
https://github.com/valyala/fasttemplate - Šablonovací systém gofpdf
https://github.com/jung-kurt/gofpdf - Šablonovací systém gospin
https://github.com/m1/gospin - Šablonovací systém goview
https://github.com/foolin/goview