summaryrefslogtreecommitdiff
path: root/libgo/go/html/template
diff options
context:
space:
mode:
authorbstarynk <bstarynk@138bc75d-0d04-0410-961f-82ee72b054a4>2011-12-14 08:52:21 +0000
committerbstarynk <bstarynk@138bc75d-0d04-0410-961f-82ee72b054a4>2011-12-14 08:52:21 +0000
commitd0b175bffc2f2cb91d1fb529b7c36d3e984d9594 (patch)
treeeb438cb5c7cabed8d102b2c0c1bdd1b0aebb59eb /libgo/go/html/template
parent5e3123db0a9b4c8def9fee64446b130ce81ace45 (diff)
downloadgcc-d0b175bffc2f2cb91d1fb529b7c36d3e984d9594.tar.gz
2011-12-14 Basile Starynkevitch <basile@starynkevitch.net>
MELT branch merged with trunk rev 182322 using svnmerge git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/branches/melt-branch@182325 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libgo/go/html/template')
-rw-r--r--libgo/go/html/template/clone_test.go32
-rw-r--r--libgo/go/html/template/content.go33
-rw-r--r--libgo/go/html/template/doc.go68
-rw-r--r--libgo/go/html/template/escape.go51
-rw-r--r--libgo/go/html/template/escape_test.go73
-rw-r--r--libgo/go/html/template/js.go20
-rw-r--r--libgo/go/html/template/template.go376
7 files changed, 367 insertions, 286 deletions
diff --git a/libgo/go/html/template/clone_test.go b/libgo/go/html/template/clone_test.go
index ed1698acd8b..39788173b99 100644
--- a/libgo/go/html/template/clone_test.go
+++ b/libgo/go/html/template/clone_test.go
@@ -7,8 +7,6 @@ package template
import (
"bytes"
"testing"
- "text/template"
- "text/template/parse"
)
func TestClone(t *testing.T) {
@@ -48,15 +46,20 @@ func TestClone(t *testing.T) {
}
for _, test := range tests {
- s := template.Must(template.New("s").Parse(test.input))
- d := template.New("d")
- d.Tree = &parse.Tree{Name: d.Name(), Root: cloneList(s.Root)}
+ s, err := New("s").Parse(test.input)
+ if err != nil {
+ t.Errorf("input=%q: unexpected parse error %v", test.input, err)
+ }
+
+ d, _ := New("d").Parse(test.input)
+ // Hack: just replace the root of the tree.
+ d.text.Root = cloneList(s.text.Root)
- if want, got := s.Root.String(), d.Root.String(); want != got {
+ if want, got := s.text.Root.String(), d.text.Root.String(); want != got {
t.Errorf("want %q, got %q", want, got)
}
- err := escape(d)
+ err = escapeTemplates(d, "d")
if err != nil {
t.Errorf("%q: failed to escape: %s", test.input, err)
continue
@@ -73,18 +76,17 @@ func TestClone(t *testing.T) {
data := []string{"foo", "<bar>", "baz"}
- // Make sure escaping d did not affect s.
var b bytes.Buffer
- s.Execute(&b, data)
- if got := b.String(); got != test.want {
- t.Errorf("%q: want %q, got %q", test.input, test.want, got)
- continue
+ d.Execute(&b, data)
+ if got := b.String(); got != test.wantClone {
+ t.Errorf("input=%q: want %q, got %q", test.input, test.wantClone, got)
}
+ // Make sure escaping d did not affect s.
b.Reset()
- d.Execute(&b, data)
- if got := b.String(); got != test.wantClone {
- t.Errorf("%q: want %q, got %q", test.input, test.wantClone, got)
+ s.text.Execute(&b, data)
+ if got := b.String(); got != test.want {
+ t.Errorf("input=%q: want %q, got %q", test.input, test.want, got)
}
}
}
diff --git a/libgo/go/html/template/content.go b/libgo/go/html/template/content.go
index d720d4ba689..4de7ccde912 100644
--- a/libgo/go/html/template/content.go
+++ b/libgo/go/html/template/content.go
@@ -6,15 +6,16 @@ package template
import (
"fmt"
+ "reflect"
)
// Strings of content from a trusted source.
type (
// CSS encapsulates known safe content that matches any of:
- // (1) The CSS3 stylesheet production, such as `p { color: purple }`.
- // (2) The CSS3 rule production, such as `a[href=~"https:"].foo#bar`.
- // (3) CSS3 declaration productions, such as `color: red; margin: 2px`.
- // (4) The CSS3 value production, such as `rgba(0, 0, 255, 127)`.
+ // 1. The CSS3 stylesheet production, such as `p { color: purple }`.
+ // 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`.
+ // 3. CSS3 declaration productions, such as `color: red; margin: 2px`.
+ // 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`.
// See http://www.w3.org/TR/css3-syntax/#style
CSS string
@@ -40,8 +41,8 @@ type (
// JSStr encapsulates a sequence of characters meant to be embedded
// between quotes in a JavaScript expression.
// The string must match a series of StringCharacters:
- // StringCharacter :: SourceCharacter but not `\` or LineTerminator
- // | EscapeSequence
+ // StringCharacter :: SourceCharacter but not `\` or LineTerminator
+ // | EscapeSequence
// Note that LineContinuations are not allowed.
// JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not.
JSStr string
@@ -70,10 +71,25 @@ const (
contentTypeUnsafe
)
+// indirect returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil).
+func indirect(a interface{}) interface{} {
+ if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
+ // Avoid creating a reflect.Value if it's not a pointer.
+ return a
+ }
+ v := reflect.ValueOf(a)
+ for v.Kind() == reflect.Ptr && !v.IsNil() {
+ v = v.Elem()
+ }
+ return v.Interface()
+}
+
// stringify converts its arguments to a string and the type of the content.
+// All pointers are dereferenced, as in the text/template package.
func stringify(args ...interface{}) (string, contentType) {
if len(args) == 1 {
- switch s := args[0].(type) {
+ switch s := indirect(args[0]).(type) {
case string:
return s, contentTypePlain
case CSS:
@@ -90,5 +106,8 @@ func stringify(args ...interface{}) (string, contentType) {
return string(s), contentTypeURL
}
}
+ for i, arg := range args {
+ args[i] = indirect(arg)
+ }
return fmt.Sprint(args...), contentTypePlain
}
diff --git a/libgo/go/html/template/doc.go b/libgo/go/html/template/doc.go
index 0324c9c0ee3..fc0e3826442 100644
--- a/libgo/go/html/template/doc.go
+++ b/libgo/go/html/template/doc.go
@@ -13,9 +13,9 @@ Introduction
This package wraps package template so you can use the standard template API
to parse and execute templates.
- set, err := new(template.Set).Parse(...)
- // Error checking elided
- err = set.Execute(out, "Foo", data)
+ set, err := new(template.Set).Parse(...)
+ // Error checking elided
+ err = set.Execute(out, "Foo", data)
If successful, set will now be injection-safe. Otherwise, err is an error
defined in the docs for ErrorCode.
@@ -29,25 +29,25 @@ trusted, while Execute's data parameter is not. More details are provided below.
Example
- import "template"
- ...
- t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
- err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>")
+ import "text/template"
+ ...
+ t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
+ err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>")
produces
- Hello, <script>alert('you have been pwned')</script>!
+ Hello, <script>alert('you have been pwned')</script>!
but with contextual autoescaping,
- import "html/template"
- ...
- t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
- err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>")
+ import "html/template"
+ ...
+ t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
+ err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>")
produces safe, escaped HTML output
- Hello, &lt;script&gt;alert('you have been pwned')&lt;/script&gt;!
+ Hello, &lt;script&gt;alert('you have been pwned')&lt;/script&gt;!
Contexts
@@ -80,36 +80,36 @@ Contexts
Assuming {{.}} is `O'Reilly: How are <i>you</i>?`, the table below shows
how {{.}} appears when used in the context to the left.
-Context {{.}} After
-{{.}} O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
-<a title='{{.}}'> O&#39;Reilly: How are you?
-<a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
-<a href="?q={{.}}"> O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
-<a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...?
-<a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?"
-<a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f
+ Context {{.}} After
+ {{.}} O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
+ <a title='{{.}}'> O&#39;Reilly: How are you?
+ <a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
+ <a href="?q={{.}}"> O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
+ <a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...?
+ <a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?"
+ <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f
If used in an unsafe context, then the value might be filtered out:
-Context {{.}} After
-<a href="{{.}}"> #ZgotmplZ
+ Context {{.}} After
+ <a href="{{.}}"> #ZgotmplZ
since "O'Reilly:" is not an allowed protocol like "http:".
If {{.}} is the innocuous word, `left`, then it can appear more widely,
-Context {{.}} After
-{{.}} left
-<a title='{{.}}'> left
-<a href='{{.}}'> left
-<a href='/{{.}}'> left
-<a href='?dir={{.}}'> left
-<a style="border-{{.}}: 4px"> left
-<a style="align: {{.}}"> left
-<a style="background: '{{.}}'> left
-<a style="background: url('{{.}}')> left
-<style>p.{{.}} {color:red}</style> left
+ Context {{.}} After
+ {{.}} left
+ <a title='{{.}}'> left
+ <a href='{{.}}'> left
+ <a href='/{{.}}'> left
+ <a href='?dir={{.}}'> left
+ <a style="border-{{.}}: 4px"> left
+ <a style="align: {{.}}"> left
+ <a style="background: '{{.}}'> left
+ <a style="background: url('{{.}}')> left
+ <style>p.{{.}} {color:red}</style> left
Non-string values can be used in JavaScript contexts.
If {{.}} is
diff --git a/libgo/go/html/template/escape.go b/libgo/go/html/template/escape.go
index 8ac07eae24c..4a7a9354c93 100644
--- a/libgo/go/html/template/escape.go
+++ b/libgo/go/html/template/escape.go
@@ -12,24 +12,15 @@ import (
"text/template/parse"
)
-// escape rewrites each action in the template to guarantee that the output is
-// properly escaped.
-func escape(t *template.Template) error {
- var s template.Set
- s.Add(t)
- return escapeSet(&s, t.Name())
- // TODO: if s contains cloned dependencies due to self-recursion
- // cross-context, error out.
-}
-
-// escapeSet rewrites the template set to guarantee that the output of any of
-// the named templates is properly escaped.
-// Names should include the names of all templates that might be Executed but
-// need not include helper templates.
-// If no error is returned, then the named templates have been modified.
-// Otherwise the named templates have been rendered unusable.
-func escapeSet(s *template.Set, names ...string) error {
- e := newEscaper(s)
+// escapeTemplates rewrites the named templates, which must be
+// associated with t, to guarantee that the output of any of the named
+// templates is properly escaped. Names should include the names of
+// all templates that might be Executed but need not include helper
+// templates. If no error is returned, then the named templates have
+// been modified. Otherwise the named templates have been rendered
+// unusable.
+func escapeTemplates(tmpl *Template, names ...string) error {
+ e := newEscaper(tmpl)
for _, name := range names {
c, _ := e.escapeTree(context{}, name, 0)
var err error
@@ -41,12 +32,13 @@ func escapeSet(s *template.Set, names ...string) error {
if err != nil {
// Prevent execution of unsafe templates.
for _, name := range names {
- if t := s.Template(name); t != nil {
- t.Tree = nil
+ if t := tmpl.set[name]; t != nil {
+ t.text.Tree = nil
}
}
return err
}
+ tmpl.escaped = true
}
e.commit()
return nil
@@ -83,8 +75,7 @@ var equivEscapers = map[string]string{
// escaper collects type inferences about templates and changes needed to make
// templates injection safe.
type escaper struct {
- // set is the template set being escaped.
- set *template.Set
+ tmpl *Template
// output[templateName] is the output context for a templateName that
// has been mangled to include its input context.
output map[string]context
@@ -102,9 +93,9 @@ type escaper struct {
}
// newEscaper creates a blank escaper for the given set.
-func newEscaper(s *template.Set) *escaper {
+func newEscaper(t *Template) *escaper {
return &escaper{
- s,
+ t,
map[string]context{},
map[string]*template.Template{},
map[string]bool{},
@@ -442,7 +433,7 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context {
// It returns the best guess at an output context, and the result of the filter
// which is the same as whether e was updated.
func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
- e1 := newEscaper(e.set)
+ e1 := newEscaper(e.tmpl)
// Make type inferences available to f.
for k, v := range e.output {
e1.output[k] = v
@@ -501,7 +492,7 @@ func (e *escaper) escapeTree(c context, name string, line int) (context, string)
}, dname
}
if dname != name {
- // Use any template derived during an earlier call to escapeSet
+ // Use any template derived during an earlier call to escapeTemplate
// with different top level templates, or clone if necessary.
dt := e.template(dname)
if dt == nil {
@@ -529,7 +520,7 @@ func (e *escaper) computeOutCtx(c context, t *template.Template) context {
if !ok && c1.state != stateError {
return context{
state: stateError,
- // TODO: Find the first node with a line in t.Tree.Root
+ // TODO: Find the first node with a line in t.text.Tree.Root
err: errorf(ErrOutputContext, 0, "cannot compute output context for template %s", t.Name()),
}
}
@@ -729,7 +720,9 @@ func (e *escaper) commit() {
e.template(name).Funcs(funcMap)
}
for _, t := range e.derived {
- e.set.Add(t)
+ if _, err := e.tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
+ panic("error adding derived template")
+ }
}
for n, s := range e.actionNodeEdits {
ensurePipelineContains(n.Pipe, s)
@@ -744,7 +737,7 @@ func (e *escaper) commit() {
// template returns the named template given a mangled template name.
func (e *escaper) template(name string) *template.Template {
- t := e.set.Template(name)
+ t := e.tmpl.text.Lookup(name)
if t == nil {
t = e.derived[name]
}
diff --git a/libgo/go/html/template/escape_test.go b/libgo/go/html/template/escape_test.go
index d8bfa321121..b4daca7d6bd 100644
--- a/libgo/go/html/template/escape_test.go
+++ b/libgo/go/html/template/escape_test.go
@@ -28,7 +28,7 @@ func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
}
func TestEscape(t *testing.T) {
- var data = struct {
+ data := struct {
F, T bool
C, G, H string
A, E []string
@@ -50,6 +50,7 @@ func TestEscape(t *testing.T) {
Z: nil,
W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
}
+ pdata := &data
tests := []struct {
name string
@@ -668,6 +669,15 @@ func TestEscape(t *testing.T) {
t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
continue
}
+ b.Reset()
+ if err := tmpl.Execute(b, pdata); err != nil {
+ t.Errorf("%s: template execution failed for pointer: %s", test.name, err)
+ continue
+ }
+ if w, g := test.output, b.String(); w != g {
+ t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
+ continue
+ }
}
}
@@ -796,13 +806,15 @@ func TestEscapeSet(t *testing.T) {
for name, body := range test.inputs {
source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body)
}
- s := &Set{}
- s.Funcs(fns)
- s.Parse(source)
+ tmpl, err := New("root").Funcs(fns).Parse(source)
+ if err != nil {
+ t.Errorf("error parsing %q: %v", source, err)
+ continue
+ }
var b bytes.Buffer
- if err := s.Execute(&b, "main", data); err != nil {
- t.Errorf("%q executing %v", err.Error(), s.Template("main"))
+ if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil {
+ t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main"))
continue
}
if got := b.String(); test.want != got {
@@ -919,13 +931,13 @@ func TestErrors(t *testing.T) {
"z:1: no such template foo",
},
{
- `{{define "z"}}<div{{template "y"}}>{{end}}` +
+ `<div{{template "y"}}>` +
// Illegal starting in stateTag but not in stateText.
`{{define "y"}} foo<b{{end}}`,
`"<" in attribute name: " foo<b"`,
},
{
- `{{define "z"}}<script>reverseList = [{{template "t"}}]</script>{{end}}` +
+ `<script>reverseList = [{{template "t"}}]</script>` +
// Missing " after recursive call.
`{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`,
`: cannot compute output context for template t$htmltemplate_stateJS_elementScript`,
@@ -957,21 +969,13 @@ func TestErrors(t *testing.T) {
}
for _, test := range tests {
- var err error
buf := new(bytes.Buffer)
- if strings.HasPrefix(test.input, "{{define") {
- var s *Set
- s, err = (&Set{}).Parse(test.input)
- if err == nil {
- err = s.Execute(buf, "z", nil)
- }
- } else {
- var t *Template
- t, err = New("z").Parse(test.input)
- if err == nil {
- err = t.Execute(buf, nil)
- }
+ tmpl, err := New("z").Parse(test.input)
+ if err != nil {
+ t.Errorf("input=%q: unexpected parse error %s\n", test.input, err)
+ continue
}
+ err = tmpl.Execute(buf, nil)
var got string
if err != nil {
got = err.Error()
@@ -1559,11 +1563,11 @@ func TestEscapeErrorsNotIgnorable(t *testing.T) {
func TestEscapeSetErrorsNotIgnorable(t *testing.T) {
var b bytes.Buffer
- s, err := (&Set{}).Parse(`{{define "t"}}<a{{end}}`)
+ tmpl, err := New("root").Parse(`{{define "t"}}<a{{end}}`)
if err != nil {
t.Errorf("failed to parse set: %q", err)
}
- err = s.Execute(&b, "t", nil)
+ err = tmpl.ExecuteTemplate(&b, "t", nil)
if err == nil {
t.Errorf("Expected error")
} else if b.Len() != 0 {
@@ -1605,6 +1609,29 @@ func TestRedundantFuncs(t *testing.T) {
}
}
+func TestIndirectPrint(t *testing.T) {
+ a := 3
+ ap := &a
+ b := "hello"
+ bp := &b
+ bpp := &bp
+ tmpl := Must(New("t").Parse(`{{.}}`))
+ var buf bytes.Buffer
+ err := tmpl.Execute(&buf, ap)
+ if err != nil {
+ t.Errorf("Unexpected error: %s", err)
+ } else if buf.String() != "3" {
+ t.Errorf(`Expected "3"; got %q`, buf.String())
+ }
+ buf.Reset()
+ err = tmpl.Execute(&buf, bpp)
+ if err != nil {
+ t.Errorf("Unexpected error: %s", err)
+ } else if buf.String() != "hello" {
+ t.Errorf(`Expected "hello"; got %q`, buf.String())
+ }
+}
+
func BenchmarkEscapedExecute(b *testing.B) {
tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))
var buf bytes.Buffer
diff --git a/libgo/go/html/template/js.go b/libgo/go/html/template/js.go
index 68c53e5ca3b..0e632df4220 100644
--- a/libgo/go/html/template/js.go
+++ b/libgo/go/html/template/js.go
@@ -8,6 +8,7 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "reflect"
"strings"
"unicode/utf8"
)
@@ -117,12 +118,24 @@ var regexpPrecederKeywords = map[string]bool{
"void": true,
}
+var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
+
+// indirectToJSONMarshaler returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil) or an implementation of json.Marshal.
+func indirectToJSONMarshaler(a interface{}) interface{} {
+ v := reflect.ValueOf(a)
+ for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+ v = v.Elem()
+ }
+ return v.Interface()
+}
+
// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
-// nether side-effects nor free variables outside (NaN, Infinity).
+// neither side-effects nor free variables outside (NaN, Infinity).
func jsValEscaper(args ...interface{}) string {
var a interface{}
if len(args) == 1 {
- a = args[0]
+ a = indirectToJSONMarshaler(args[0])
switch t := a.(type) {
case JS:
return string(t)
@@ -135,6 +148,9 @@ func jsValEscaper(args ...interface{}) string {
a = t.String()
}
} else {
+ for i, arg := range args {
+ args[i] = indirectToJSONMarshaler(arg)
+ }
a = fmt.Sprint(args...)
}
// TODO: detect cycles before calling Marshal which loops infinitely on
diff --git a/libgo/go/html/template/template.go b/libgo/go/html/template/template.go
index 47334299384..f05ca190f73 100644
--- a/libgo/go/html/template/template.go
+++ b/libgo/go/html/template/template.go
@@ -7,233 +7,257 @@ package template
import (
"fmt"
"io"
+ "io/ioutil"
"path/filepath"
+ "sync"
"text/template"
+ "text/template/parse"
)
-// Set is a specialized template.Set that produces a safe HTML document
-// fragment.
-type Set struct {
- escaped map[string]bool
- template.Set
-}
-
// Template is a specialized template.Template that produces a safe HTML
// document fragment.
type Template struct {
escaped bool
- *template.Template
+ // We could embed the text/template field, but it's safer not to because
+ // we need to keep our version of the name space and the underlying
+ // template's in sync.
+ text *template.Template
+ *nameSpace // common to all associated templates
}
-// Execute applies the named template to the specified data object, writing
-// the output to wr.
-func (s *Set) Execute(wr io.Writer, name string, data interface{}) error {
- if !s.escaped[name] {
- if err := escapeSet(&s.Set, name); err != nil {
- return err
- }
- if s.escaped == nil {
- s.escaped = make(map[string]bool)
+// nameSpace is the data structure shared by all templates in an association.
+type nameSpace struct {
+ mu sync.Mutex
+ set map[string]*Template
+}
+
+// Execute applies a parsed template to the specified data object,
+// writing the output to wr.
+func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
+ t.nameSpace.mu.Lock()
+ if !t.escaped {
+ if err = escapeTemplates(t, t.Name()); err != nil {
+ t.escaped = true
}
- s.escaped[name] = true
}
- return s.Set.Execute(wr, name, data)
+ t.nameSpace.mu.Unlock()
+ if err != nil {
+ return
+ }
+ return t.text.Execute(wr, data)
}
-// Parse parses a string into a set of named templates. Parse may be called
-// multiple times for a given set, adding the templates defined in the string
-// to the set. If a template is redefined, the element in the set is
-// overwritten with the new definition.
-func (set *Set) Parse(src string) (*Set, error) {
- set.escaped = nil
- s, err := set.Set.Parse(src)
- if err != nil {
- return nil, err
+// 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{}) (err error) {
+ t.nameSpace.mu.Lock()
+ tmpl := t.set[name]
+ if tmpl == nil {
+ t.nameSpace.mu.Unlock()
+ return fmt.Errorf("template: no template %q associated with template %q", name, t.Name())
}
- if s != &(set.Set) {
- panic("allocated new set")
+ if !tmpl.escaped {
+ err = escapeTemplates(tmpl, name)
+ }
+ t.nameSpace.mu.Unlock()
+ if err != nil {
+ return
}
- return set, nil
+ return tmpl.text.ExecuteTemplate(wr, name, data)
}
-// Parse parses the template definition string to construct an internal
-// representation of the template for execution.
-func (tmpl *Template) Parse(src string) (*Template, error) {
- tmpl.escaped = false
- t, err := tmpl.Template.Parse(src)
+// Parse parses a string into a template. Nested template definitions
+// will be associated with the top-level template t. Parse may be
+// called multiple times to parse definitions of templates to associate
+// with t. It is an error if a resulting template is non-empty (contains
+// content other than template definitions) and would replace a
+// non-empty template with the same name. (In multiple calls to Parse
+// with the same receiver template, only one call can contain text
+// other than space, comments, and template definitions.)
+func (t *Template) Parse(src string) (*Template, error) {
+ t.nameSpace.mu.Lock()
+ t.escaped = false
+ t.nameSpace.mu.Unlock()
+ ret, err := t.text.Parse(src)
if err != nil {
return nil, err
}
- tmpl.Template = t
- return tmpl, nil
-}
-
-// Execute applies a parsed template to the specified data object,
-// writing the output to wr.
-func (t *Template) Execute(wr io.Writer, data interface{}) error {
- if !t.escaped {
- if err := escape(t.Template); err != nil {
- return 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.
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ for _, v := range ret.Templates() {
+ name := v.Name()
+ tmpl := t.set[name]
+ if tmpl == nil {
+ tmpl = t.new(name)
}
- t.escaped = true
+ tmpl.escaped = false
+ tmpl.text = v
}
- return t.Template.Execute(wr, data)
+ return t, nil
+}
+
+// AddParseTree is unimplemented.
+func (t *Template) AddParseTree(name string, tree *parse.Tree) error {
+ return fmt.Errorf("html/template: AddParseTree unimplemented")
+}
+
+// Clone is unimplemented.
+func (t *Template) Clone(name string) error {
+ return fmt.Errorf("html/template: Add unimplemented")
}
// New allocates a new HTML template with the given name.
func New(name string) *Template {
- return &Template{false, template.New(name)}
+ tmpl := &Template{
+ false,
+ template.New(name),
+ &nameSpace{
+ set: make(map[string]*Template),
+ },
+ }
+ tmpl.set[name] = tmpl
+ return tmpl
}
-// Must panics if err is non-nil in the same way as template.Must.
-func Must(t *Template, err error) *Template {
- t.Template = template.Must(t.Template, err)
- return t
+// New allocates a new HTML template associated with the given one
+// and with the same delimiters. The association, which is transitive,
+// allows one template to invoke another with a {{template}} action.
+func (t *Template) New(name string) *Template {
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ return t.new(name)
}
-// ParseFile creates a new Template and parses the template definition from
-// the named file. The template name is the base name of the file.
-func ParseFile(filename string) (*Template, error) {
- t, err := template.ParseFile(filename)
- if err != nil {
- return nil, err
+// new is the implementation of New, without the lock.
+func (t *Template) new(name string) *Template {
+ tmpl := &Template{
+ false,
+ t.text.New(name),
+ t.nameSpace,
}
- return &Template{false, t}, nil
+ tmpl.set[name] = tmpl
+ return tmpl
}
-// ParseFile reads the template definition from a file and parses it to
-// construct an internal representation of the template for execution.
-// The returned template will be nil if an error occurs.
-func (tmpl *Template) ParseFile(filename string) (*Template, error) {
- t, err := tmpl.Template.ParseFile(filename)
- if err != nil {
- return nil, err
- }
- tmpl.Template = t
- return tmpl, nil
+// Name returns the name of the template.
+func (t *Template) Name() string {
+ return t.text.Name()
}
-// SetMust panics if the error is non-nil just like template.SetMust.
-func SetMust(s *Set, err error) *Set {
- if err != nil {
- template.SetMust(&(s.Set), err)
- }
- return s
+// Funcs adds the elements of the argument map to the template's function map.
+// It panics if a value in the map is not a function with appropriate return
+// type. However, it is legal to overwrite elements of the map. The return
+// value is the template, so calls can be chained.
+func (t *Template) Funcs(funcMap template.FuncMap) *Template {
+ t.text.Funcs(funcMap)
+ return t
}
-// ParseFiles parses the named files into a set of named templates.
-// Each file must be parseable by itself.
-// If an error occurs, parsing stops and the returned set is nil.
-func (set *Set) ParseFiles(filenames ...string) (*Set, error) {
- s, err := set.Set.ParseFiles(filenames...)
- if err != nil {
- return nil, err
- }
- if s != &(set.Set) {
- panic("allocated new set")
- }
- return set, nil
+// Delims sets the action delimiters to the specified strings, to be used in
+// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
+// definitions will inherit the settings. An empty delimiter stands for the
+// corresponding default: {{ or }}.
+// The return value is the template, so calls can be chained.
+func (t *Template) Delims(left, right string) *Template {
+ t.text.Delims(left, right)
+ return t
}
-// ParseSetFiles creates a new Set and parses the set definition from the
-// named files. Each file must be individually parseable.
-func ParseSetFiles(filenames ...string) (*Set, error) {
- set := new(Set)
- s, err := set.Set.ParseFiles(filenames...)
- if err != nil {
- return nil, err
- }
- if s != &(set.Set) {
- panic("allocated new set")
- }
- return set, nil
+// Lookup returns the template with the given name that is associated with t,
+// or nil if there is no such template.
+func (t *Template) Lookup(name string) *Template {
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ return t.set[name]
}
-// ParseGlob parses the set definition from the files identified by the
-// pattern. The pattern is processed by filepath.Glob and must match at
-// least one file.
-// If an error occurs, parsing stops and the returned set is nil.
-func (s *Set) ParseGlob(pattern string) (*Set, error) {
- filenames, err := filepath.Glob(pattern)
- if err != nil {
- return nil, err
- }
- if len(filenames) == 0 {
- return nil, fmt.Errorf("pattern matches no files: %#q", pattern)
- }
- return s.ParseFiles(filenames...)
+// Must panics if err is non-nil in the same way as template.Must.
+func Must(t *Template, err error) *Template {
+ t.text = template.Must(t.text, err)
+ return t
}
-// ParseSetGlob creates a new Set and parses the set definition from the
-// files identified by the pattern. The pattern is processed by filepath.Glob
-// and must match at least one file.
-func ParseSetGlob(pattern string) (*Set, error) {
- set, err := new(Set).ParseGlob(pattern)
- if err != nil {
- return nil, err
- }
- return set, nil
+// ParseFiles creates a new Template and parses the template definitions from
+// the named files. The returned template's name will have the (base) name and
+// (parsed) contents of the first file. There must be at least one file.
+// If an error occurs, parsing stops and the returned *Template is nil.
+func ParseFiles(filenames ...string) (*Template, error) {
+ return parseFiles(nil, filenames...)
}
-// Functions and methods to parse stand-alone template files into a set.
+// ParseFiles parses the named files and associates the resulting templates with
+// t. If an error occurs, parsing stops and the returned template is nil;
+// otherwise it is t. There must be at least one file.
+func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
+ return parseFiles(t, filenames...)
+}
-// ParseTemplateFiles parses the named template files and adds them to the set
-// in the same way as template.ParseTemplateFiles but ensures that templates
-// with upper-case names are contextually-autoescaped.
-func (set *Set) ParseTemplateFiles(filenames ...string) (*Set, error) {
- s, err := set.Set.ParseTemplateFiles(filenames...)
- if err != nil {
- return nil, err
+// 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 len(filenames) == 0 {
+ // Not really a problem, but be consistent.
+ return nil, fmt.Errorf("template: no files named in call to ParseFiles")
}
- if s != &(set.Set) {
- panic("new set allocated")
+ for _, filename := range filenames {
+ b, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ s := string(b)
+ name := filepath.Base(filename)
+ // First template becomes return value if not already defined,
+ // and we use that one for subsequent New calls to associate
+ // all the templates together. Also, if this file has the same name
+ // as t, this file becomes the contents of t, so
+ // t, err := New(name).Funcs(xxx).ParseFiles(name)
+ // works. Otherwise we create a new template associated with t.
+ var tmpl *Template
+ if t == nil {
+ t = New(name)
+ }
+ if name == t.Name() {
+ tmpl = t
+ } else {
+ tmpl = t.New(name)
+ }
+ _, err = tmpl.Parse(s)
+ if err != nil {
+ return nil, err
+ }
}
- return set, nil
-}
-
-// ParseTemplateGlob parses the template files matched by the
-// patern and adds them to the set. Each template will be named
-// the base name of its file.
-// Unlike with ParseGlob, each file should be a stand-alone template
-// definition suitable for Template.Parse (not Set.Parse); that is, the
-// file does not contain {{define}} clauses. ParseTemplateGlob is
-// therefore equivalent to calling the ParseFile function to create
-// individual templates, which are then added to the set.
-// Each file must be parseable by itself.
-// If an error occurs, parsing stops and the returned set is nil.
-func (s *Set) ParseTemplateGlob(pattern string) (*Set, error) {
+ return t, nil
+}
+
+// ParseGlob creates a new Template and parses the template definitions from the
+// files identified by the pattern, which must match at least one file. The
+// returned template will have the (base) name and (parsed) contents of the
+// first file matched by the pattern. ParseGlob is equivalent to calling
+// ParseFiles with the list of files matched by the pattern.
+func ParseGlob(pattern string) (*Template, error) {
+ return parseGlob(nil, pattern)
+}
+
+// ParseGlob parses the template definitions in the files identified by the
+// pattern and associates the resulting templates with t. The pattern is
+// processed by filepath.Glob and must match at least one file. ParseGlob is
+// equivalent to calling t.ParseFiles with the list of files matched by the
+// pattern.
+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) {
filenames, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
- return s.ParseTemplateFiles(filenames...)
-}
-
-// ParseTemplateFiles creates a set by parsing the named files,
-// each of which defines a single template. Each template will be
-// named the base name of its file.
-// Unlike with ParseFiles, each file should be a stand-alone template
-// definition suitable for Template.Parse (not Set.Parse); that is, the
-// file does not contain {{define}} clauses. ParseTemplateFiles is
-// therefore equivalent to calling the ParseFile function to create
-// individual templates, which are then added to the set.
-// Each file must be parseable by itself. Parsing stops if an error is
-// encountered.
-func ParseTemplateFiles(filenames ...string) (*Set, error) {
- return new(Set).ParseTemplateFiles(filenames...)
-}
-
-// ParseTemplateGlob creates a set by parsing the files matched
-// by the pattern, each of which defines a single template. The pattern
-// is processed by filepath.Glob and must match at least one file. Each
-// template will be named the base name of its file.
-// Unlike with ParseGlob, each file should be a stand-alone template
-// definition suitable for Template.Parse (not Set.Parse); that is, the
-// file does not contain {{define}} clauses. ParseTemplateGlob is
-// therefore equivalent to calling the ParseFile function to create
-// individual templates, which are then added to the set.
-// Each file must be parseable by itself. Parsing stops if an error is
-// encountered.
-func ParseTemplateGlob(pattern string) (*Set, error) {
- return new(Set).ParseTemplateGlob(pattern)
+ if len(filenames) == 0 {
+ return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
+ }
+ return parseFiles(t, filenames...)
}