Пакет текста/шаблона стандартной библиотеки языка Go, объясненный простым языком

Go

содержание

Официальное определение:

Package template implements data-driven templates for generating textual output.

Пакет шаблонов — это управляемый данными шаблон вывода текста, который фактически заполняет данные в письменном шаблоне.

шаблон

Что такое шаблон?

Вот простой пример шаблона:

// 模板定义
tepl := "My name is {{ . }}"

// 解析模板
tmpl, err := template.New("test").Parse(tepl)

// 数据驱动模板
data := "jack"
err = tmpl.Execute(os.Stdout, data)

период между {{ и }}.Представляет данные, переданные в шаблон, и отображает различное содержимое в соответствии с входящими данными.

.Может представлять любой тип на языке go, например, структуры, хэши и т. д.

Что касается содержимого пакетов {{ и }}, то они в совокупности называются действиями, которые делятся на два типа:

  • оценки данных
  • структуры управления

Результат оценки действия будет скопирован непосредственно в шаблон.Структура управления аналогична структуре программы Go, которую мы написали, включая условные операторы, операторы цикла, переменные, вызовы функций и т. д.

После того, как шаблон успешно проанализирован (Parse), его можно безопасно использовать в параллельной среде, если вывод осуществляется в тот жеio.WriterДанные могут перекрываться (поскольку порядок параллельного выполнения не гарантируется).

Actions

В шаблоне не так много действий, рассмотрим их по порядку.

Примечания

{{/* comment */}}

Обрезать пробелы

// 裁剪 content 前后的空格
{{- content -}}

// 裁剪 content 前面的空格
{{- content }}

// 裁剪 content 后面的空格
{{ content -}}

вывод текста

{{ pipeline }}

Данные, представленные конвейером, будут сгенерированы и вызваныfmt.PrintФункционально-подобный вывод, такой как целочисленный тип 3, будет преобразован в вывод строки "3".

Условные операторы

{{ if pipeline }} T1 {{ end }}

{{ if pipeline }} T1 {{ else }} T0 {{ end }}

{{ if pipeline }} T1 {{ else if pipeline }} T0 {{ end }}

// 上面的语法其实是下面的简写
{{ if pipeline }} T1 {{ else }}{{ if pipeline }} T0 { {end }}{{ end }}

{{ if pipeline }} T1 {{ else if pipeline }} T2 {{ else }} T0 {{ end }}

Если значение конвейера пусто, T1 не будет выведено, иначе будет выведено T1.

Нулевые значения — это false, 0, произвольные нулевые указатели, значения интерфейса, массивы, срезы, словари и пустые строки.""(строка длины 0).

оператор цикла

{{ range pipeline }} T1 {{ end }}

// 这个 else 比较有意思,如果 pipeline 的长度为 0 则输出 else 中的内容
{{ range pipeline }} T1 {{ else }} T0 {{ end }}

// 获取容器的下标
{{ range $index, $value := pipeline }} T1 {{ end }}

Значение конвейера должно быть одним из массива, среза, словаря и канала, то есть значением итерируемого типа, и выводить несколько T1 в соответствии с длиной значения.

define

{{ define "name" }} T {{ end }}

Определите шаблон с именем name.

template

{{ template "name" }}

{{ template "name" pipeline }}

Относится к шаблону с именем name.

block

{{ block "name" pipeline }} T1 {{ end }}

Семантика блока заключается в том, что если есть шаблон с именем name, на него будут ссылаться и он будет выполнен, а если нет шаблона с именем name, он выполнит содержимое, определенное им самим.

То есть делается еще один шаг, чтобы определить, существует ли шаблон, и в соответствии с этим результатом отрисовывается другой контент.

with

{{ with pipeline }} T1 {{ end }}

// 如果 pipeline 是空值则输出 T0
{{ with pipeline }} T1 {{ else }} T0 {{ end }}

{{ with arg }}
    . // 此时 . 就是 arg
{{ end }}

with создает новый контекст, в котором.снаружи.Это не имеет значения.

параметр

