| // Copyright 2011 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 | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "io" | |
| "reflect" | |
| "strings" | |
| "unicode" | |
| "url" | |
| "utf8" | |
| ) | |
| // FuncMap is the type of the map defining the mapping from names to functions. | |
| // Each function must have either a single return value, or two return values of | |
| // which the second has type error. If the second argument evaluates to non-nil | |
| // during execution, execution terminates and Execute returns an error. | |
| type FuncMap map[string]interface{} | |
| var builtins = FuncMap{ | |
| "and": and, | |
| "html": HTMLEscaper, | |
| "index": index, | |
| "js": JSEscaper, | |
| "len": length, | |
| "not": not, | |
| "or": or, | |
| "print": fmt.Sprint, | |
| "printf": fmt.Sprintf, | |
| "println": fmt.Sprintln, | |
| "urlquery": URLQueryEscaper, | |
| } | |
| var builtinFuncs = createValueFuncs(builtins) | |
| // createValueFuncs turns a FuncMap into a map[string]reflect.Value | |
| func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { | |
| m := make(map[string]reflect.Value) | |
| addValueFuncs(m, funcMap) | |
| return m | |
| } | |
| // addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. | |
| func addValueFuncs(out map[string]reflect.Value, in FuncMap) { | |
| for name, fn := range in { | |
| v := reflect.ValueOf(fn) | |
| if v.Kind() != reflect.Func { | |
| panic("value for " + name + " not a function") | |
| } | |
| if !goodFunc(v.Type()) { | |
| panic(fmt.Errorf("can't handle multiple results from method/function %q", name)) | |
| } | |
| out[name] = v | |
| } | |
| } | |
| // addFuncs adds to values the functions in funcs. It does no checking of the input - | |
| // call addValueFuncs first. | |
| func addFuncs(out, in FuncMap) { | |
| for name, fn := range in { | |
| out[name] = fn | |
| } | |
| } | |
| // goodFunc checks that the function or method has the right result signature. | |
| func goodFunc(typ reflect.Type) bool { | |
| // We allow functions with 1 result or 2 results where the second is an error. | |
| switch { | |
| case typ.NumOut() == 1: | |
| return true | |
| case typ.NumOut() == 2 && typ.Out(1) == errorType: | |
| return true | |
| } | |
| return false | |
| } | |
| // findFunction looks for a function in the template, set, and global map. | |
| func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) { | |
| if tmpl != nil { | |
| if fn := tmpl.execFuncs[name]; fn.IsValid() { | |
| return fn, true | |
| } | |
| } | |
| if set != nil { | |
| if fn := set.execFuncs[name]; fn.IsValid() { | |
| return fn, true | |
| } | |
| } | |
| if fn := builtinFuncs[name]; fn.IsValid() { | |
| return fn, true | |
| } | |
| return reflect.Value{}, false | |
| } | |
| // Indexing. | |
| // index returns the result of indexing its first argument by the following | |
| // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each | |
| // indexed item must be a map, slice, or array. | |
| func index(item interface{}, indices ...interface{}) (interface{}, error) { | |
| v := reflect.ValueOf(item) | |
| for _, i := range indices { | |
| index := reflect.ValueOf(i) | |
| var isNil bool | |
| if v, isNil = indirect(v); isNil { | |
| return nil, fmt.Errorf("index of nil pointer") | |
| } | |
| switch v.Kind() { | |
| case reflect.Array, reflect.Slice: | |
| var x int64 | |
| switch index.Kind() { | |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| x = index.Int() | |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | |
| x = int64(index.Uint()) | |
| default: | |
| return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) | |
| } | |
| if x < 0 || x >= int64(v.Len()) { | |
| return nil, fmt.Errorf("index out of range: %d", x) | |
| } | |
| v = v.Index(int(x)) | |
| case reflect.Map: | |
| if !index.Type().AssignableTo(v.Type().Key()) { | |
| return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type()) | |
| } | |
| if x := v.MapIndex(index); x.IsValid() { | |
| v = x | |
| } else { | |
| v = reflect.Zero(v.Type().Key()) | |
| } | |
| default: | |
| return nil, fmt.Errorf("can't index item of type %s", index.Type()) | |
| } | |
| } | |
| return v.Interface(), nil | |
| } | |
| // Length | |
| // length returns the length of the item, with an error if it has no defined length. | |
| func length(item interface{}) (int, error) { | |
| v, isNil := indirect(reflect.ValueOf(item)) | |
| if isNil { | |
| return 0, fmt.Errorf("len of nil pointer") | |
| } | |
| switch v.Kind() { | |
| case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: | |
| return v.Len(), nil | |
| } | |
| return 0, fmt.Errorf("len of type %s", v.Type()) | |
| } | |
| // Boolean logic. | |
| func truth(a interface{}) bool { | |
| t, _ := isTrue(reflect.ValueOf(a)) | |
| return t | |
| } | |
| // and computes the Boolean AND of its arguments, returning | |
| // the first false argument it encounters, or the last argument. | |
| func and(arg0 interface{}, args ...interface{}) interface{} { | |
| if !truth(arg0) { | |
| return arg0 | |
| } | |
| for i := range args { | |
| arg0 = args[i] | |
| if !truth(arg0) { | |
| break | |
| } | |
| } | |
| return arg0 | |
| } | |
| // or computes the Boolean OR of its arguments, returning | |
| // the first true argument it encounters, or the last argument. | |
| func or(arg0 interface{}, args ...interface{}) interface{} { | |
| if truth(arg0) { | |
| return arg0 | |
| } | |
| for i := range args { | |
| arg0 = args[i] | |
| if truth(arg0) { | |
| break | |
| } | |
| } | |
| return arg0 | |
| } | |
| // not returns the Boolean negation of its argument. | |
| func not(arg interface{}) (truth bool) { | |
| truth, _ = isTrue(reflect.ValueOf(arg)) | |
| return !truth | |
| } | |
| // HTML escaping. | |
| var ( | |
| htmlQuot = []byte(""") // shorter than """ | |
| htmlApos = []byte("'") // shorter than "'" | |
| htmlAmp = []byte("&") | |
| htmlLt = []byte("<") | |
| htmlGt = []byte(">") | |
| ) | |
| // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. | |
| func HTMLEscape(w io.Writer, b []byte) { | |
| last := 0 | |
| for i, c := range b { | |
| var html []byte | |
| switch c { | |
| case '"': | |
| html = htmlQuot | |
| case '\'': | |
| html = htmlApos | |
| case '&': | |
| html = htmlAmp | |
| case '<': | |
| html = htmlLt | |
| case '>': | |
| html = htmlGt | |
| default: | |
| continue | |
| } | |
| w.Write(b[last:i]) | |
| w.Write(html) | |
| last = i + 1 | |
| } | |
| w.Write(b[last:]) | |
| } | |
| // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. | |
| func HTMLEscapeString(s string) string { | |
| // Avoid allocation if we can. | |
| if strings.IndexAny(s, `'"&<>`) < 0 { | |
| return s | |
| } | |
| var b bytes.Buffer | |
| HTMLEscape(&b, []byte(s)) | |
| return b.String() | |
| } | |
| // HTMLEscaper returns the escaped HTML equivalent of the textual | |
| // representation of its arguments. | |
| func HTMLEscaper(args ...interface{}) string { | |
| ok := false | |
| var s string | |
| if len(args) == 1 { | |
| s, ok = args[0].(string) | |
| } | |
| if !ok { | |
| s = fmt.Sprint(args...) | |
| } | |
| return HTMLEscapeString(s) | |
| } | |
| // JavaScript escaping. | |
| var ( | |
| jsLowUni = []byte(`\u00`) | |
| hex = []byte("0123456789ABCDEF") | |
| jsBackslash = []byte(`\\`) | |
| jsApos = []byte(`\'`) | |
| jsQuot = []byte(`\"`) | |
| jsLt = []byte(`\x3C`) | |
| jsGt = []byte(`\x3E`) | |
| ) | |
| // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. | |
| func JSEscape(w io.Writer, b []byte) { | |
| last := 0 | |
| for i := 0; i < len(b); i++ { | |
| c := b[i] | |
| if !jsIsSpecial(rune(c)) { | |
| // fast path: nothing to do | |
| continue | |
| } | |
| w.Write(b[last:i]) | |
| if c < utf8.RuneSelf { | |
| // Quotes, slashes and angle brackets get quoted. | |
| // Control characters get written as \u00XX. | |
| switch c { | |
| case '\\': | |
| w.Write(jsBackslash) | |
| case '\'': | |
| w.Write(jsApos) | |
| case '"': | |
| w.Write(jsQuot) | |
| case '<': | |
| w.Write(jsLt) | |
| case '>': | |
| w.Write(jsGt) | |
| default: | |
| w.Write(jsLowUni) | |
| t, b := c>>4, c&0x0f | |
| w.Write(hex[t : t+1]) | |
| w.Write(hex[b : b+1]) | |
| } | |
| } else { | |
| // Unicode rune. | |
| r, size := utf8.DecodeRune(b[i:]) | |
| if unicode.IsPrint(r) { | |
| w.Write(b[i : i+size]) | |
| } else { | |
| // TODO(dsymonds): Do this without fmt? | |
| fmt.Fprintf(w, "\\u%04X", r) | |
| } | |
| i += size - 1 | |
| } | |
| last = i + 1 | |
| } | |
| w.Write(b[last:]) | |
| } | |
| // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. | |
| func JSEscapeString(s string) string { | |
| // Avoid allocation if we can. | |
| if strings.IndexFunc(s, jsIsSpecial) < 0 { | |
| return s | |
| } | |
| var b bytes.Buffer | |
| JSEscape(&b, []byte(s)) | |
| return b.String() | |
| } | |
| func jsIsSpecial(r rune) bool { | |
| switch r { | |
| case '\\', '\'', '"', '<', '>': | |
| return true | |
| } | |
| return r < ' ' || utf8.RuneSelf <= r | |
| } | |
| // JSEscaper returns the escaped JavaScript equivalent of the textual | |
| // representation of its arguments. | |
| func JSEscaper(args ...interface{}) string { | |
| ok := false | |
| var s string | |
| if len(args) == 1 { | |
| s, ok = args[0].(string) | |
| } | |
| if !ok { | |
| s = fmt.Sprint(args...) | |
| } | |
| return JSEscapeString(s) | |
| } | |
| // URLQueryEscaper returns the escaped value of the textual representation of | |
| // its arguments in a form suitable for embedding in a URL query. | |
| func URLQueryEscaper(args ...interface{}) string { | |
| s, ok := "", false | |
| if len(args) == 1 { | |
| s, ok = args[0].(string) | |
| } | |
| if !ok { | |
| s = fmt.Sprint(args...) | |
| } | |
| return url.QueryEscape(s) | |
| } |