Поиск по сайту:

Как использовать шаблоны в Go


Введение

Вам нужно представить некоторые данные в виде хорошо отформатированного вывода, текстовых отчетов или HTML-страниц? Вы можете сделать это с помощью шаблонов Go. Любая программа Go может использовать пакет html/template — оба включены в стандартную библиотеку Go — для аккуратного представления данных.

Оба пакета позволяют вам писать текстовые шаблоны и передавать в них данные для отображения документа, отформатированного по вашему вкусу. В шаблонах вы можете перебирать данные и использовать условную логику, чтобы решить, какие элементы включить в документ и как они должны отображаться. В этом руководстве показано, как использовать оба пакета шаблонов. Сначала вы будете использовать text/template для преобразования некоторых данных в простой текстовый отчет с использованием циклов, условной логики и пользовательских функций. Затем вы будете использовать html/template для преобразования тех же данных в HTML-документ без внедрения кода.

Предпосылки

Прежде чем приступить к этому руководству, вам нужно только установить Go. Прочитайте правильный учебник для вашей операционной системы:

  • Как установить Go в Ubuntu 20.04
  • Как установить Go и настроить локальную среду программирования в macOS
  • Как установить Go и настроить локальную среду программирования в Windows 10

Вам также понадобятся практические знания языка Go, в том числе использование методов struct.

Давайте начнем.

Шаг 1 — Импорт текста/шаблона

Предположим, вы хотите создать простой отчет о некоторых имеющихся у вас данных о собаках. Вы хотите отобразить это так:

---
Name:  Jujube

Sex:   Female (spayed)

Age:   10 months

Breed: German Shepherd/Pitbull

---
Name:  Zephyr

Sex:   Male (intact)

Age:   13 years, 3 months

Breed: German Shepherd/Border Collie

Это отчет, который вы создадите с помощью пакета text/template. Выделенные элементы — это ваши данные, а остальное — статический текст из шаблона. Шаблоны живут либо в виде строк в вашем коде, либо в своих собственных файлах вместе с вашим кодом. Они содержат шаблонный статический текст, чередующийся с условными операторами (т. е. if/else), операторами управления потоком (т. е. циклами) и вызовами функций, заключенными в {{. . .}} маркеры. Вы передадите некоторые данные в свой шаблон, чтобы отобразить окончательный документ, подобный приведенному выше.

Чтобы начать, перейдите в рабочую область Go (go env GOPATH) и создайте новый каталог для этого проекта:

  1. cd `go env GOPATH`
  2. mkdir pets
  3. cd pets

Используя nano или ваш любимый текстовый редактор, откройте новый файл с именем pets.go и вставьте его в:

  1. nano pets.go
package main

import (
	"os"
	"text/template"
)

func main() {
}

Этот файл объявляет себя в пакете main и содержит функцию main, что означает, что его можно запустить с помощью go run. Он импортирует пакет стандартной библиотеки text/template, позволяющий писать и отображать шаблон, и os, который будет использоваться для печати на терминале.

Шаг 2 — Создание данных шаблона

Прежде чем писать шаблон, давайте создадим некоторые данные для передачи в шаблон. Под операторами import и перед main() определите структуру с именем Pet, содержащую поля для Name питомца. , Пол, независимо от того, стерилизовали ли животное или нет (Неповрежденный), Возраст и Порода. Отредактируйте pets.go и добавьте эту структуру:

. . .
type Pet struct {
	Name   string
	Sex    string
	Intact bool
	Age    string
	Breed  string
}
. . .

Теперь в теле функции main() создайте фрагмент Pet для хранения данных о двух собаках:

. . .
func main() {
	dogs := []Pet{
		{
			Name:   "Jujube",
			Sex:    "Female",
			Intact: false,
			Age:    "10 months",
			Breed:  "German Shepherd/Pitbull",
		},
		{
			Name:   "Zephyr",
			Sex:    "Male",
			Intact: true,
			Age:    "13 years, 3 months",
			Breed:  "German Shepherd/Border Collie",
		},
	}
} // end main

Эти данные будут переданы в ваш шаблон для отображения окончательного отчета. Конечно, данные, которые вы передаете своим шаблонам, могут поступать откуда угодно: из вашей базы данных, стороннего API и т. д. Для этого руководства проще всего просто вставить некоторые образцы данных в свой код.

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