Значение параметра имеет несколько представлений и может оценивать любой тип, включая функции, указатели (указатели автоматически косвенно оцениваются до исходного значения):

  • Логические значения, строки, символы, числа с плавающей запятой, комплексные числа ведут себя как Go
  • ключевые словаnilПредставляет на языке gonil
  • Период символа представляет собой результат значения
  • Переменные, начинающиеся с символа $, являются соответствующими значениями переменной
  • Поля структуры представлены в виде.Field, результатом является значение Field, которое поддерживает связанные вызовы.Field1.Field2
  • Ключ словаря представлен как.KeyРезультатом является значение, соответствующее ключу
  • Если это метод в наборе методов структуры .Method Результатом является значение вызова метода с точкой в ​​качестве получателя **
    • Метод либо имеет только одно возвращаемое значение любого типа, либо второе возвращаемое значение является ошибкой, не более того, если ошибка не равна нулю, он напрямую сообщит об ошибке и остановит рендеринг шаблона.
    • Результат вызова метода может продолжаться в цепочке.Field1.Key1.Method1.Field2.Key2.Method2
    • Объявление набора переменных методов также может быть вызвано$x.Method1.Field
    • Групповые вызовы в скобкахprint (.Func1 arg1) (.Func2 arg2)или(.StructValuedMethod "arg").Field

Пожалуй, самое сложное для понимания здесь — это способ вызова функции.Если вы обращаетесь к функции в наборе методов структуры и к функции в поле, какая разница в поведении на данный момент?

Напишите демо для проверки:

type T struct {
	Add func(int) int
}

func (t *T) Sub(i int) int {
	log.Println("get argument i:", i)
	return i - 1
}

func arguments() {
	ts := &T{
		Add: func(i int) int {
			return i + 1
		},
	}
	tpl := `
		// 只能使用 call 调用
		call field func Add: {{ call .ts.Add .y }}
		// 直接传入 .y 调用
		call method func Sub: {{ .ts.Sub .y }}
	`
	t, _ := template.New("test").Parse(tpl)
	t.Execute(os.Stdout, map[string]interface{}{
		"y": 3,
		"ts": ts,
	})
}

output:

call field func Add: 4
call method func Sub: 2

Можно сделать вывод, что если функция является функциональным полем в структуре, функция не будет вызываться автоматически, можно использовать только встроенные функцииcallперечислить.

Если функция является методом в наборе методов структуры, метод будет вызываться автоматически, а возвращаемое значение будет присвоено., если функция возвращает новую структуру, карту, вы можете продолжить цепочку вызовов.

Переменная

Конвейер в экшене может инициализировать переменную для сохранения результата, и синтаксис тоже очень простой:

$variable = pipeline

В этот момент действие объявляет переменную, не производя никакого вывода.

Цикл диапазона может объявить две переменные:

range $index, $element := pipeline

В if, with и range область действия переменной распространяется на то место, где находится {{ end }}.

Если это не управляющая структура, область объявленной переменной распространяется на весь шаблон.

Например, чтобы объявить переменную в начале шаблона:

{{ $pages := .pagination.Pages }}
{{ $current := .pagination.Current }}

Когда начинается рендеринг,$переменная будет заменена на.начальное значение, например$pagesбудет заменен на.pagenation.Pages. Поэтому взаимные ссылки между шаблонами не передают переменные, а переменные работают только в определенной области.

функция

Рендеринг шаблона ищет функции в двух местах:

  • пользовательская карта функций
  • глобальная карта функций, эти функции являются встроенными шаблонами

Использование пользовательских функцийfunc (t *Template) Funcs(funcMap FuncMap) *Templateрегистр.

Список глобальных функций:

and

Возвращает результат булевой операции между параметрами, которая на самом деле является логическим оператором в JavaScript.&&, возвращает первое значение, которое можно преобразовать в false , которое в Go равно нулю, и возвращает последнее значение, если оба значения верны.

tpl := "{{ and .x .y .z }}"
t, _ := template.New("test").Parse(tpl)
t.Execute(os.Stdout, map[string]interface{}{
    "x": 1,
    "y": 0,
    "z": 3,
})

output:

0

or

логический оператор||, возвращает первое значение, которое можно преобразовать в значение true, которое в Go является ненулевым значением, и возвращает последнее значение, если оба значения ложны.

tpl := "{{ or .x .y .z }}"
t, _ := template.New("test").Parse(tpl)
t.Execute(os.Stdout, map[string]interface{}{
    "x": 1,
    "y": 0,
    "z": 3,
})

output:

1

call

Возвращает результат вызова первого параметра функции, функция должна иметь одно или два возвращаемых значения (второе возвращаемое значение должно быть ошибкой, если значение не равно нулю, рендеринг шаблона остановится)

