blob: 7eba716f1b6648c2272213e9c6a1558108fd85a6 [file] [log] [blame]
Brent Austinba3052e2015-04-21 16:08:23 -07001// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package template
6
7import (
8 "fmt"
9 "io"
Colin Cross1f805522021-05-14 11:10:59 -070010 "io/fs"
11 "os"
12 "path"
Brent Austinba3052e2015-04-21 16:08:23 -070013 "path/filepath"
14 "sync"
15 "text/template"
16 "text/template/parse"
17)
18
19// Template is a specialized Template from "text/template" that produces a safe
20// HTML document fragment.
21type Template struct {
Dan Willemsen38f2dba2016-07-08 14:54:35 -070022 // Sticky error if escaping fails, or escapeOK if succeeded.
Brent Austinba3052e2015-04-21 16:08:23 -070023 escapeErr error
24 // We could embed the text/template field, but it's safer not to because
25 // we need to keep our version of the name space and the underlying
26 // template's in sync.
27 text *template.Template
28 // The underlying template's parse tree, updated to be HTML-safe.
29 Tree *parse.Tree
30 *nameSpace // common to all associated templates
31}
32
33// escapeOK is a sentinel value used to indicate valid escaping.
34var escapeOK = fmt.Errorf("template escaped correctly")
35
36// nameSpace is the data structure shared by all templates in an association.
37type nameSpace struct {
Dan Willemsenebae3022017-01-13 23:01:08 -080038 mu sync.Mutex
39 set map[string]*Template
40 escaped bool
Dan Willemsend2797482017-07-26 13:13:13 -070041 esc escaper
Brent Austinba3052e2015-04-21 16:08:23 -070042}
43
44// Templates returns a slice of the templates associated with t, including t
45// itself.
46func (t *Template) Templates() []*Template {
47 ns := t.nameSpace
48 ns.mu.Lock()
49 defer ns.mu.Unlock()
50 // Return a slice so we don't expose the map.
51 m := make([]*Template, 0, len(ns.set))
52 for _, v := range ns.set {
53 m = append(m, v)
54 }
55 return m
56}
57
Dan Willemsen09eb3b12015-09-16 14:34:17 -070058// Option sets options for the template. Options are described by
59// strings, either a simple string or "key=value". There can be at
60// most one equals sign in an option string. If the option string
61// is unrecognized or otherwise invalid, Option panics.
62//
63// Known options:
64//
65// missingkey: Control the behavior during execution if a map is
66// indexed with a key that is not present in the map.
67// "missingkey=default" or "missingkey=invalid"
68// The default behavior: Do nothing and continue execution.
69// If printed, the result of the index operation is the string
70// "<no value>".
71// "missingkey=zero"
72// The operation returns the zero value for the map type's element.
73// "missingkey=error"
74// Execution stops immediately with an error.
75//
76func (t *Template) Option(opt ...string) *Template {
77 t.text.Option(opt...)
78 return t
79}
80
Dan Willemsenebae3022017-01-13 23:01:08 -080081// checkCanParse checks whether it is OK to parse templates.
82// If not, it returns an error.
83func (t *Template) checkCanParse() error {
84 if t == nil {
85 return nil
86 }
87 t.nameSpace.mu.Lock()
88 defer t.nameSpace.mu.Unlock()
89 if t.nameSpace.escaped {
90 return fmt.Errorf("html/template: cannot Parse after Execute")
91 }
92 return nil
93}
94
Brent Austinba3052e2015-04-21 16:08:23 -070095// escape escapes all associated templates.
96func (t *Template) escape() error {
97 t.nameSpace.mu.Lock()
98 defer t.nameSpace.mu.Unlock()
Dan Willemsenebae3022017-01-13 23:01:08 -080099 t.nameSpace.escaped = true
Brent Austinba3052e2015-04-21 16:08:23 -0700100 if t.escapeErr == nil {
Dan Willemsen09eb3b12015-09-16 14:34:17 -0700101 if t.Tree == nil {
Dan Willemsenebae3022017-01-13 23:01:08 -0800102 return fmt.Errorf("template: %q is an incomplete or empty template", t.Name())
Dan Willemsen09eb3b12015-09-16 14:34:17 -0700103 }
Brent Austinba3052e2015-04-21 16:08:23 -0700104 if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil {
105 return err
106 }
107 } else if t.escapeErr != escapeOK {
108 return t.escapeErr
109 }
110 return nil
111}
112
113// Execute applies a parsed template to the specified data object,
114// writing the output to wr.
115// If an error occurs executing the template or writing its output,
116// execution stops, but partial results may already have been written to
117// the output writer.
Dan Willemsend2797482017-07-26 13:13:13 -0700118// A template may be executed safely in parallel, although if parallel
119// executions share a Writer the output may be interleaved.
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800120func (t *Template) Execute(wr io.Writer, data any) error {
Brent Austinba3052e2015-04-21 16:08:23 -0700121 if err := t.escape(); err != nil {
122 return err
123 }
124 return t.text.Execute(wr, data)
125}
126
127// ExecuteTemplate applies the template associated with t that has the given
128// name to the specified data object and writes the output to wr.
129// If an error occurs executing the template or writing its output,
130// execution stops, but partial results may already have been written to
131// the output writer.
Dan Willemsend2797482017-07-26 13:13:13 -0700132// A template may be executed safely in parallel, although if parallel
133// executions share a Writer the output may be interleaved.
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800134func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error {
Brent Austinba3052e2015-04-21 16:08:23 -0700135 tmpl, err := t.lookupAndEscapeTemplate(name)
136 if err != nil {
137 return err
138 }
139 return tmpl.text.Execute(wr, data)
140}
141
142// lookupAndEscapeTemplate guarantees that the template with the given name
143// is escaped, or returns an error if it cannot be. It returns the named
144// template.
145func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
146 t.nameSpace.mu.Lock()
147 defer t.nameSpace.mu.Unlock()
Dan Willemsenebae3022017-01-13 23:01:08 -0800148 t.nameSpace.escaped = true
Brent Austinba3052e2015-04-21 16:08:23 -0700149 tmpl = t.set[name]
150 if tmpl == nil {
151 return nil, fmt.Errorf("html/template: %q is undefined", name)
152 }
153 if tmpl.escapeErr != nil && tmpl.escapeErr != escapeOK {
154 return nil, tmpl.escapeErr
155 }
156 if tmpl.text.Tree == nil || tmpl.text.Root == nil {
157 return nil, fmt.Errorf("html/template: %q is an incomplete template", name)
158 }
159 if t.text.Lookup(name) == nil {
160 panic("html/template internal error: template escaping out of sync")
161 }
162 if tmpl.escapeErr == nil {
163 err = escapeTemplate(tmpl, tmpl.text.Root, name)
164 }
165 return tmpl, err
166}
167
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700168// DefinedTemplates returns a string listing the defined templates,
169// prefixed by the string "; defined templates are: ". If there are none,
170// it returns the empty string. Used to generate an error message.
171func (t *Template) DefinedTemplates() string {
172 return t.text.DefinedTemplates()
173}
174
Dan Willemsenebae3022017-01-13 23:01:08 -0800175// Parse parses text as a template body for t.
176// Named template definitions ({{define ...}} or {{block ...}} statements) in text
177// define additional templates associated with t and are removed from the
178// definition of t itself.
179//
180// Templates can be redefined in successive calls to Parse,
181// before the first use of Execute on t or any associated template.
182// A template definition with a body containing only white space and comments
183// is considered empty and will not replace an existing template's body.
184// This allows using Parse to add new named template definitions without
185// overwriting the main template body.
186func (t *Template) Parse(text string) (*Template, error) {
187 if err := t.checkCanParse(); err != nil {
188 return nil, err
189 }
190
191 ret, err := t.text.Parse(text)
Brent Austinba3052e2015-04-21 16:08:23 -0700192 if err != nil {
193 return nil, err
194 }
Dan Willemsenebae3022017-01-13 23:01:08 -0800195
Brent Austinba3052e2015-04-21 16:08:23 -0700196 // In general, all the named templates might have changed underfoot.
197 // Regardless, some new ones may have been defined.
198 // The template.Template set has been updated; update ours.
199 t.nameSpace.mu.Lock()
200 defer t.nameSpace.mu.Unlock()
201 for _, v := range ret.Templates() {
202 name := v.Name()
203 tmpl := t.set[name]
204 if tmpl == nil {
205 tmpl = t.new(name)
206 }
Brent Austinba3052e2015-04-21 16:08:23 -0700207 tmpl.text = v
208 tmpl.Tree = v.Tree
209 }
210 return t, nil
211}
212
213// AddParseTree creates a new template with the name and parse tree
214// and associates it with t.
215//
Dan Willemsenebae3022017-01-13 23:01:08 -0800216// It returns an error if t or any associated template has already been executed.
Brent Austinba3052e2015-04-21 16:08:23 -0700217func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
Dan Willemsenebae3022017-01-13 23:01:08 -0800218 if err := t.checkCanParse(); err != nil {
219 return nil, err
220 }
221
Brent Austinba3052e2015-04-21 16:08:23 -0700222 t.nameSpace.mu.Lock()
223 defer t.nameSpace.mu.Unlock()
Brent Austinba3052e2015-04-21 16:08:23 -0700224 text, err := t.text.AddParseTree(name, tree)
225 if err != nil {
226 return nil, err
227 }
228 ret := &Template{
229 nil,
230 text,
231 text.Tree,
232 t.nameSpace,
233 }
234 t.set[name] = ret
235 return ret, nil
236}
237
238// Clone returns a duplicate of the template, including all associated
239// templates. The actual representation is not copied, but the name space of
240// associated templates is, so further calls to Parse in the copy will add
241// templates to the copy but not to the original. Clone can be used to prepare
242// common templates and use them with variant definitions for other templates
243// by adding the variants after the clone is made.
244//
245// It returns an error if t has already been executed.
246func (t *Template) Clone() (*Template, error) {
247 t.nameSpace.mu.Lock()
248 defer t.nameSpace.mu.Unlock()
249 if t.escapeErr != nil {
250 return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
251 }
252 textClone, err := t.text.Clone()
253 if err != nil {
254 return nil, err
255 }
Dan Willemsend2797482017-07-26 13:13:13 -0700256 ns := &nameSpace{set: make(map[string]*Template)}
257 ns.esc = makeEscaper(ns)
Brent Austinba3052e2015-04-21 16:08:23 -0700258 ret := &Template{
259 nil,
260 textClone,
261 textClone.Tree,
Dan Willemsend2797482017-07-26 13:13:13 -0700262 ns,
Brent Austinba3052e2015-04-21 16:08:23 -0700263 }
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700264 ret.set[ret.Name()] = ret
Brent Austinba3052e2015-04-21 16:08:23 -0700265 for _, x := range textClone.Templates() {
266 name := x.Name()
267 src := t.set[name]
268 if src == nil || src.escapeErr != nil {
269 return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
270 }
271 x.Tree = x.Tree.Copy()
272 ret.set[name] = &Template{
273 nil,
274 x,
275 x.Tree,
276 ret.nameSpace,
277 }
278 }
Dan Willemsenebae3022017-01-13 23:01:08 -0800279 // Return the template associated with the name of this template.
280 return ret.set[ret.Name()], nil
Brent Austinba3052e2015-04-21 16:08:23 -0700281}
282
283// New allocates a new HTML template with the given name.
284func New(name string) *Template {
Dan Willemsend2797482017-07-26 13:13:13 -0700285 ns := &nameSpace{set: make(map[string]*Template)}
286 ns.esc = makeEscaper(ns)
Brent Austinba3052e2015-04-21 16:08:23 -0700287 tmpl := &Template{
288 nil,
289 template.New(name),
290 nil,
Dan Willemsend2797482017-07-26 13:13:13 -0700291 ns,
Brent Austinba3052e2015-04-21 16:08:23 -0700292 }
293 tmpl.set[name] = tmpl
294 return tmpl
295}
296
297// New allocates a new HTML template associated with the given one
298// and with the same delimiters. The association, which is transitive,
299// allows one template to invoke another with a {{template}} action.
Dan Willemsena3223282018-02-27 19:41:43 -0800300//
301// If a template with the given name already exists, the new HTML template
302// will replace it. The existing template will be reset and disassociated with
303// t.
Brent Austinba3052e2015-04-21 16:08:23 -0700304func (t *Template) New(name string) *Template {
305 t.nameSpace.mu.Lock()
306 defer t.nameSpace.mu.Unlock()
307 return t.new(name)
308}
309
310// new is the implementation of New, without the lock.
311func (t *Template) new(name string) *Template {
312 tmpl := &Template{
313 nil,
314 t.text.New(name),
315 nil,
316 t.nameSpace,
317 }
Dan Willemsena3223282018-02-27 19:41:43 -0800318 if existing, ok := tmpl.set[name]; ok {
319 emptyTmpl := New(existing.Name())
320 *existing = *emptyTmpl
321 }
Brent Austinba3052e2015-04-21 16:08:23 -0700322 tmpl.set[name] = tmpl
323 return tmpl
324}
325
326// Name returns the name of the template.
327func (t *Template) Name() string {
328 return t.text.Name()
329}
330
331// FuncMap is the type of the map defining the mapping from names to
332// functions. Each function must have either a single return value, or two
333// return values of which the second has type error. In that case, if the
334// second (error) argument evaluates to non-nil during execution, execution
335// terminates and Execute returns that error. FuncMap has the same base type
336// as FuncMap in "text/template", copied here so clients need not import
337// "text/template".
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800338type FuncMap map[string]any
Brent Austinba3052e2015-04-21 16:08:23 -0700339
340// Funcs adds the elements of the argument map to the template's function map.
Dan Willemsend2797482017-07-26 13:13:13 -0700341// It must be called before the template is parsed.
Brent Austinba3052e2015-04-21 16:08:23 -0700342// It panics if a value in the map is not a function with appropriate return
343// type. However, it is legal to overwrite elements of the map. The return
344// value is the template, so calls can be chained.
345func (t *Template) Funcs(funcMap FuncMap) *Template {
346 t.text.Funcs(template.FuncMap(funcMap))
347 return t
348}
349
350// Delims sets the action delimiters to the specified strings, to be used in
351// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
352// definitions will inherit the settings. An empty delimiter stands for the
353// corresponding default: {{ or }}.
354// The return value is the template, so calls can be chained.
355func (t *Template) Delims(left, right string) *Template {
356 t.text.Delims(left, right)
357 return t
358}
359
360// Lookup returns the template with the given name that is associated with t,
361// or nil if there is no such template.
362func (t *Template) Lookup(name string) *Template {
363 t.nameSpace.mu.Lock()
364 defer t.nameSpace.mu.Unlock()
365 return t.set[name]
366}
367
368// Must is a helper that wraps a call to a function returning (*Template, error)
369// and panics if the error is non-nil. It is intended for use in variable initializations
370// such as
371// var t = template.Must(template.New("name").Parse("html"))
372func Must(t *Template, err error) *Template {
373 if err != nil {
374 panic(err)
375 }
376 return t
377}
378
379// ParseFiles creates a new Template and parses the template definitions from
380// the named files. The returned template's name will have the (base) name and
381// (parsed) contents of the first file. There must be at least one file.
382// If an error occurs, parsing stops and the returned *Template is nil.
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700383//
384// When parsing multiple files with the same name in different directories,
385// the last one mentioned will be the one that results.
386// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
387// named "foo", while "a/foo" is unavailable.
Brent Austinba3052e2015-04-21 16:08:23 -0700388func ParseFiles(filenames ...string) (*Template, error) {
Colin Cross1f805522021-05-14 11:10:59 -0700389 return parseFiles(nil, readFileOS, filenames...)
Brent Austinba3052e2015-04-21 16:08:23 -0700390}
391
392// ParseFiles parses the named files and associates the resulting templates with
393// t. If an error occurs, parsing stops and the returned template is nil;
394// otherwise it is t. There must be at least one file.
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700395//
396// When parsing multiple files with the same name in different directories,
397// the last one mentioned will be the one that results.
Dan Willemsenebae3022017-01-13 23:01:08 -0800398//
399// ParseFiles returns an error if t or any associated template has already been executed.
Brent Austinba3052e2015-04-21 16:08:23 -0700400func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
Colin Cross1f805522021-05-14 11:10:59 -0700401 return parseFiles(t, readFileOS, filenames...)
Brent Austinba3052e2015-04-21 16:08:23 -0700402}
403
404// parseFiles is the helper for the method and function. If the argument
405// template is nil, it is created from the first file.
Colin Cross1f805522021-05-14 11:10:59 -0700406func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
Dan Willemsenebae3022017-01-13 23:01:08 -0800407 if err := t.checkCanParse(); err != nil {
408 return nil, err
409 }
410
Brent Austinba3052e2015-04-21 16:08:23 -0700411 if len(filenames) == 0 {
412 // Not really a problem, but be consistent.
413 return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
414 }
415 for _, filename := range filenames {
Colin Cross1f805522021-05-14 11:10:59 -0700416 name, b, err := readFile(filename)
Brent Austinba3052e2015-04-21 16:08:23 -0700417 if err != nil {
418 return nil, err
419 }
420 s := string(b)
Brent Austinba3052e2015-04-21 16:08:23 -0700421 // First template becomes return value if not already defined,
422 // and we use that one for subsequent New calls to associate
423 // all the templates together. Also, if this file has the same name
424 // as t, this file becomes the contents of t, so
425 // t, err := New(name).Funcs(xxx).ParseFiles(name)
426 // works. Otherwise we create a new template associated with t.
427 var tmpl *Template
428 if t == nil {
429 t = New(name)
430 }
431 if name == t.Name() {
432 tmpl = t
433 } else {
434 tmpl = t.New(name)
435 }
436 _, err = tmpl.Parse(s)
437 if err != nil {
438 return nil, err
439 }
440 }
441 return t, nil
442}
443
Colin Cross430342c2019-09-07 08:36:04 -0700444// ParseGlob creates a new Template and parses the template definitions from
445// the files identified by the pattern. The files are matched according to the
446// semantics of filepath.Match, and the pattern must match at least one file.
447// The returned template will have the (base) name and (parsed) contents of the
Brent Austinba3052e2015-04-21 16:08:23 -0700448// first file matched by the pattern. ParseGlob is equivalent to calling
449// ParseFiles with the list of files matched by the pattern.
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700450//
451// When parsing multiple files with the same name in different directories,
452// the last one mentioned will be the one that results.
Brent Austinba3052e2015-04-21 16:08:23 -0700453func ParseGlob(pattern string) (*Template, error) {
454 return parseGlob(nil, pattern)
455}
456
457// ParseGlob parses the template definitions in the files identified by the
Colin Cross430342c2019-09-07 08:36:04 -0700458// pattern and associates the resulting templates with t. The files are matched
459// according to the semantics of filepath.Match, and the pattern must match at
460// least one file. ParseGlob is equivalent to calling t.ParseFiles with the
461// list of files matched by the pattern.
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700462//
463// When parsing multiple files with the same name in different directories,
464// the last one mentioned will be the one that results.
Dan Willemsenebae3022017-01-13 23:01:08 -0800465//
466// ParseGlob returns an error if t or any associated template has already been executed.
Brent Austinba3052e2015-04-21 16:08:23 -0700467func (t *Template) ParseGlob(pattern string) (*Template, error) {
468 return parseGlob(t, pattern)
469}
470
471// parseGlob is the implementation of the function and method ParseGlob.
472func parseGlob(t *Template, pattern string) (*Template, error) {
Dan Willemsenebae3022017-01-13 23:01:08 -0800473 if err := t.checkCanParse(); err != nil {
474 return nil, err
475 }
Brent Austinba3052e2015-04-21 16:08:23 -0700476 filenames, err := filepath.Glob(pattern)
477 if err != nil {
478 return nil, err
479 }
480 if len(filenames) == 0 {
481 return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern)
482 }
Colin Cross1f805522021-05-14 11:10:59 -0700483 return parseFiles(t, readFileOS, filenames...)
Brent Austinba3052e2015-04-21 16:08:23 -0700484}
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700485
486// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
487// and whether the value has a meaningful truth value. This is the definition of
488// truth used by if and other such actions.
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800489func IsTrue(val any) (truth, ok bool) {
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700490 return template.IsTrue(val)
491}
Colin Cross1f805522021-05-14 11:10:59 -0700492
493// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
494// instead of the host operating system's file system.
495// It accepts a list of glob patterns.
496// (Note that most file names serve as glob patterns matching only themselves.)
497func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
498 return parseFS(nil, fs, patterns)
499}
500
501// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
502// instead of the host operating system's file system.
503// It accepts a list of glob patterns.
504// (Note that most file names serve as glob patterns matching only themselves.)
505func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
506 return parseFS(t, fs, patterns)
507}
508
509func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
510 var filenames []string
511 for _, pattern := range patterns {
512 list, err := fs.Glob(fsys, pattern)
513 if err != nil {
514 return nil, err
515 }
516 if len(list) == 0 {
517 return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
518 }
519 filenames = append(filenames, list...)
520 }
521 return parseFiles(t, readFileFS(fsys), filenames...)
522}
523
524func readFileOS(file string) (name string, b []byte, err error) {
525 name = filepath.Base(file)
526 b, err = os.ReadFile(file)
527 return
528}
529
530func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
531 return func(file string) (name string, b []byte, err error) {
532 name = path.Base(file)
533 b, err = fs.ReadFile(fsys, file)
534 return
535 }
536}