Шаг 3 — Выполнение шаблона

На этом шаге вы увидите, как использовать text/template для создания готового документа из шаблона, но на самом деле вы не сможете написать полезный шаблон до шага 4.

Создайте пустой текстовый файл с именем pets.tmpl со статическим текстом:

Nothing here yet.

Сохраните шаблон и выйдите из редактора. Если вы используете nano, нажмите CTRL+X, затем Y и ENTER, чтобы подтвердить изменения.

Хотя при выполнении этого шаблона будет просто напечатано «Здесь пока ничего», давайте передадим ваши данные и выполним шаблон, чтобы убедиться, что text/template работает. Добавьте следующее в ваш main() после фрагмента dogs:

	. . .
	var tmplFile = “pets.tmpl”
	tmpl, err := template.New(tmplFile).ParseFiles(tmplFile)
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, dogs)
	if err != nil {
		panic(err)
	}
} // end main

В этом фрагменте кода вы используете Template.New для создания нового Template, а затем вызываете ParseFiles для получившегося шаблона для анализа вашего минимального файл шаблона. После проверки на наличие ошибок вы вызываете метод Execute нового шаблона, передаете os.Stdout для печати готового отчета на терминал, а также передаете свой фрагмент собак. В качестве первого аргумента вы можете передать все, что реализует интерфейс io.Writer, что означает, например, что вы можете записать свой отчет в файл. Мы увидим, как это сделать позже.

Полная программа должна выглядеть так:

package main

import (
	"os"
	"text/template"
)

type Pet struct {
	Name   string
	Sex    string
	Intact bool
	Age    string
	Breed  string
}

func main() {
	dogs := []Pet{
		{
			Name:   "Jujube",
			Sex:    "Female",
			Intact: false,
			Age:    "10 months",
			Breed:  "German Shepherd/Pitbull",
		},
		{
			Name:   "Zephyr",
			Sex:    "Male",
			Intact: true,
			Age:    "13 years, 3 months",
			Breed:  "German Shepherd/Border Collie",
		},
	}
	var tmplFile = “pets.tmpl”
	tmpl, err := template.New(tmplFile).ParseFiles(tmplFile)
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, dogs)
	if err != nil {
		panic(err)
	}
} // end main

Сохраните программу, затем запустите ее с помощью go run:

  1. go run pets.go
Output
Nothing here yet.

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

Шаг 4 — Написание шаблона

Шаблон — это всего лишь действия, инструкции для механизма шаблонов, которые сообщают ему, как обрабатывать переданные данные и что включить в вывод. Действия заключаются в пару открывающих и закрывающих двойных фигурных скобок — {{ }} — и они получают доступ к данным через курсор, обозначаемый точкой (.).

Данные, передаваемые в шаблон, могут быть абсолютно любыми, но обычно они передаются в виде среза, массива или карты — чего-то итерируемого. Давайте пройдемся по вашему фрагменту dogs с помощью шаблона.

Зацикливание на срезе

В коде Go вы можете использовать range в операторе открытия цикла for для перебора среза. В шаблонах вы используете действие range для той же цели, но у него другой синтаксис: нет for, но есть добавленный end, чтобы закрыть цикл.

Откройте pets.tmpl и замените его содержимое следующим:

{{ range . }}
---
(Pet will appear here...)
{{ end }}

Действие range принимает здесь один аргумент: курсор (.), который относится ко всему фрагменту dogs. Цикл замыкается с помощью {{ end }} внизу. В теле цикла вы печатаете какой-то статический текст и еще ничего о собаках.

Сохраните pets.tmpl и снова запустите pets.go:

  1. go run pets.go
Output
--- (Pet will appear here...) --- (Pet will appear here...)

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

Отображение поля

При передаче . в range в этом шаблоне точка относится ко всему фрагменту, но в каждой итерации цикла range точка относится к к текущему элементу в срезе. Это позволяет вам получить доступ к экспортированным полям каждого питомца, используя только голую точку. Вам не нужно ссылаться на индексы слайсов.

