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,
что является хорошим преимуществом данного шаблонизатора.