Obsah
1. Standardní šablonovací systém jazyka Go
3. Aplikace šablony obsahující pouze konstantní text
4. Aplikace šablony metodou ExecuteTemplate
5. Získání řetězce s aplikovanou šablonou
6. Konstrukce šablony s automatickou kontrolou chyb – template.Must
7. Předání dat, které se v šabloně použijí
8. Vícenásobné použití vstupních dat v šabloně
9. Využití složitější datové struktury v šabloně
10. Pokus o předání nekompatibilní datové struktury s odlišnými jmény prvků
11. Předání a využití struktury obsahující textové položky
12. Vícenásobné použití šablony pro různá vstupní data
13. Pokus o přístup k privátním prvkům datové struktury
14. Iterace přes prvky pole či řezu přímo v šabloně – konstrukce {{range}}
15. Praktická ukázka iterace přes prvky pole či řezu přímo v šabloně
16. Šablona uložená v samostatném souboru – problém s pojmenováním šablony
17. Korektní způsob načtení a použití šablony uložené v samostatném souboru
18. Přímé volání konstruktoru ParseFiles
19. Repositář s demonstračními příklady
1. Standardní šablonovací systém jazyka Go
V praxi se velmi často setkáme s požadavkem na tvorbu různých dokumentů, tiskových sestav, výstupních protokolů atd. Všechny tyto typy výstupů většinou mají společnou vlastnost – jsou založeny na nějakém dokumentu s předpřipraveným formátem (například se může jednat o předpis, jak má vypadat objednávka, faktura, protokol s výsledkem měření atd.), do kterého se na k tomu určená místa dosazují hodnoty připravené programem – tedy hodnoty nějakým způsobem dopočítané, hodnoty přečtené z databáze apod. A právě k těmto účelům slouží šablony (template) (což jsou ony předpřipravené dokumenty) a šablonovací systémy, které umožňují propojit šablonu s reálnými daty a vytvořit tak výsledný dokument.
Obrázek 1: Princip činnosti šablonovacího systému.
První šablonovací systémy byly vyvinuty jako součást zařízení určených pro přípravu, editaci a tisk textů. Jednalo se o specializované počítače; typickým příkladem jsou stroje společnosti Wang. Do vytvářených dokumentů bylo možné přidávat i řídicí kódy, které například umožňovaly vkládání obrázků (což nás dnes nezajímá) či byly určeny k takzvanému mail merge, což je systém použitelný například pro tisk obálek, vysvědčení, obchodních dopisů atd. (stejná šablona, odlišný obsah). A právě postupným zobecňováním funkce mail merge vznikly obecnější šablonovací systémy.
Obrázek 2: Wang 1220 je jedním z prvních modelů specializovaných zařízení firmy Wang Laboratories určených pro přípravu a editaci textů, jejich ukládání na magnetické pásky (jednotka pro magnetické pásky je umístěná napravo) a dokonce i pro provádění jednoduchého vyplňování formulářů z databáze (mail-merge).
Šablonovací systémy existují pro všechny mainstreamové programovací jazyky. Příkladem může být Python s několika desítkami knihoven. I pro jazyk Go existuje velké množství podobně koncipovaných nástrojů. Dnes se však budeme zabývat nástrojem, který je součástí standardní knihovny jazyka Go. Konkrétně se jedná o balíček nazvaný text/template, jenž je až překvapivě výkonný a přitom rozšiřitelný. Existuje i varianta tohoto balíčku nazvaná html/template, tou se však dnes zabývat nebudeme.
Obrázek 3: Na pravé straně klávesnice zařízení Wang 1220 byly umístěny klávesy pro ovládání textového procesoru.
2. Konstrukce šablony
Podívejme se nejdříve na to, jakým způsobem se vytvoří (zkonstruuje) objekt, který představuje šablonu. Posléze budeme tuto šablonu aplikovat, ovšem vzhledem k tomu, že text šablony obsahuje pouze neměnný text, bude výsledkem aplikace šablony původní text. Konstrukce objektu, který představuje šablonu, je snadná. Nejprve zavoláme konstruktor template.New kterému předáme název šablony a následně metodu Template.Parse, které předáme řetězec se šablonou:
// vytvoření nové šablony tmpl, err := template.New(templateName).Parse(templateFormat)
Vidíme, že musíme znát dvě hodnoty, a to konkrétně jméno šablony a vlastní text (obsah) šablony. V obou případech se jedná o řetězce, takže:
const ( templateName = "test" templateFormat = "Toto je testovací šablona" )
Metoda Parse může skončit s chybou, takže je navíc nutné provést kontrolu, zda se zpracování podařilo či nikoli:
if err != nil { panic(err) }
3. Aplikace šablony obsahující pouze konstantní text
Dalším krokem je aplikace šablony, tj. operace, při níž je šablonovacímu systému předána jak již zkonstruovaná šablona, tak i data, která se mají „propsat“ do výsledného textu. Tato aplikace je provedena metodou Template.Execute. Této metodě se předávají dva parametry, a to konkrétně objekt typu io.Writer, do kterého bude výsledný text zapisován a (libovolná) datová struktura, jejíž obsah se má propsat do výsledného textu. Podívejme se nyní na dnešní první demonstrační příklad, v němž je tato operace provedena. Výstup je proveden do os.Stdout, tedy na standardní výstup (terminál):
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "Toto je testovací šablona" ) func main() { // vytvoření nové šablony tmpl, err := template.New(templateName).Parse(templateFormat) if err != nil { panic(err) } // aplikace šablony - přepis hodnot + výpis výsledku err = tmpl.Execute(os.Stdout, nil) if err != nil { panic(err) } }
Výsledkem by měl být text, který odpovídá konstantě předávané při konstrukci šablony:
Toto je testovací šablona
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template01.go.
4. Aplikace šablony metodou ExecuteTemplate
V úvodním demonstračním příkladu byla aplikace šablony provedena metodou Execute, které se předával pouze objekt typu io.Writer a data propisovaná do šablony. Existuje ovšem ještě jedna podobně pojmenovaná metoda – ExecuteTemplate. Této metodě se navíc předává i jméno šablony; volání tedy musí vypadat následovně:
err = tmpl.ExecuteTemplate(os.Stdout, templateName, nil) if err != nil { panic(err) }
Můžete se ptát, proč vlastně tato metoda existuje. Ve skutečnosti je možné, což si ostatně ukážeme v dalších kapitolách, jediným zavoláním metody ParseFiles načíst a zpracovat větší množství šablon. Každé takové šabloně je potom přiřazeno jméno odvozené od templateName a jména souboru se šablonou. A právě toto jméno lze použít pro rozlišení toho, jakou šablonu v daný okamžik použít.
Ve druhém demonstračním příkladu je použit právě tento způsob aplikace šablony:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "Toto je testovací šablona" ) func main() { // vytvoření nové šablony tmpl, err := template.New(templateName).Parse(templateFormat) if err != nil { panic(err) } // aplikace šablony - přepis hodnot + výpis výsledku err = tmpl.ExecuteTemplate(os.Stdout, templateName, nil) if err != nil { panic(err) } }
Výsledek po překladu a spuštění:
Toto je testovací šablona
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template02.go.
5. Získání řetězce s aplikovanou šablonou
Při pohledu na první dva demonstrační příklady jste si mohli všimnout, že výsledný text získaný aplikací šablony není vrácen ve formě běžného řetězce. Výstup je totiž proveden do objektu typu io.Writer, což je nám již velmi dobře známé rozhraní s jedinou metodou Write. Důvod pro toto chování je jednoduchý – šablony totiž mohou obsahovat smyčky a mohou zpracovávat velmi rozsáhlá data (tabulky). Nebylo by tedy optimální vytvářet neustále rostoucí text (což je paměťově i výkonově náročná operace). Namísto toho se části aplikované šablony postupně posílají na výstup právě zmíněnou metodou Write.
V případě, že skutečně vyžadujeme, aby byl výsledkem aplikace šablony běžný řetězec, pomůžeme si pomocným objektem – bufferem. Do bufferu si necháme vypsat výsledek aplikace šablony a následně z bufferu text získáme, což je v Go poměrně idiomatický přístup:
// buffer pro uložení výsledků aplikace šablony buffer := new(bytes.Buffer) // aplikace šablony - přepis hodnot err = tmpl.Execute(buffer, nil) ... ... ... // výpis výsledného textu fmt.Println(buffer.String())
Celý postup je ukázán na následujícím demonstračním příkladu:
package main import ( "bytes" "fmt" "text/template" ) const ( templateName = "test" templateFormat = "Toto je testovací šablona" ) func main() { // vytvoření nové šablony tmpl, err := template.New(templateName).Parse(templateFormat) if err != nil { panic(err) } // buffer pro uložení výsledků aplikace šablony buffer := new(bytes.Buffer) // aplikace šablony - přepis hodnot err = tmpl.Execute(buffer, nil) if err != nil { panic(err) } // výpis výsledného textu fmt.Println(buffer.String()) }
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template03.go.
6. Konstrukce šablony s automatickou kontrolou chyb – template.Must
V předchozí kapitole jsme se zmínili o jednom z idiomů, který je v jazyku Go používán. Dalším idiomem je náhrada explicitní kontroly výsledku nějaké operace, přesněji řečeno kontroly, zda operace proběhla v pořádku, za volání nějaké formy funkce Must. V našem konkrétním případě je možné nahradit volání konstruktoru New následovaného voláním metody Parse a kontrolou chyby, tedy tento programový kód:
// vytvoření nové šablony tmpl, err := template.New(templateName).Parse(templateFormat) if err != nil { panic(err) }
Za tento kód:
// vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat))
V případě chyby bude druhé volání taktéž volat funkci panic.
Úprava prvního demonstračního příkladu tak, aby tuto techniku používal, je triviální:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "Toto je testovací šablona" ) func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, nil) if err != nil { panic(err) } }
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template04.go.
7. Předání dat, které se v šabloně použijí
Po zahřívacích kolech se konečně zaměřme na praktické využití šablon. Ukážeme si, jak je možné do šablony předat data, která se následně při aplikaci šablony použijí. Nejprve se podívejme, jak bude vypadat nová šablona:
templateFormat = "Hello {{.}}"
V této šabloně můžeme vidět použití znaků se speciálním významem – což jsou dvojice složených závorek „{{“ a „}}“. Text, který se nachází mezi těmito oddělovači, je zpracováván odlišně od běžného textu. V našem konkrétním případě je uvnitř oddělovačů použita pouze tečka, což je znak, který reprezentuje vstupní data. V tomto konkrétním případě je celá část šablony {{.}} nahrazena textovou podobou předaných dat. V tom nejjednodušším případě se bude jednat o řetězec:
err := tmpl.Execute(os.Stdout, "world") if err != nil { panic(err) }
Výsledkem aplikace dat na šablonu tedy bude výsledek:
Hello world
Pro úplnost si ukažme, jak vypadá celý skript, který takto popsanou aplikaci dat na šablonu provede:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "Hello {{.}}" ) func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, "world") if err != nil { panic(err) } }
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template05.go.
8. Vícenásobné použití vstupních dat v šabloně
Data, která jsou předána do šablony, je možné použít vícekrát (což se v praxi často děje). Podívejme se na velmi jednoduchou modifikaci předchozího demonstračního příkladu:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "The {{.}} language is often referred to as Golang, but the proper name is '{{.}}'." ) func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, "Go") if err != nil { panic(err) } }
Po spuštění tohoto demonstračního příkladu by se měl vypsat následující text:
The Go language is often referred to as Golang, but the proper name is 'Go'.
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template06.go.
9. Využití složitější datové struktury v šabloně
V předchozí dvojici demonstračních příkladů jsme v šabloně použili předaná data na jediném místě (popř. opakovaně), ale vždy vcelku. V praxi je však nutné například vyplnit adresu, tabulku s nakoupeným zbožím, výslednou cenu atd. – tedy využít v daném místě šablony jen určitou část předaných dat. Tento problém se v Go řeší tak, že se do šablony předává datová struktura (záznam) a v samotné šabloně se použije „tečková notace“ pro přístup k jednotlivým prvkům šablony.
Předávaná struktura může vypadat například takto:
type Expression struct { X int Y int Z int }
V šabloně lze využít jednotlivé prvky této struktury:
templateFormat = "Součet {{.X}} + {{.Y}} = {{.Z}}"
Předání dat do šablony je triviální:
// aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, expression) if err != nil { panic(err) }
Výsledný skript bude vypadat následovně:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "Součet {{.X}} + {{.Y}} = {{.Z}}" ) type Expression struct { X int Y int Z int } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // tyto hodnoty budou použity při aplikaci šablony expression := Expression{ X: 10, Y: 20, Z: 30, } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, expression) if err != nil { panic(err) } }
Výsledek po spuštění:
Součet 10 + 20 = 30
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template07.go.
10. Pokus o předání nekompatibilní datové struktury s odlišnými jmény prvků
V případě, že se do šablony budeme snažit předat nekompatibilní strukturu s odlišnými jmény prvků, dojde k běhové (runtime) chybě. Jinými slovy – chyba je odhalena až po spuštění příkladu, nikoli při jeho překladu. Ostatně si to můžeme vyzkoušet sami překladem a spuštěním dalšího demonstračního příkladu:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "Součet {{.X}} + {{.Y}} = {{.Z}}" ) type User struct { FirstName string Surname string Born string } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // tyto hodnoty budou použity při aplikaci šablony user := User{ FirstName: "Jára", Surname: "Cimrman", Born: "Böhmen", } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, user) if err != nil { panic(err) } }
Chyba, která se vypíše po spuštění tohoto příkladu:
Součet panic: template: test:1:10: executing "test" at <.X>: can't evaluate field X in type main.User goroutine 1 [running]: main.main() /home/ptisnovs/src/go-root/article_79/template06.go:33 +0x2a8
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template08.go.
11. Předání a využití struktury obsahující textové položky
Velmi často se setkáme s tím, že se do šablony předávají textové položky. Práce s nimi je (prozatím) prakticky stejná, jako tomu bylo s celočíselnými položkami, přičemž rozdíly se budeme zabývat příště. V dalším demonstračním příkladu zkontrolujeme, zda je možné v textových položkách používat Unicode – což je v Go dodrženo:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = "Uživatel {{.FirstName}} {{.Surname}} born in {{.Born}}" ) type User struct { FirstName string Surname string Born string } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // tyto hodnoty budou použity při aplikaci šablony user := User{ FirstName: "Jára", Surname: "Cimrman", Born: "Böhmen", } // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, user) if err != nil { panic(err) } }
Výsledek:
Uživatel Jára Cimrman born in Böhmen
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template09.go.
12. Vícenásobné použití šablony pro různá vstupní data
Šablonu je pochopitelně možné použít vícekrát – a ostatně právě z důvodu znovupoužitelnosti je nutné vytvářet objekt (datovou strukturu), která šablonu představuje. Pokud již totiž je šablona předpřipravena (ve formě objektu), je její vícenásobné použití rychlejší, než neustálé vytváření šablon pro použití jednorázová. V následujícím demonstračním příkladu je ukázáno, jakým způsobem můžeme šablonu aplikovat na větší množství struktur, které jsou uloženy v řezu a postupně zpracovávány v programové smyčce typu for-each:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = `Jméno {{.Name}} {{.Surname}} Popularita {{.Popularity}} ` ) type Role struct { Name string Surname string Popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // 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 for _, role := range roles { err := tmpl.Execute(os.Stdout, role) if err != nil { panic(err) } } }
Po spuštění tohoto demonstračního příkladu by se na standardní výstup měly vypsat následující řádky:
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 příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template10.go.
13. Pokus o přístup k privátním prvkům datové struktury
Jak je v programovacím jazyku Go zvykem, je možné „zvenku“ (tj. nikoli z metody) přistupovat pouze k veřejným (viditelným) prvkům datové struktury a nikoli k prvkům privátním. Rozlišení veřejný/privátní je provedeno na základě prvního znaku prvku datové struktury. Pokud je tento znak verzálkou, bude prvek veřejný, pokud minuskou, pak privátní (přitom se ovšem nemusí jednat o znak z ASCII!).
Můžeme se pochopitelně pokusit použít strukturu s privátními prvky:
type Role struct { name string surname string popularity int }
A v šabloně realizovat přístup k těmto prvkům:
templateFormat = `Jméno {{.name}} {{.surname}} Popularita {{.popularity}} `
Ovšem v čase běhu (runtime) dojde k běhové chybě:
$ ./template11 Jméno panic: template: test:1:9: executing "test" at <.name>: name is an unexported field of struct type main.Role goroutine 1 [running]: main.main() /home/ptisnovs/src/go-root/article_79/template11.go:41 +0x2dc
Pro úplnost si ukažme celý příklad, který po spuštění skončí s chybou:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = `Jméno {{.name}} {{.surname}} Popularita {{.popularity}} ` ) type Role struct { name string surname string popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // 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 for _, role := range roles { err := tmpl.Execute(os.Stdout, role) if err != nil { panic(err) } } }
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template11.go.
14. Iterace přes prvky pole či řezu přímo v šabloně – konstrukce {{range}}
Nyní se již dostáváme k mnohem zajímavějším vlastnostem standardní šablonovací knihovny programovacího jazyka Go. Často se totiž dostaneme do situace, kdy je například nutné vytvořit kusovník, fakturu s rozpisem zboží/služeb, protokol s tabulkami s výsledky atd. – vždy se tedy jedná o opakující se údaje se stejným formátem. Již ve dvanácté kapitole jsme si ukázali jedno z možných řešení – použití klasické programové smyčky typu for-each:
// aplikace šablony - přepis hodnot for _, role := range roles { err := tmpl.Execute(os.Stdout, role) if err != nil { panic(err) } }
Toto řešení však ani zdaleka není ideální, protože nás nutí si rozdělit šablonu do více částí, které se budou aplikovat samostatně. Existuje však i alternativní způsob – deklarovat přímo v šabloně místo, které se má opakovat pro všechny prvky získané ze vstupních dat. Tato možnost skutečně existuje a je založena na použití značek „{{range selektor}}“ a „{{end}}“. Selektorem je myšleno určení opakujících se prvků ve vstupních datech; prozatím zde využijeme tečku (dot). Vše, co je použito mezi značkami „{{range}}“ a „{{end}}“ 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 předpokládá, že vstupem bude pole či řez hodnot typu:
struct { Name string Surname string Popularity int }
15. Praktická ukázka iterace přes prvky pole či řezu přímo v šabloně
Povídejme se nyní na praktickou ukázku iterace (opakování) provedeného přes prvky pole či řezu, přičemž toto opakování je předepsáno přímo v šabloně:
const ( templateFormat = `{{range .}}Jméno {{.Name}} {{.Surname}} Popularita {{.Popularity}} {{end}}` )
Takto definovaná šablona je použita v následujícím demonstračním příkladu:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFormat = `{{range .}}Jméno {{.Name}} {{.Surname}} Popularita {{.Popularity}} {{end}}` ) type Role struct { Name string Surname string Popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).Parse(templateFormat)) // 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) } }
Výsledek po spuštění tohoto demonstračního příkladu by měl vypadat takto:
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 příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template12.go.
16. Šablona uložená v samostatném souboru – problém s pojmenováním šablony
Již při pohledu na zdrojový kód předchozího demonstračního příkladu, především na samotný obsah šablony je zřejmé, že použití řetězcových literálů nemusí být tím nejlepším způsobem, jak šablonu definovat a udržovat:
const ( templateFormat = `{{range .}}Jméno {{.Name}} {{.Surname}} Popularita {{.Popularity}} {{end}}` )
Mnohem praktičtější by bylo, aby šablona byla uložena v samostatném textovém souboru, který by mohl mít například tento obsah:
{{range .}}Jméno {{.Name}} {{.Surname}} Popularita {{.Popularity}} --- {{end}}
I to je pochopitelně možné, protože šablonu lze načíst z externího souboru metodou Template.ParseFiles. Tato metoda slouží k načtení (obecně) většího množství šablon, které se od sebe odlišují svým jménem, které je určeno (mimo jiné) i jménem souboru obsahujícího šablonu. V případě, že jméno šablony nebude nalezeno, nahlásí se chyba, což je ostatně ukázáno v dalším příkladu:
package main import ( "os" "text/template" ) const ( templateName = "test" templateFilename = "template13.txt" ) type Role struct { Name string Surname string Popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).ParseFiles(templateFilename)) println(tmpl) // 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) } }
Při pokusu o spuštění tohoto příkladu (tedy v runtime) dojde k následující chybě:
panic: template: test: "test" is an incomplete or empty template goroutine 1 [running]: main.main() /home/ptisnovs/src/go-root/article_79/template13.go:37 +0x272 exit status 2
Úplný zdrojový kód příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template13.go.
17. Korektní způsob načtení a použití šablony uložené v samostatném souboru
Nyní si ukažme korektní způsob načtení a použití šablony uložené v samostatném souboru. Šablonu budeme stále, stejně jako v předchozí kapitole, načítat metodou Template.ParseFiles, které předáme jméno souboru obsahujícího šablonu. Jméno šablony bude v tomto příkladu totožné se jménem tohoto souboru, tedy:
package main import ( "os" "text/template" ) const ( templateName = "template14.txt" templateFilename = "template14.txt" ) type Role struct { Name string Surname string Popularity int } func main() { // vytvoření nové šablony tmpl := template.Must(template.New(templateName).ParseFiles(templateFilename)) println(tmpl) // 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) } }
Po spuštění tohoto demonstračního příkladu by se na standardní výstup měly vypsat následující řádky:
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 příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template14.go.
18. Přímé volání konstruktoru ParseFiles
Ve všech předchozích demonstračních příkladech se objekt typu Template vytvářel konstruktorem template.New. První volanou metodou pak byla Parse, popř. ParseFiles:
// vytvoření nové šablony tmpl := template.Must(template.New(templateName).ParseFiles(templateFilename)) ... ... ... // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles)
Toto řešení je sice univerzální a umožňuje načtení většího množství šablon, na druhou stranu však vyžaduje práci se jménem šablony. V případě, že budeme chtít pracovat s jedinou šablonou a to navíc bez nutnosti uvádění jejího jména, lze použít určitou zkratku – namísto konstruktoru template.New přímo zavolat funkci template.ParseFiles (což je sice stejné jméno, jako u metody Template.ParseFiles, ale skutečně se jedná o „běžnou“ funkci). Kód se tak nepatrně zjednoduší a především – nebude nutné řešit další jméno, tj. potenciálně další stav aplikace:
// vytvoření nové šablony tmpl := template.Must(template.ParseFiles(templateFilename)) ... ... ... // aplikace šablony - přepis hodnot err := tmpl.Execute(os.Stdout, roles)
Úplný zdrojový kód tohoto demonstračního příkladu bude vypadat následovně:
package main import ( "os" "text/template" ) const ( templateFilename = "template15.txt" ) 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) } }
Opět se podívejme na výsledek získaný po spuštění tohoto příkladu:
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 příkladu z této kapitoly je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article79/template15.go.
19. Repositář s demonstračními příklady
Zdrojové kódy všech 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 |
20. Odkazy na Internetu
- 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