Отобразить поле так же просто, как заключить его в фигурные скобки и поставить перед ним точку. Откройте pets.tmpl и замените его содержимое следующим:

{{ range . }}
---
Name:  {{ .Name }}

Sex:   {{ .Sex }}

Age:   {{ .Age }}

Breed: {{ .Breed }}
{{ end }}

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

Сохраните и снова запустите программу:

  1. go run pets.go
Output
--- Name: Jujube Sex: Female Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male Age: 13 years, 3 months Breed: German Shepherd/Border Collie

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

Использование условий

Причина, по которой мы не включили поле Intact путем добавления {{ .Intact }} в шаблон, заключается в том, что это было бы не очень удобно для чтения. Представьте, если бы в вашем ветеринарном счете было указано Неповрежден: false в сводке о вашей собаке. Хотя может быть эффективным хранить это поле как логическое значение, а не как строку, а Intact — хорошее нейтральное по полу имя для этого поля, мы можем отобразить его по-другому в нашем окончательном отчете, используя if -другое действие.

Снова откройте pets.tmpl и добавьте выделенную часть, показанную здесь:

{{ range . }}
---
Name:  {{ .Name }}

Sex:   {{ .Sex }} ({{ if .Intact }}intact{{ else }}fixed{{ end }})

Age:   {{ .Age }}

Breed: {{ .Breed }}
{{ end }}

Шаблон теперь проверяет, является ли поле Intact истинным, и печатает (intact), если да, или (fixed), если нет. Но мы можем сделать лучше, чем это. Давайте еще раз отредактируем шаблон, чтобы напечатать термины, относящиеся к полу, для фиксированной собаки — стерилизованная или стерилизованная — вместо фиксированная. Добавьте вложенный if в исходный else:

{{ range . }}
---
Name:  {{ .Name }}

Sex:   {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})

Age:   {{ .Age }}

Breed: {{ .Breed }}
{{ end }}

Сохраните шаблон и запустите pets.go:

  1. go run pets.go
Output
--- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie

У нас есть две собаки, но три возможных случая отображения Intact. Давайте добавим еще одну собаку в раздел pets.go, чтобы охватить все три случая. Отредактируйте pets.go и добавьте к фрагменту третью собаку:

