| // 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" |
| "net/url" |
| "reflect" |
| "strings" |
| "unicode" |
| "unicode/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. In that case, if the second (error) |
| // argument evaluates to non-nil during execution, execution terminates and |
| // Execute returns that 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, and global map. |
| func findFunction(name string, tmpl *Template) (reflect.Value, bool) { |
| if tmpl != nil && tmpl.common != nil { |
| if fn := tmpl.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 { |
| 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) |
| } |