// nameSpace is the data structure shared by all templates in an association.
type nameSpace struct {
- mu sync.Mutex
- set map[string]*Template
+ mu sync.Mutex
+ set map[string]*Template
+ escaped bool
}
// Templates returns a slice of the templates associated with t, including t
return t
}
+// checkCanParse checks whether it is OK to parse templates.
+// If not, it returns an error.
+func (t *Template) checkCanParse() error {
+ if t == nil {
+ return nil
+ }
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ if t.nameSpace.escaped {
+ return fmt.Errorf("html/template: cannot Parse after Execute")
+ }
+ return nil
+}
+
// escape escapes all associated templates.
func (t *Template) escape() error {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
+ t.nameSpace.escaped = true
if t.escapeErr == nil {
if t.Tree == nil {
return fmt.Errorf("template: %q is an incomplete or empty template%s", t.Name(), t.DefinedTemplates())
func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
+ t.nameSpace.escaped = true
tmpl = t.set[name]
if tmpl == nil {
return nil, fmt.Errorf("html/template: %q is undefined", name)
// define additional templates associated with t and are removed from the
// definition of t itself.
//
+// Templates can be redefined in successive calls to Parse,
+// before the first use of Execute on t or any associated template.
// A template definition with a body containing only white space and comments
-// is considered empty and is not recorded as the template's body.
-// Each template can be given a non-empty definition at most once.
-// That is, Parse may be called multiple times to parse definitions of templates
-// to associate with t, but at most one such call can include a non-empty body for
-// t itself, and each named associated template can be given at most one
-// non-empty definition.
+// is considered empty and will not replace an existing template's body.
+// This allows using Parse to add new named template definitions without
+// overwriting the main template body.
func (t *Template) Parse(text string) (*Template, error) {
- t.nameSpace.mu.Lock()
- t.escapeErr = nil
- t.nameSpace.mu.Unlock()
+ if err := t.checkCanParse(); err != nil {
+ return nil, err
+ }
+
ret, err := t.text.Parse(text)
if err != nil {
return nil, err
}
+
// In general, all the named templates might have changed underfoot.
// Regardless, some new ones may have been defined.
// The template.Template set has been updated; update ours.
tmpl := t.set[name]
if tmpl == nil {
tmpl = t.new(name)
- } else if tmpl.escapeErr != nil {
- return nil, fmt.Errorf("html/template: cannot redefine %q after it has executed", name)
}
- // Restore our record of this text/template to its unescaped original state.
- tmpl.escapeErr = nil
tmpl.text = v
tmpl.Tree = v.Tree
}
// AddParseTree creates a new template with the name and parse tree
// and associates it with t.
//
-// It returns an error if t has already been executed.
+// It returns an error if t or any associated template has already been executed.
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
+ if err := t.checkCanParse(); err != nil {
+ return nil, err
+ }
+
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
- if t.escapeErr != nil {
- return nil, fmt.Errorf("html/template: cannot AddParseTree to %q after it has executed", t.Name())
- }
text, err := t.text.AddParseTree(name, tree)
if err != nil {
return nil, err
//
// When parsing multiple files with the same name in different directories,
// the last one mentioned will be the one that results.
+//
+// ParseFiles returns an error if t or any associated template has already been executed.
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(t, filenames...)
}
// parseFiles is the helper for the method and function. If the argument
// template is nil, it is created from the first file.
func parseFiles(t *Template, filenames ...string) (*Template, error) {
+ if err := t.checkCanParse(); err != nil {
+ return nil, err
+ }
+
if len(filenames) == 0 {
// Not really a problem, but be consistent.
return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
//
// When parsing multiple files with the same name in different directories,
// the last one mentioned will be the one that results.
+//
+// ParseGlob returns an error if t or any associated template has already been executed.
func (t *Template) ParseGlob(pattern string) (*Template, error) {
return parseGlob(t, pattern)
}
// parseGlob is the implementation of the function and method ParseGlob.
func parseGlob(t *Template, pattern string) (*Template, error) {
+ if err := t.checkCanParse(); err != nil {
+ return nil, err
+ }
filenames, err := filepath.Glob(pattern)
if err != nil {
return nil, err
-package template
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template_test
import (
"bytes"
+ . "html/template"
+ "strings"
"testing"
)
t.Fatalf("got %q; want %q", got, want)
}
}
+
+func TestRedefineNonEmptyAfterExecution(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, `foo`)
+ c.mustExecute(c.root, nil, "foo")
+ c.mustNotParse(c.root, `bar`)
+}
+
+func TestRedefineEmptyAfterExecution(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, ``)
+ c.mustExecute(c.root, nil, "")
+ c.mustNotParse(c.root, `foo`)
+ c.mustExecute(c.root, nil, "")
+}
+
+func TestRedefineAfterNonExecution(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, `{{if .}}<{{template "X"}}>{{end}}{{define "X"}}foo{{end}}`)
+ c.mustExecute(c.root, 0, "")
+ c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
+ c.mustExecute(c.root, 1, "<foo>")
+}
+
+func TestRedefineAfterNamedExecution(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, `<{{template "X" .}}>{{define "X"}}foo{{end}}`)
+ c.mustExecute(c.root, nil, "<foo>")
+ c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
+ c.mustExecute(c.root, nil, "<foo>")
+}
+
+func TestRedefineNestedByNameAfterExecution(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
+ c.mustExecute(c.lookup("X"), nil, "foo")
+ c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
+ c.mustExecute(c.lookup("X"), nil, "foo")
+}
+
+func TestRedefineNestedByTemplateAfterExecution(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
+ c.mustExecute(c.lookup("X"), nil, "foo")
+ c.mustNotParse(c.lookup("X"), `bar`)
+ c.mustExecute(c.lookup("X"), nil, "foo")
+}
+
+func TestRedefineSafety(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, `<html><a href="{{template "X"}}">{{define "X"}}{{end}}`)
+ c.mustExecute(c.root, nil, `<html><a href="">`)
+ // Note: Every version of Go prior to Go 1.8 accepted the redefinition of "X"
+ // on the next line, but luckily kept it from being used in the outer template.
+ // Now we reject it, which makes clearer that we're not going to use it.
+ c.mustNotParse(c.root, `{{define "X"}}" bar="baz{{end}}`)
+ c.mustExecute(c.root, nil, `<html><a href="">`)
+}
+
+func TestRedefineTopUse(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, `{{template "X"}}{{.}}{{define "X"}}{{end}}`)
+ c.mustExecute(c.root, 42, `42`)
+ c.mustNotParse(c.root, `{{define "X"}}<script>{{end}}`)
+ c.mustExecute(c.root, 42, `42`)
+}
+
+func TestRedefineOtherParsers(t *testing.T) {
+ c := newTestCase(t)
+ c.mustParse(c.root, ``)
+ c.mustExecute(c.root, nil, ``)
+ if _, err := c.root.ParseFiles("no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
+ t.Errorf("ParseFiles: %v\nwanted error about already having Executed", err)
+ }
+ if _, err := c.root.ParseGlob("*.no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
+ t.Errorf("ParseGlob: %v\nwanted error about already having Executed", err)
+ }
+ if _, err := c.root.AddParseTree("t1", c.root.Tree); err == nil || !strings.Contains(err.Error(), "Execute") {
+ t.Errorf("AddParseTree: %v\nwanted error about already having Executed", err)
+ }
+}
+
+type testCase struct {
+ t *testing.T
+ root *Template
+}
+
+func newTestCase(t *testing.T) *testCase {
+ return &testCase{
+ t: t,
+ root: New("root"),
+ }
+}
+
+func (c *testCase) lookup(name string) *Template {
+ return c.root.Lookup(name)
+}
+
+func (c *testCase) mustParse(t *Template, text string) {
+ _, err := t.Parse(text)
+ if err != nil {
+ c.t.Fatalf("parse: %v", err)
+ }
+}
+
+func (c *testCase) mustNotParse(t *Template, text string) {
+ _, err := t.Parse(text)
+ if err == nil {
+ c.t.Fatalf("parse: unexpected success")
+ }
+}
+
+func (c *testCase) mustExecute(t *Template, val interface{}, want string) {
+ var buf bytes.Buffer
+ err := t.Execute(&buf, val)
+ if err != nil {
+ c.t.Fatalf("execute: %v", err)
+ }
+ if buf.String() != want {
+ c.t.Fatalf("template output:\n%s\nwant:\n%s", buf.String(), want)
+ }
+}
// define additional templates associated with t and are removed from the
// definition of t itself.
//
+// Templates can be redefined in successive calls to Parse.
// A template definition with a body containing only white space and comments
-// is considered empty and is not recorded as the template's body.
-// Each template can be given a non-empty definition at most once.
-// That is, Parse may be called multiple times to parse definitions of templates
-// to associate with t, but at most one such call can include a non-empty body for
-// t itself, and each named associated template can be given at most one
-// non-empty definition.
+// is considered empty and will not replace an existing template's body.
+// This allows using Parse to add new named template definitions without
+// overwriting the main template body.
func (t *Template) Parse(text string) (*Template, error) {
t.init()
t.muFuncs.RLock()