. . .
func main() {
	dogs := []Pet{
		{
			Name:   "Jujube",
			Sex:    "Female",
			Intact: false,
			Age:    "10 months",
			Breed:  "German Shepherd/Pitbull",
		},
		{
			Name:   "Zephyr",
			Sex:    "Male",
			Intact: true,
			Age:    "13 years, 3 months",
			Breed:  "German Shepherd/Border Collie",
		},
		{
			Name:	"Bruce Wayne",
			Sex:	"Male",
			Intact:	false,
			Age:	"3 years, 8 months",
			Breed:	"Chihuahua",
		},
	}
. . .

Теперь сохраните и запустите pets.go:

  1. go run pets.go
Output
--- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua

Отлично — выглядит так, как ожидалось.

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

Использование шаблонных функций

Наряду с eq существуют и другие функции для сравнения значений полей и возврата логического значения: gt (>), ne (!=), le (<=) и т. д. Вы можете вызывать эти функции — и любую функцию шаблона — одним из двух способов:

  1. Напишите имя функции, а затем один или несколько параметров с пробелом между ними. Вот как вы использовали eq выше: eq .Sex \Female.
  2. Сначала напишите один параметр, затем вертикальную черту (|), затем имя функции, а затем любые другие параметры. Это похоже на конвейер, когда выходные данные одного вызова передаются в качестве входных данных для следующего вызова и т. д.

Таким образом, хотя сравнение eq в вашем шаблоне записывается как eq .Sex \Female, его также можно записать как .Sex | eq \Female . Эти два выражения эквивалентны.

Давайте воспользуемся функцией len, чтобы отобразить количество собак в верхней части отчета. Откройте pets.tmpl и добавьте сверху следующее:

Number of dogs: {{ . | len -}}

{{ range . }}
. . .

Вы также могли написать {{ len . -}}.

Обратите внимание на тире (-) рядом с закрывающими двойными фигурными скобками. Это предотвращает печать новой строки (\n) после действия. Вы также можете добавить дефис к открывающей двойной фигурной скобке ({{-), чтобы скрыть новую строку перед действием.

Сохраните шаблон и запустите pets.go:

  1. go run pets.go
Output
Number of dogs: 3 --- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd & Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd & Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua

Из-за дефиса в {{ . | len -}} между меткой Количество собак и первой собакой нет пустых строк.

Вы могли заметить, что список встроенных функций в документации text/template довольно мал. Хорошая новость заключается в том, что вы можете сделать любую функцию Go доступной для ваших шаблонов, если она возвращает либо одно значение, либо два значения, если второе имеет тип error.

Использование функций Go в шаблонах

Предположим, вы хотите написать шаблон, который берет часть собак и печатает только последнюю. Вы можете получить подмножество фрагмента в шаблонах с помощью встроенной функции slice, которая работает аналогично mySlice[x:y] в Go. Вы можете написать {{ slice . 2 }} для получения последнего элемента среза из трех элементов, хотя функция slice возвращает другой срез, а не элемент. То есть {{ slice . 2 }} эквивалентен slice[2:], а не slice[2]. (Функция также может принимать более одного индекса, например, {{ slice . 0 2 }} для получения среза slice[0:2], но не будет использовать что здесь.)

Но как вы можете сослаться на последний индекс фрагмента в вашем шаблоне? Функция len доступна, но последний элемент в срезе имеет индекс len - 1, и, к сожалению, в шаблонах нельзя выполнять арифметические действия.

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

Но прежде чем мы это сделаем, давайте создадим новый шаблон. Откройте новый файл lastPet.tmpl и вставьте в него следующее:

{{- range (len . | dec | slice . ) }}
---
Name:  {{ .Name }}

Sex:   {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }})

Age:   {{ .Age }}

Breed: {{ .Breed }}
{{ end -}}

Вызов функции dec в первой строке ссылается на пользовательскую функцию, которую вы собираетесь определить и передать в шаблон. В функции main() в pets.go внесите следующие изменения (выделены) под фрагментом dogs и перед вызовом tmpl.Execute():

	. . .
	funcMap := template.FuncMap{
		"dec": func(i int) int { return i - 1 },
	}
	var tmplFile = “lastPet.tmpl”
	tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile)
	if err != nil {
		panic(err)
	}
	. . .

Во-первых, вы объявляете FuncMap, представляющий собой карту функций: ключи — это имена функций, доступные для ваших шаблонов, а значения — это сами функции. Ваша единственная функция здесь, dec, является анонимной встроенной функцией, потому что она очень короткая. Он принимает целое число, вычитает из него единицу и возвращает результат.

Затем вы меняете имя файла шаблона. Наконец, вы вставляете вызов Template.Funcs перед вызовом ParseFile, передавая ему только что определенный вами funcMap. Метод Funcs должен вызываться до ParseFiles.

Перед запуском кода давайте разберемся, что происходит в действии range в вашем шаблоне:

{{- range (len . | dec | slice . ) }}

Вы получаете длину слайса dogs, передаете его пользовательской функции dec для вычитания единицы, а затем передаете его в качестве второго параметра в slice обсуждалась ранее. Таким образом, для среза с тремя собаками действие range эквивалентно {{- range (slice . 2) }}.

Сохраните pets.go и запустите его:

go run pets.go
Output
--- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua

Выглядит неплохо. Что, если вы хотите показать двух последних собак, а не только последнюю? Отредактируйте lastPet.tmpl и добавьте еще один вызов dec в конвейер:

{{- range (len . | dec | dec | slice . ) }}
. . .

Сохраните файл и снова запустите pets.go:

go run pets.go
Output
--- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua

Вероятно, вы можете себе представить, как можно улучшить функцию dec, заставив ее принимать параметр и изменив ее имя, чтобы вы могли вызывать minus 2 вместо dec | дек.

Теперь предположим, что вы хотели по-другому отобразить собак смешанной породы, таких как Zephyr, заменив косую черту на амперсанд. Для этого вам не нужно писать собственную функцию, потому что она есть в пакете strings, и вы можете позаимствовать функцию из любого пакета для использования в своих шаблонах. Отредактируйте pets.go, чтобы импортировать пакет strings и добавить одну из его функций в funcMap:

package main

