09.04.2020 шаблоны html text исходники
Пакеты text/template, html/template являются частью стандартной библиотеки GO.
Шаблоны GO используются во многих решениях на базе языка, таких как docker, kubernetes,
helm. С ними интегрированы также многие web-библиотеки, например echo. Понимание синтаксиса встроенного шаблонизатора GO полезно в очень многих задачах.
Данная статья содержит адаптированные материалы из документации к пакету text/template и несколько решений из
опыта автора. После описания стандартных возможностей, для более глубокого понимания рассмотрим исходники
пакетов text/template и html/template.
Шаблоны GO являются активными, что позволяет использовать в них управляющие конструкции,
такие как if, else, циклы range.
Не смотря на четкую типизацию языка GO, шаблонизатор работает с любыми данными,
достигается это использованием пакета reflect.
Все инструкции шаблона заключаются в символы {{ и }}.
Текст вне этих символов является не вызовами шаблонизатора, а простым текстом.
Простой текст копируется из шаблона в вывод без какого-либо изменения.
Для запуска шаблона со стороны GO-кода у нас есть функции Execute и ExecuteTemplate.
В них обоих есть параметр data interface{}.
Execute(wr io.Writer, data interface{}) error ExecuteTemplate(wr io.Writer, name string, data interface{}) error
Здесь данные, переданные через параметр data — те, с которыми шаблон работает по умолчанию.
Из шаблона они доступны как ..
Для вывода текущих данных достаточно следующего:
{{ . }}
Будем называть данные, доступные через ., текущим контекстом шаблона.
Некоторые конструкции изменяют этот контекст.
Далее рассмотрим синтаксические компоненты шаблонизатора GO.
{{/* комментарий */}}
{{if condition}} T1 {{end}}
Если condition будет равен 0, "", nil,
или пустым массивом, срезом, это будет воспринято
как false. В остальных случаях — инструкция T1 выполнится.
Допустимы вариации с else, else if:
{{if condition}} T1 {{else}} T0 {{end}} {{if condition1}} T1 {{else if condition2}} T0 {{end}}
{{range somelist}} T1 {{end}}
Итерировать в цикле можно массивы, срезы, карты и каналы. На каждую итерацию будет вызвана инструкция T1, при этом контекст шаблона будет переключен на элемент конкретной итерации.
Также возможна вариация с else, который выполнится если somelist пуст:
{{range somelist}} T1 {{else}} T2 {{end}}
Можно получить ключи и значения каждого элемента в переменные:
{{range $key, $value := somelist}} {{ $key }}: {{ $value }} {{end}}
{{with pipeline}} T1 {{end}}
В данном случае к pipeline применяется проверка, будто бы мы используем if.
Если значение pipeline будет эквивалентно true (cм описание if),
инструкция T1 выполнится. При этом текущий контекст этой инструкции изменится на pipeline.
Шаблон задать можно следующими двумя конструкциями:
{{block "name"}} T1 {{end}}
{{define "name"}} T1 {{end}}
Запустить шаблон c данными context:
{{template "name" context}}
Шаблоны GO могут выводить различные значения.
Наример, поля структуры или значения их мапы.
Поля структуры для использования в шаблоне должны быть экспотируемы (начинаться с большой буквы).
Ключи мапы могут начинаться с буквы любого регистра.
Можно выстраивать цепочки их получения:
.Field1.Field2.key1
.key1.key2
Можно использовать вызовы методов. Функция или метод для использования в шаблоне должны удовлетворять одному из условий:
error;В исходниках пакета text/template
присуствует код проверки вызываемых функций/методов на соответствие этим условиям.
Следующий код на GO подготавливает метод для вызова в шаблоне:
type myType struct{} func(m *myType) Method() string { return "123" }
Код шаблона вызывает метод:
.Method()
В шаблонизаторе GO два типа функций — встроенные и пользовательские.
Вызов функции любого типа выглядит так:
funcname arg1 arg2 arg3
Список стандартных функций:
call funcLocation arg1 arg2 — встроенная функция call позволяет вызывать другие функции,
например функции, хранящиеся в переменных или чье имя находится в переменной;index x 1 2 3 — получение элемента среза/массива/мапы;slice x 1 2 — получение нового среза из среза, аналогично s[1:2];len x — получение длины среза/массива/мапы;print, printf, println — явный вывод данных;Булевы операторы в шаблонах также реализованы как функции:
eq arg1 arg2 — arg1 == arg2
ne arg1 arg2 — arg1 != arg2
lt arg1 arg2 — arg1 < arg2
le arg1 arg2 — arg1 <= arg2
gt arg1 arg2 — arg1 > arg2
ge arg1 arg2 — arg1 >= arg2
Значения могут быть переданы в функцию с помощью оператора |.
Такие значения становятся последними аргументами в вызове функции:
{{"output" | printf "%q"}}
Можно определить функцию в коде на GO и затем использовать ее в шаблонах.
Ниже приведен GO-код функции last,
которая помогает определить, является ли элемент последним в списке:
tempTemplate := template.New("main").Funcs( template.FuncMap{ "last": func(x int, a interface{}) bool { return x == reflect.ValueOf(a).Num()-1 }, })
Использование функции last в шаблоне:
{{ $allKeywords := .Data.Keywords }} {{ range $k,$v := .Data.Keywords}} {{ $v }}{{ if ne (last $i $allKeywords) }},{{ end }} {{ end }}
Шаблоны GO используют пакет reflect.
Например, конструкция
range релизована следующим образом в text/template:
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { // ... switch val.Kind() { case reflect.Array, reflect.Slice: // ... case reflect.Map: // ... case reflect.Chan: // ... }
Для каждой конструкции шаблонов написана реализация, которая работает с любым стандартным
типом данных через пакет reflect,
например так работает получение поля структуры.
html/template во многом использует text/template.
Особенностью html/template является то, что шаблонизатор понимает,
какой именно html-контент в данный момент выводится шаблоном (html-тэг, атрибут, тело тэга, css-контент, URL)
и исходя из этого применяет разные способы экранирования, не ломая при этом внешнюю структуру html,
что является хорошим преимуществом данного шаблонизатора.