tpl := "call: {{ call .x .y .z }} \n"
t, _ := template.New("test").Parse(tpl)
t.Execute(os.Stdout, map[string]interface{}{
    "x": func(x, y int) int { return x+y},
    "y": 2,
    "z": 3,
})

output:

5

html

Возвращает экранированную строку HTML, эту функцию нельзя использовать вhtml/templateиспользуется в.

js

Возвращает экранированную строку JavaScript.

index

Используется, когда первым параметром является массив, срез или карта, и возвращает значение соответствующего нижнего индекса.

index x 1 2 3равныйx[1][2][3].

len

Возвращает длину составного типа.

not

Возвращает инверсию параметра логического типа.

print

равныйfmt.Sprint.

printf

равныйfmt.Sprintf.

println

равныйfmt.Sprintln.

urlquery

URL-запрос экранирует строку, а не вhtml/templateиспользуется в упаковке.

// URLQueryEscaper returns the escaped value of the textual representation of
// its arguments in a form suitable for embedding in a URL query.
func URLQueryEscaper(args ...interface{}) string {
	return url.QueryEscape(evalArgs(args))
}

Вы можете видеть из исходного кода, что эта функция вызывается напрямуюurl.QueryEscapeВ экранировании строк нет ничего загадочного.

функция сравнения

  • eq: ==
  • ge: >=
  • gt: >
  • le: <=
  • lt: <
  • ne: !=

Проанализируйте два исходных кода:

// eq evaluates the comparison a == b || a == c || ...
func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
	v1 := indirectInterface(arg1)
	k1, err := basicKind(v1)
	if err != nil {
		return false, err
	}
	if len(arg2) == 0 {
		return false, errNoComparison
	}
	for _, arg := range arg2 {
		v2 := indirectInterface(arg)
		k2, err := basicKind(v2)
		if err != nil {
			return false, err
		}
		truth := false
		if k1 != k2 {
			// Special case: Can compare integer values regardless of type's sign.
			switch {
			case k1 == intKind && k2 == uintKind:
				truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
			case k1 == uintKind && k2 == intKind:
				truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
			default:
				return false, errBadComparison
			}
		} else {
			switch k1 {
			case boolKind:
				truth = v1.Bool() == v2.Bool()
			case complexKind:
				truth = v1.Complex() == v2.Complex()
			case floatKind:
				truth = v1.Float() == v2.Float()
			case intKind:
				truth = v1.Int() == v2.Int()
			case stringKind:
				truth = v1.String() == v2.String()
			case uintKind:
				truth = v1.Uint() == v2.Uint()
			default:
				panic("invalid kind")
			}
		}
		if truth {
			return true, nil
		}
	}
	return false, nil
}

// ne evaluates the comparison a != b.
func ne(arg1, arg2 reflect.Value) (bool, error) {
	// != is the inverse of ==.
	equal, err := eq(arg1, arg2)
	return !equal, err
}

eq сначала определяет, равны ли типы интерфейсов, а потом определяет, равны ли значения, в этом нет ничего особенного.

ne — это простой вызов eq, затем инвертируется.

ge, gt, le и lt аналогичны экв. Сначала оценивается тип, а затем оценивается размер.

Вложенные шаблоны

Вот более сложный пример:

// 加载模板
template.ParseFiles("templates/")

// 加载多个模板到一个命名空间(同一个命名空间的模块可以互相引用)
template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")

// must 加载失败时 panic
tmpl := template.Must(template.ParseFiles("layout.html"))

// 执行加载后的模板文件,默认执行第一个
tmpl.Execute(w, "test")

// 如果 tmpl 中有很多个模板,可以指定要执行的模板名
tmpl.ExecuteTemplate(w, "layout", "Hello world")

ExecuteTemplateУказанное имя находится в файле шаблонаdefine "name"имя.

Суммировать

Parseсерийная функция инициализированаTemplateТип экземпляр.

ExecuteРяд функций передает данные в шаблон для отображения окончательной строки.

Шаблоны по сутиParseФункция для загрузки нескольких файлов в одинTempalteВ экземпляре типа ключевое слово define в файле синтаксического анализа регистрирует именованный шаблон, который можно использовать между именованными шаблонами.templateссылаться друг на друга,ExecuteПередайте соответствующий рендеринг данных.