import (
	"os"
	"strings"
	"text/template"
)
. . .
func main() {
	. . .
	funcMap := template.FuncMap{
		"dec":     func(i int) int { return i - 1 },
		"replace": strings.ReplaceAll,
	}
	. . .
} // end main

Теперь вы импортируете пакет strings и добавляете его функцию ReplaceAll в ваш funcMap под именем replace. Теперь отредактируйте lastPet.tmpl, чтобы использовать эту функцию:

{{- range (len . | dec | dec | slice . ) }}
---
Name:  {{ .Name }}

Sex:   {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }})

Age:   {{ .Age }}

Breed: {{ replace .Breed “/” “ & ” }}
{{ end -}}

Сохраните файл и запустите его еще раз:

  1. go run pets.go
Output
--- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd & Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua

Порода Зефира теперь содержит амперсанд вместо косой черты.

Вы могли бы манипулировать этой строкой в pets.go вместо своего шаблона, но представление данных — это задача шаблонов, а не кода.

На самом деле, некоторые данные о собаках уже содержат некоторую презентацию, а может быть, и не должны. Поле Breed объединяет несколько пород в одну строку, разделяя их косой чертой. Этот однострочный шаблон может предлагать вводящим данные вводить в базу данных различные форматы: лабрадор/пудель, лабрадор и пудель, лабрадор, пудель, Помесь лабрадора и пуделя и т. д. Может быть лучше хранить Порода в виде фрагмента строк ([]string) вместо строки для избежать этой двусмысленности формата, сделать поиск по породе более гибким и упростить его представление. Затем вы можете использовать функцию strings.Join в своих шаблонах для печати всех пород, а также дополнительное примечание в поле .Breed ((чистокровный) или (метис)).

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

. . .
type Pet struct {
	Name   string
	Sex    string
	Intact bool
	Age    string
	Breed  []string
}

func main() {
	dogs := []Pet{
		{
			Name:   "Jujube",
			. . .
			Breed:  []string{"German Shepherd", "Pit Bull"},
		},
		{
			Name:   "Zephyr",
			. . .
			Breed:  []string{"German Shepherd", "Border Collie"},
		},
		{
			Name:   "Bruce Wayne",
			. . .
			Breed:  []string{"Chihuahua"},
		},
	}
	funcMap := template.FuncMap{
		"dec":     func(i int) int { return i - 1 },
		"replace": strings.ReplaceAll,
		"join":    strings.Join,
	}
	. . .
} // end main
{{- range (len . | dec | dec | slice . ) }}
---
Name:  {{ .Name }}

Sex:   {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }})

Age:   {{ .Age }}

Breed: {{ join .Breed " & " }} ({{ if len .Breed | eq 1 }}purebred{{ else }}mixed breed{{ end }})
{{ end -}}

В заключение давайте представим те же самые данные о собаке в HTML-документ и посмотрим, почему вы всегда должны использовать пакет html/template, когда выходные данные ваших шаблонов представляют собой HTML.

Шаг 5 — Написание HTML-шаблона

Инструмент командной строки может использовать text/template для аккуратной печати вывода, а некоторые другие пакетные программы могут использовать его для создания хорошо структурированных файлов из некоторых данных. Но шаблоны Go также обычно используются для отображения HTML-страниц для веб-приложений. Например, популярный генератор статических сайтов с открытым исходным кодом Hugo использует как text/template, так и html/template в качестве основы для своих шаблонов.

HTML имеет несколько особых проблем, которых нет у обычного текста. Он использует угловые скобки для переноса элементов (<td>), амперсанд для обозначения объектов ( ) и символы кавычек для переноса атрибутов тега. Если какие-либо данные, вставляемые вашим шаблоном, содержат эти символы, использование пакета text/template может привести к искаженный HTML или, что еще хуже, внедрение кода.

С пакетом html/template дело обстоит иначе. Этот пакет экранирует эти проблемные символы, вставляя их безопасные HTML-эквиваленты (сущности). Амперсанд в ваших данных становится &, левая угловая скобка становится < и так далее.

Давайте воспользуемся теми же данными о собаке, что и ранее, для создания HTML-документа, но сначала продолжим использовать text/template, чтобы показать его опасность.

