mirror of
https://github.com/golang/go
synced 2024-11-22 05:14:40 -07:00
html/template: add Clone and AddParseTree. Make text/template's Clone
return (*Template, error), not just *Template. Fixes #2757. R=r CC=golang-dev https://golang.org/cl/5665044
This commit is contained in:
parent
aca8071fd5
commit
0c5239410e
@ -7,9 +7,10 @@ package template
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"text/template/parse"
|
||||
)
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
func TestCloneList(t *testing.T) {
|
||||
tests := []struct {
|
||||
input, want, wantClone string
|
||||
}{
|
||||
@ -90,3 +91,107 @@ func TestClone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddParseTree(t *testing.T) {
|
||||
root := Must(New("root").Parse(`{{define "a"}} {{.}} {{template "b"}} {{.}} "></a>{{end}}`))
|
||||
tree, err := parse.Parse("t", `{{define "b"}}<a href="{{end}}`, "", "", nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
added := Must(root.AddParseTree("b", tree["b"]))
|
||||
b := new(bytes.Buffer)
|
||||
err = added.ExecuteTemplate(b, "a", "1>0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := b.String(), ` 1>0 <a href=" 1%3e0 "></a>`; got != want {
|
||||
t.Errorf("got %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
// The {{.}} will be executed with data "<i>*/" in different contexts.
|
||||
// In the t0 template, it will be in a text context.
|
||||
// In the t1 template, it will be in a URL context.
|
||||
// In the t2 template, it will be in a JavaScript context.
|
||||
// In the t3 template, it will be in a CSS context.
|
||||
const tmpl = `{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}`
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
// Create an incomplete template t0.
|
||||
t0 := Must(New("t0").Parse(tmpl))
|
||||
|
||||
// Clone t0 as t1.
|
||||
t1 := Must(t0.Clone())
|
||||
Must(t1.Parse(`{{define "lhs"}} <a href=" {{end}}`))
|
||||
Must(t1.Parse(`{{define "rhs"}} "></a> {{end}}`))
|
||||
|
||||
// Execute t1.
|
||||
b.Reset()
|
||||
if err := t1.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := b.String(), ` <a href=" %3ci%3e*/ "></a> `; got != want {
|
||||
t.Errorf("t1: got %q want %q", got, want)
|
||||
}
|
||||
|
||||
// Clone t0 as t2.
|
||||
t2 := Must(t0.Clone())
|
||||
Must(t2.Parse(`{{define "lhs"}} <p onclick="javascript: {{end}}`))
|
||||
Must(t2.Parse(`{{define "rhs"}} "></p> {{end}}`))
|
||||
|
||||
// Execute t2.
|
||||
b.Reset()
|
||||
if err := t2.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := b.String(), ` <p onclick="javascript: "\u003ci\u003e*/" "></p> `; got != want {
|
||||
t.Errorf("t2: got %q want %q", got, want)
|
||||
}
|
||||
|
||||
// Clone t0 as t3, but do not execute t3 yet.
|
||||
t3 := Must(t0.Clone())
|
||||
Must(t3.Parse(`{{define "lhs"}} <style> {{end}}`))
|
||||
Must(t3.Parse(`{{define "rhs"}} </style> {{end}}`))
|
||||
|
||||
// Complete t0.
|
||||
Must(t0.Parse(`{{define "lhs"}} ( {{end}}`))
|
||||
Must(t0.Parse(`{{define "rhs"}} ) {{end}}`))
|
||||
|
||||
// Clone t0 as t4. Redefining the "lhs" template should fail.
|
||||
t4 := Must(t0.Clone())
|
||||
if _, err := t4.Parse(`{{define "lhs"}} FAIL {{end}}`); err == nil {
|
||||
t.Error(`redefine "lhs": got nil err want non-nil`)
|
||||
}
|
||||
|
||||
// Execute t0.
|
||||
b.Reset()
|
||||
if err := t0.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := b.String(), ` ( <i>*/ ) `; got != want {
|
||||
t.Errorf("t0: got %q want %q", got, want)
|
||||
}
|
||||
|
||||
// Clone t0. This should fail, as t0 has already executed.
|
||||
if _, err := t0.Clone(); err == nil {
|
||||
t.Error(`t0.Clone(): got nil err want non-nil`)
|
||||
}
|
||||
|
||||
// Similarly, cloning sub-templates should fail.
|
||||
if _, err := t0.Lookup("a").Clone(); err == nil {
|
||||
t.Error(`t0.Lookup("a").Clone(): got nil err want non-nil`)
|
||||
}
|
||||
if _, err := t0.Lookup("lhs").Clone(); err == nil {
|
||||
t.Error(`t0.Lookup("lhs").Clone(): got nil err want non-nil`)
|
||||
}
|
||||
|
||||
// Execute t3.
|
||||
b.Reset()
|
||||
if err := t3.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := b.String(), ` <style> ZgotmplZ </style> `; got != want {
|
||||
t.Errorf("t3: got %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||
// ExecuteTemplate applies the template associated with t that has the given
|
||||
// name to the specified data object and writes the output to wr.
|
||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||
tmpl, err := t.lookupAndEscapeTemplate(wr, name)
|
||||
tmpl, err := t.lookupAndEscapeTemplate(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -60,7 +60,7 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})
|
||||
// lookupAndEscapeTemplate guarantees that the template with the given name
|
||||
// is escaped, or returns an error if it cannot be. It returns the named
|
||||
// template.
|
||||
func (t *Template) lookupAndEscapeTemplate(wr io.Writer, name string) (tmpl *Template, err error) {
|
||||
func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
|
||||
t.nameSpace.mu.Lock()
|
||||
defer t.nameSpace.mu.Unlock()
|
||||
tmpl = t.set[name]
|
||||
@ -106,14 +106,71 @@ func (t *Template) Parse(src string) (*Template, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// AddParseTree is unimplemented.
|
||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) error {
|
||||
return fmt.Errorf("html/template: AddParseTree unimplemented")
|
||||
// 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.
|
||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||
t.nameSpace.mu.Lock()
|
||||
defer t.nameSpace.mu.Unlock()
|
||||
if t.escaped {
|
||||
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
|
||||
}
|
||||
ret := &Template{
|
||||
false,
|
||||
text,
|
||||
t.nameSpace,
|
||||
}
|
||||
t.set[name] = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Clone is unimplemented.
|
||||
func (t *Template) Clone(name string) error {
|
||||
return fmt.Errorf("html/template: Clone unimplemented")
|
||||
// Clone returns a duplicate of the template, including all associated
|
||||
// templates. The actual representation is not copied, but the name space of
|
||||
// associated templates is, so further calls to Parse in the copy will add
|
||||
// templates to the copy but not to the original. Clone can be used to prepare
|
||||
// common templates and use them with variant definitions for other templates
|
||||
// by adding the variants after the clone is made.
|
||||
//
|
||||
// It returns an error if t has already been executed.
|
||||
func (t *Template) Clone() (*Template, error) {
|
||||
t.nameSpace.mu.Lock()
|
||||
defer t.nameSpace.mu.Unlock()
|
||||
if t.escaped {
|
||||
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
|
||||
}
|
||||
textClone, err := t.text.Clone()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &Template{
|
||||
false,
|
||||
textClone,
|
||||
&nameSpace{
|
||||
set: make(map[string]*Template),
|
||||
},
|
||||
}
|
||||
for _, x := range textClone.Templates() {
|
||||
name := x.Name()
|
||||
src := t.set[name]
|
||||
if src == nil || src.escaped {
|
||||
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
|
||||
}
|
||||
x.Tree = &parse.Tree{
|
||||
Name: x.Tree.Name,
|
||||
Root: x.Tree.Root.CopyList(),
|
||||
}
|
||||
ret.set[name] = &Template{
|
||||
false,
|
||||
x,
|
||||
ret.nameSpace,
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// New allocates a new HTML template with the given name.
|
||||
|
@ -193,7 +193,7 @@ func TestClone(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clone := root.Clone()
|
||||
clone := Must(root.Clone())
|
||||
// Add variants to both.
|
||||
_, err = root.Parse(cloneText3)
|
||||
if err != nil {
|
||||
|
@ -69,9 +69,9 @@ func (t *Template) init() {
|
||||
// templates. The actual representation is not copied, but the name space of
|
||||
// associated templates is, so further calls to Parse in the copy will add
|
||||
// templates to the copy but not to the original. Clone can be used to prepare
|
||||
// common templates and use them with variant definitions for other templates by
|
||||
// adding the variants after the clone is made.
|
||||
func (t *Template) Clone() *Template {
|
||||
// common templates and use them with variant definitions for other templates
|
||||
// by adding the variants after the clone is made.
|
||||
func (t *Template) Clone() (*Template, error) {
|
||||
nt := t.copy(nil)
|
||||
nt.init()
|
||||
nt.tmpl[t.name] = nt
|
||||
@ -89,7 +89,7 @@ func (t *Template) Clone() *Template {
|
||||
for k, v := range t.execFuncs {
|
||||
nt.execFuncs[k] = v
|
||||
}
|
||||
return nt
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// copy returns a shallow copy of t, with common set to the argument.
|
||||
|
Loading…
Reference in New Issue
Block a user