Откройте pets.go и добавьте следующий выделенный текст в поле Name Jujube:

        . . .
	dogs := []Pet{
		{
			Name:   "<script>alert(\"Gotcha!\");</script>Jujube",
			Sex:    "Female",
			Intact: false,
			Age:    "10 months",
			Breed:  "German Shepherd/Pit Bull",
		},
		{
			Name:   "Zephyr",
			Sex:    "Male",
			Intact: true,
			Age:    "13 years, 3 months",
			Breed:  "German Shepherd/Border Collie",
		},
		{
			Name:   "Bruce Wayne",
			Sex:    "Male",
			Intact: false,
			Age:    "3 years, 8 months",
			Breed:  "Chihuahua",
		},
	}
        . . .

Теперь создайте шаблон HTML в новом файле с именем petsHtml.tmpl:

<p><strong>Pets:</strong> {{ . | len }}</p>
{{ range . }}
<hr />
<dl>
	<dt>Name</dt>
	<dd>{{ .Name }}</dd>
	<dt>Sex</dt>
	<dd>{{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})</dd>
	<dt>Age</dt>
	<dd>{{ .Age }}</dd>
	<dt>Breed</dt>
	<dd>{{ replace .Breed “/” “ & ” }}</dd>
</dl>
{{ end }}

Сохраните HTML-шаблон. Перед запуском pets.go нам нужно отредактировать переменную tmpFile, но давайте также отредактируем программу для вывода шаблона в файл, а не в терминал. Откройте pets.go и добавьте выделенный код в функцию main():

	. . .
	funcMap := template.FuncMap{
		"dec":     func(i int) int { return i - 1 },
		"replace": strings.ReplaceAll,
	}
	var tmplFile = "petsHtml.tmpl"
	tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile)
	if err != nil {
		panic(err)
	}
	var f *os.File
	f, err = os.Create("pets.html")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(f, dogs)
	if err != nil {
		panic(err)
	}
	err = f.Close()
	if err != nil {
		panic(err)
	}
} // end main

Вы открываете новый файл с именем pets.html и передаете его (вместо os.Stdout) в tmpl.Execute, а затем закрыть файл по завершении.

Теперь запустите go run pets.go, чтобы сгенерировать HTML-файл. Затем откройте эту локальную веб-страницу в браузере:

Браузер запустил введенный скрипт. Вот почему вам никогда не следует использовать пакет text/template для создания HTML, особенно если вы не можете полностью доверять источнику данных ваших шаблонов.

Помимо экранирования символов HTML в данных, пакет html/template работает точно так же, как text/template и имеет то же базовое имя (template), Это означает, что все, что вам нужно сделать, чтобы сделать ваш шаблон безопасным для внедрения, это заменить импорт text/template на html/template. Отредактируйте pets.go и сделайте это прямо сейчас:

package main

import (
	"os"
	"strings"
	"html/template"
)
. . .

Сохраните файл и запустите его в последний раз, перезаписав pets.html. Затем обновите HTML-файл в браузере:

Пакет html/template отобразил внедрённый скрипт как просто текст на веб-странице. Откройте pets.html в текстовом редакторе (или просмотрите исходный код страницы в браузере) и посмотрите на первую собаку Jujube:

. . .
<dl>
        <dt>Name</dt>
        <dd>&lt;script&gt;alert(&#34;Gotcha!&#34;);&lt;/script&gt;Jujube</dd>
        <dt>Sex</dt>
        <dd>Female (spayed)</dd>
        <dt>Age</dt>
        <dd>10 months</dd>
        <dt>Breed</dt>
        <dd>German Shepherd &amp; Pit Bull</dd>
</dl>
. . .

Пакет html заменил угловые скобки и кавычки в имени Jujube, а также амперсанд в породе.

Заключение

Go Templates — удобный инструмент для обтекания любым текстом любых данных. Вы можете использовать их в инструментах командной строки для форматирования вывода, в веб-приложениях для рендеринга HTML и т. д. В этом руководстве вы использовали встроенные пакеты шаблонов Go для печати хорошо отформатированного текста и HTML-страницы из одних и тех же данных. Чтобы узнать больше о том, как использовать эти пакеты, ознакомьтесь с документацией по html/template.