github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/flosch/pongo2.v3/filters_builtin.go (about)

     1  package pongo2
     2  
     3  /* Filters that are provided through yougam/libraries/flosch/pongo2-addons:
     4     ------------------------------------------------------------------
     5  
     6     filesizeformat
     7     slugify
     8     timesince
     9     timeuntil
    10  
    11     Filters that won't be added:
    12     ----------------------------
    13  
    14     get_static_prefix (reason: web-framework specific)
    15     pprint (reason: python-specific)
    16     static (reason: web-framework specific)
    17  
    18     Reconsideration (not implemented yet):
    19     --------------------------------------
    20  
    21     force_escape (reason: not yet needed since this is the behaviour of pongo2's escape filter)
    22     safeseq (reason: same reason as `force_escape`)
    23     unordered_list (python-specific; not sure whether needed or not)
    24     dictsort (python-specific; maybe one could add a filter to sort a list of structs by a specific field name)
    25     dictsortreversed (see dictsort)
    26  */
    27  
    28  import (
    29  	"bytes"
    30  	"fmt"
    31  	"math/rand"
    32  	"net/url"
    33  	"regexp"
    34  	"strconv"
    35  	"strings"
    36  	"time"
    37  	"unicode/utf8"
    38  )
    39  
    40  func init() {
    41  	rand.Seed(time.Now().Unix())
    42  
    43  	RegisterFilter("escape", filterEscape)
    44  	RegisterFilter("safe", filterSafe)
    45  	RegisterFilter("escapejs", filterEscapejs)
    46  
    47  	RegisterFilter("add", filterAdd)
    48  	RegisterFilter("addslashes", filterAddslashes)
    49  	RegisterFilter("capfirst", filterCapfirst)
    50  	RegisterFilter("center", filterCenter)
    51  	RegisterFilter("cut", filterCut)
    52  	RegisterFilter("date", filterDate)
    53  	RegisterFilter("default", filterDefault)
    54  	RegisterFilter("default_if_none", filterDefaultIfNone)
    55  	RegisterFilter("divisibleby", filterDivisibleby)
    56  	RegisterFilter("first", filterFirst)
    57  	RegisterFilter("floatformat", filterFloatformat)
    58  	RegisterFilter("get_digit", filterGetdigit)
    59  	RegisterFilter("iriencode", filterIriencode)
    60  	RegisterFilter("join", filterJoin)
    61  	RegisterFilter("last", filterLast)
    62  	RegisterFilter("length", filterLength)
    63  	RegisterFilter("length_is", filterLengthis)
    64  	RegisterFilter("linebreaks", filterLinebreaks)
    65  	RegisterFilter("linebreaksbr", filterLinebreaksbr)
    66  	RegisterFilter("linenumbers", filterLinenumbers)
    67  	RegisterFilter("ljust", filterLjust)
    68  	RegisterFilter("lower", filterLower)
    69  	RegisterFilter("make_list", filterMakelist)
    70  	RegisterFilter("phone2numeric", filterPhone2numeric)
    71  	RegisterFilter("pluralize", filterPluralize)
    72  	RegisterFilter("random", filterRandom)
    73  	RegisterFilter("removetags", filterRemovetags)
    74  	RegisterFilter("rjust", filterRjust)
    75  	RegisterFilter("slice", filterSlice)
    76  	RegisterFilter("stringformat", filterStringformat)
    77  	RegisterFilter("striptags", filterStriptags)
    78  	RegisterFilter("time", filterDate) // time uses filterDate (same golang-format)
    79  	RegisterFilter("title", filterTitle)
    80  	RegisterFilter("truncatechars", filterTruncatechars)
    81  	RegisterFilter("truncatechars_html", filterTruncatecharsHtml)
    82  	RegisterFilter("truncatewords", filterTruncatewords)
    83  	RegisterFilter("truncatewords_html", filterTruncatewordsHtml)
    84  	RegisterFilter("upper", filterUpper)
    85  	RegisterFilter("urlencode", filterUrlencode)
    86  	RegisterFilter("urlize", filterUrlize)
    87  	RegisterFilter("urlizetrunc", filterUrlizetrunc)
    88  	RegisterFilter("wordcount", filterWordcount)
    89  	RegisterFilter("wordwrap", filterWordwrap)
    90  	RegisterFilter("yesno", filterYesno)
    91  
    92  	RegisterFilter("float", filterFloat)     // pongo-specific
    93  	RegisterFilter("integer", filterInteger) // pongo-specific
    94  }
    95  
    96  func filterTruncatecharsHelper(s string, newLen int) string {
    97  	runes := []rune(s)
    98  	if newLen < len(runes) {
    99  		if newLen >= 3 {
   100  			return fmt.Sprintf("%s...", string(runes[:newLen-3]))
   101  		}
   102  		// Not enough space for the ellipsis
   103  		return string(runes[:newLen])
   104  	}
   105  	return string(runes)
   106  }
   107  
   108  func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) {
   109  	vLen := len(value)
   110  	tag_stack := make([]string, 0)
   111  	idx := 0
   112  
   113  	for idx < vLen && !cond() {
   114  		c, s := utf8.DecodeRuneInString(value[idx:])
   115  		if c == utf8.RuneError {
   116  			idx += s
   117  			continue
   118  		}
   119  
   120  		if c == '<' {
   121  			new_output.WriteRune(c)
   122  			idx += s // consume "<"
   123  
   124  			if idx+1 < vLen {
   125  				if value[idx] == '/' {
   126  					// Close tag
   127  
   128  					new_output.WriteString("/")
   129  
   130  					tag := ""
   131  					idx += 1 // consume "/"
   132  
   133  					for idx < vLen {
   134  						c2, size2 := utf8.DecodeRuneInString(value[idx:])
   135  						if c2 == utf8.RuneError {
   136  							idx += size2
   137  							continue
   138  						}
   139  
   140  						// End of tag found
   141  						if c2 == '>' {
   142  							idx++ // consume ">"
   143  							break
   144  						}
   145  						tag += string(c2)
   146  						idx += size2
   147  					}
   148  
   149  					if len(tag_stack) > 0 {
   150  						// Ideally, the close tag is TOP of tag stack
   151  						// In malformed HTML, it must not be, so iterate through the stack and remove the tag
   152  						for i := len(tag_stack) - 1; i >= 0; i-- {
   153  							if tag_stack[i] == tag {
   154  								// Found the tag
   155  								tag_stack[i] = tag_stack[len(tag_stack)-1]
   156  								tag_stack = tag_stack[:len(tag_stack)-1]
   157  								break
   158  							}
   159  						}
   160  					}
   161  
   162  					new_output.WriteString(tag)
   163  					new_output.WriteString(">")
   164  				} else {
   165  					// Open tag
   166  
   167  					tag := ""
   168  
   169  					params := false
   170  					for idx < vLen {
   171  						c2, size2 := utf8.DecodeRuneInString(value[idx:])
   172  						if c2 == utf8.RuneError {
   173  							idx += size2
   174  							continue
   175  						}
   176  
   177  						new_output.WriteRune(c2)
   178  
   179  						// End of tag found
   180  						if c2 == '>' {
   181  							idx++ // consume ">"
   182  							break
   183  						}
   184  
   185  						if !params {
   186  							if c2 == ' ' {
   187  								params = true
   188  							} else {
   189  								tag += string(c2)
   190  							}
   191  						}
   192  
   193  						idx += size2
   194  					}
   195  
   196  					// Add tag to stack
   197  					tag_stack = append(tag_stack, tag)
   198  				}
   199  			}
   200  		} else {
   201  			idx = fn(c, s, idx)
   202  		}
   203  	}
   204  
   205  	finalize()
   206  
   207  	for i := len(tag_stack) - 1; i >= 0; i-- {
   208  		tag := tag_stack[i]
   209  		// Close everything from the regular tag stack
   210  		new_output.WriteString(fmt.Sprintf("</%s>", tag))
   211  	}
   212  }
   213  
   214  func filterTruncatechars(in *Value, param *Value) (*Value, *Error) {
   215  	s := in.String()
   216  	newLen := param.Integer()
   217  	return AsValue(filterTruncatecharsHelper(s, newLen)), nil
   218  }
   219  
   220  func filterTruncatecharsHtml(in *Value, param *Value) (*Value, *Error) {
   221  	value := in.String()
   222  	newLen := max(param.Integer()-3, 0)
   223  
   224  	new_output := bytes.NewBuffer(nil)
   225  
   226  	textcounter := 0
   227  
   228  	filterTruncateHtmlHelper(value, new_output, func() bool {
   229  		return textcounter >= newLen
   230  	}, func(c rune, s int, idx int) int {
   231  		textcounter++
   232  		new_output.WriteRune(c)
   233  
   234  		return idx + s
   235  	}, func() {
   236  		if textcounter >= newLen && textcounter < len(value) {
   237  			new_output.WriteString("...")
   238  		}
   239  	})
   240  
   241  	return AsSafeValue(new_output.String()), nil
   242  }
   243  
   244  func filterTruncatewords(in *Value, param *Value) (*Value, *Error) {
   245  	words := strings.Fields(in.String())
   246  	n := param.Integer()
   247  	if n <= 0 {
   248  		return AsValue(""), nil
   249  	}
   250  	nlen := min(len(words), n)
   251  	out := make([]string, 0, nlen)
   252  	for i := 0; i < nlen; i++ {
   253  		out = append(out, words[i])
   254  	}
   255  
   256  	if n < len(words) {
   257  		out = append(out, "...")
   258  	}
   259  
   260  	return AsValue(strings.Join(out, " ")), nil
   261  }
   262  
   263  func filterTruncatewordsHtml(in *Value, param *Value) (*Value, *Error) {
   264  	value := in.String()
   265  	newLen := max(param.Integer(), 0)
   266  
   267  	new_output := bytes.NewBuffer(nil)
   268  
   269  	wordcounter := 0
   270  
   271  	filterTruncateHtmlHelper(value, new_output, func() bool {
   272  		return wordcounter >= newLen
   273  	}, func(_ rune, _ int, idx int) int {
   274  		// Get next word
   275  		word_found := false
   276  
   277  		for idx < len(value) {
   278  			c2, size2 := utf8.DecodeRuneInString(value[idx:])
   279  			if c2 == utf8.RuneError {
   280  				idx += size2
   281  				continue
   282  			}
   283  
   284  			if c2 == '<' {
   285  				// HTML tag start, don't consume it
   286  				return idx
   287  			}
   288  
   289  			new_output.WriteRune(c2)
   290  			idx += size2
   291  
   292  			if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
   293  				// Word ends here, stop capturing it now
   294  				break
   295  			} else {
   296  				word_found = true
   297  			}
   298  		}
   299  
   300  		if word_found {
   301  			wordcounter++
   302  		}
   303  
   304  		return idx
   305  	}, func() {
   306  		if wordcounter >= newLen {
   307  			new_output.WriteString("...")
   308  		}
   309  	})
   310  
   311  	return AsSafeValue(new_output.String()), nil
   312  }
   313  
   314  func filterEscape(in *Value, param *Value) (*Value, *Error) {
   315  	output := strings.Replace(in.String(), "&", "&amp;", -1)
   316  	output = strings.Replace(output, ">", "&gt;", -1)
   317  	output = strings.Replace(output, "<", "&lt;", -1)
   318  	output = strings.Replace(output, "\"", "&quot;", -1)
   319  	output = strings.Replace(output, "'", "&#39;", -1)
   320  	return AsValue(output), nil
   321  }
   322  
   323  func filterSafe(in *Value, param *Value) (*Value, *Error) {
   324  	return in, nil // nothing to do here, just to keep track of the safe application
   325  }
   326  
   327  func filterEscapejs(in *Value, param *Value) (*Value, *Error) {
   328  	sin := in.String()
   329  
   330  	var b bytes.Buffer
   331  
   332  	idx := 0
   333  	for idx < len(sin) {
   334  		c, size := utf8.DecodeRuneInString(sin[idx:])
   335  		if c == utf8.RuneError {
   336  			idx += size
   337  			continue
   338  		}
   339  
   340  		if c == '\\' {
   341  			// Escape seq?
   342  			if idx+1 < len(sin) {
   343  				switch sin[idx+1] {
   344  				case 'r':
   345  					b.WriteString(fmt.Sprintf(`\u%04X`, '\r'))
   346  					idx += 2
   347  					continue
   348  				case 'n':
   349  					b.WriteString(fmt.Sprintf(`\u%04X`, '\n'))
   350  					idx += 2
   351  					continue
   352  					/*case '\'':
   353  						b.WriteString(fmt.Sprintf(`\u%04X`, '\''))
   354  						idx += 2
   355  						continue
   356  					case '"':
   357  						b.WriteString(fmt.Sprintf(`\u%04X`, '"'))
   358  						idx += 2
   359  						continue*/
   360  				}
   361  			}
   362  		}
   363  
   364  		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' {
   365  			b.WriteRune(c)
   366  		} else {
   367  			b.WriteString(fmt.Sprintf(`\u%04X`, c))
   368  		}
   369  
   370  		idx += size
   371  	}
   372  
   373  	return AsValue(b.String()), nil
   374  }
   375  
   376  func filterAdd(in *Value, param *Value) (*Value, *Error) {
   377  	if in.IsNumber() && param.IsNumber() {
   378  		if in.IsFloat() || param.IsFloat() {
   379  			return AsValue(in.Float() + param.Float()), nil
   380  		} else {
   381  			return AsValue(in.Integer() + param.Integer()), nil
   382  		}
   383  	}
   384  	// If in/param is not a number, we're relying on the
   385  	// Value's String() convertion and just add them both together
   386  	return AsValue(in.String() + param.String()), nil
   387  }
   388  
   389  func filterAddslashes(in *Value, param *Value) (*Value, *Error) {
   390  	output := strings.Replace(in.String(), "\\", "\\\\", -1)
   391  	output = strings.Replace(output, "\"", "\\\"", -1)
   392  	output = strings.Replace(output, "'", "\\'", -1)
   393  	return AsValue(output), nil
   394  }
   395  
   396  func filterCut(in *Value, param *Value) (*Value, *Error) {
   397  	return AsValue(strings.Replace(in.String(), param.String(), "", -1)), nil
   398  }
   399  
   400  func filterLength(in *Value, param *Value) (*Value, *Error) {
   401  	return AsValue(in.Len()), nil
   402  }
   403  
   404  func filterLengthis(in *Value, param *Value) (*Value, *Error) {
   405  	return AsValue(in.Len() == param.Integer()), nil
   406  }
   407  
   408  func filterDefault(in *Value, param *Value) (*Value, *Error) {
   409  	if !in.IsTrue() {
   410  		return param, nil
   411  	}
   412  	return in, nil
   413  }
   414  
   415  func filterDefaultIfNone(in *Value, param *Value) (*Value, *Error) {
   416  	if in.IsNil() {
   417  		return param, nil
   418  	}
   419  	return in, nil
   420  }
   421  
   422  func filterDivisibleby(in *Value, param *Value) (*Value, *Error) {
   423  	if param.Integer() == 0 {
   424  		return AsValue(false), nil
   425  	}
   426  	return AsValue(in.Integer()%param.Integer() == 0), nil
   427  }
   428  
   429  func filterFirst(in *Value, param *Value) (*Value, *Error) {
   430  	if in.CanSlice() && in.Len() > 0 {
   431  		return in.Index(0), nil
   432  	}
   433  	return AsValue(""), nil
   434  }
   435  
   436  func filterFloatformat(in *Value, param *Value) (*Value, *Error) {
   437  	val := in.Float()
   438  
   439  	decimals := -1
   440  	if !param.IsNil() {
   441  		// Any argument provided?
   442  		decimals = param.Integer()
   443  	}
   444  
   445  	// if the argument is not a number (e. g. empty), the default
   446  	// behaviour is trim the result
   447  	trim := !param.IsNumber()
   448  
   449  	if decimals <= 0 {
   450  		// argument is negative or zero, so we
   451  		// want the output being trimmed
   452  		decimals = -decimals
   453  		trim = true
   454  	}
   455  
   456  	if trim {
   457  		// Remove zeroes
   458  		if float64(int(val)) == val {
   459  			return AsValue(in.Integer()), nil
   460  		}
   461  	}
   462  
   463  	return AsValue(strconv.FormatFloat(val, 'f', decimals, 64)), nil
   464  }
   465  
   466  func filterGetdigit(in *Value, param *Value) (*Value, *Error) {
   467  	i := param.Integer()
   468  	l := len(in.String()) // do NOT use in.Len() here!
   469  	if i <= 0 || i > l {
   470  		return in, nil
   471  	}
   472  	return AsValue(in.String()[l-i] - 48), nil
   473  }
   474  
   475  const filterIRIChars = "/#%[]=:;$&()+,!?*@'~"
   476  
   477  func filterIriencode(in *Value, param *Value) (*Value, *Error) {
   478  	var b bytes.Buffer
   479  
   480  	sin := in.String()
   481  	for _, r := range sin {
   482  		if strings.IndexRune(filterIRIChars, r) >= 0 {
   483  			b.WriteRune(r)
   484  		} else {
   485  			b.WriteString(url.QueryEscape(string(r)))
   486  		}
   487  	}
   488  
   489  	return AsValue(b.String()), nil
   490  }
   491  
   492  func filterJoin(in *Value, param *Value) (*Value, *Error) {
   493  	if !in.CanSlice() {
   494  		return in, nil
   495  	}
   496  	sep := param.String()
   497  	sl := make([]string, 0, in.Len())
   498  	for i := 0; i < in.Len(); i++ {
   499  		sl = append(sl, in.Index(i).String())
   500  	}
   501  	return AsValue(strings.Join(sl, sep)), nil
   502  }
   503  
   504  func filterLast(in *Value, param *Value) (*Value, *Error) {
   505  	if in.CanSlice() && in.Len() > 0 {
   506  		return in.Index(in.Len() - 1), nil
   507  	}
   508  	return AsValue(""), nil
   509  }
   510  
   511  func filterUpper(in *Value, param *Value) (*Value, *Error) {
   512  	return AsValue(strings.ToUpper(in.String())), nil
   513  }
   514  
   515  func filterLower(in *Value, param *Value) (*Value, *Error) {
   516  	return AsValue(strings.ToLower(in.String())), nil
   517  }
   518  
   519  func filterMakelist(in *Value, param *Value) (*Value, *Error) {
   520  	s := in.String()
   521  	result := make([]string, 0, len(s))
   522  	for _, c := range s {
   523  		result = append(result, string(c))
   524  	}
   525  	return AsValue(result), nil
   526  }
   527  
   528  func filterCapfirst(in *Value, param *Value) (*Value, *Error) {
   529  	if in.Len() <= 0 {
   530  		return AsValue(""), nil
   531  	}
   532  	t := in.String()
   533  	r, size := utf8.DecodeRuneInString(t)
   534  	return AsValue(strings.ToUpper(string(r)) + t[size:]), nil
   535  }
   536  
   537  func filterCenter(in *Value, param *Value) (*Value, *Error) {
   538  	width := param.Integer()
   539  	slen := in.Len()
   540  	if width <= slen {
   541  		return in, nil
   542  	}
   543  
   544  	spaces := width - slen
   545  	left := spaces/2 + spaces%2
   546  	right := spaces / 2
   547  
   548  	return AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left),
   549  		in.String(), strings.Repeat(" ", right))), nil
   550  }
   551  
   552  func filterDate(in *Value, param *Value) (*Value, *Error) {
   553  	t, is_time := in.Interface().(time.Time)
   554  	if !is_time {
   555  		return nil, &Error{
   556  			Sender:   "filter:date",
   557  			ErrorMsg: "Filter input argument must be of type 'time.Time'.",
   558  		}
   559  	}
   560  	return AsValue(t.Format(param.String())), nil
   561  }
   562  
   563  func filterFloat(in *Value, param *Value) (*Value, *Error) {
   564  	return AsValue(in.Float()), nil
   565  }
   566  
   567  func filterInteger(in *Value, param *Value) (*Value, *Error) {
   568  	return AsValue(in.Integer()), nil
   569  }
   570  
   571  func filterLinebreaks(in *Value, param *Value) (*Value, *Error) {
   572  	if in.Len() == 0 {
   573  		return in, nil
   574  	}
   575  
   576  	var b bytes.Buffer
   577  
   578  	// Newline = <br />
   579  	// Double newline = <p>...</p>
   580  	lines := strings.Split(in.String(), "\n")
   581  	lenlines := len(lines)
   582  
   583  	opened := false
   584  
   585  	for idx, line := range lines {
   586  
   587  		if !opened {
   588  			b.WriteString("<p>")
   589  			opened = true
   590  		}
   591  
   592  		b.WriteString(line)
   593  
   594  		if idx < lenlines-1 && strings.TrimSpace(lines[idx]) != "" {
   595  			// We've not reached the end
   596  			if strings.TrimSpace(lines[idx+1]) == "" {
   597  				// Next line is empty
   598  				if opened {
   599  					b.WriteString("</p>")
   600  					opened = false
   601  				}
   602  			} else {
   603  				b.WriteString("<br />")
   604  			}
   605  		}
   606  	}
   607  
   608  	if opened {
   609  		b.WriteString("</p>")
   610  	}
   611  
   612  	return AsValue(b.String()), nil
   613  }
   614  
   615  func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) {
   616  	return AsValue(strings.Replace(in.String(), "\n", "<br />", -1)), nil
   617  }
   618  
   619  func filterLinenumbers(in *Value, param *Value) (*Value, *Error) {
   620  	lines := strings.Split(in.String(), "\n")
   621  	output := make([]string, 0, len(lines))
   622  	for idx, line := range lines {
   623  		output = append(output, fmt.Sprintf("%d. %s", idx+1, line))
   624  	}
   625  	return AsValue(strings.Join(output, "\n")), nil
   626  }
   627  
   628  func filterLjust(in *Value, param *Value) (*Value, *Error) {
   629  	times := param.Integer() - in.Len()
   630  	if times < 0 {
   631  		times = 0
   632  	}
   633  	return AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times))), nil
   634  }
   635  
   636  func filterUrlencode(in *Value, param *Value) (*Value, *Error) {
   637  	return AsValue(url.QueryEscape(in.String())), nil
   638  }
   639  
   640  // TODO: This regexp could do some work
   641  var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`)
   642  var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`)
   643  
   644  func filterUrlizeHelper(input string, autoescape bool, trunc int) string {
   645  	sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string {
   646  		var prefix string
   647  		var suffix string
   648  		if strings.HasPrefix(raw_url, " ") {
   649  			prefix = " "
   650  		}
   651  		if strings.HasSuffix(raw_url, " ") {
   652  			suffix = " "
   653  		}
   654  
   655  		raw_url = strings.TrimSpace(raw_url)
   656  
   657  		t, err := ApplyFilter("iriencode", AsValue(raw_url), nil)
   658  		if err != nil {
   659  			panic(err)
   660  		}
   661  		url := t.String()
   662  
   663  		if !strings.HasPrefix(url, "http") {
   664  			url = fmt.Sprintf("http://%s", url)
   665  		}
   666  
   667  		title := raw_url
   668  
   669  		if trunc > 3 && len(title) > trunc {
   670  			title = fmt.Sprintf("%s...", title[:trunc-3])
   671  		}
   672  
   673  		if autoescape {
   674  			t, err := ApplyFilter("escape", AsValue(title), nil)
   675  			if err != nil {
   676  				panic(err)
   677  			}
   678  			title = t.String()
   679  		}
   680  
   681  		return fmt.Sprintf(`%s<a href="%s" rel="nofollow">%s</a>%s`, prefix, url, title, suffix)
   682  	})
   683  
   684  	sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string {
   685  
   686  		title := mail
   687  
   688  		if trunc > 3 && len(title) > trunc {
   689  			title = fmt.Sprintf("%s...", title[:trunc-3])
   690  		}
   691  
   692  		return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title)
   693  	})
   694  
   695  	return sout
   696  }
   697  
   698  func filterUrlize(in *Value, param *Value) (*Value, *Error) {
   699  	autoescape := true
   700  	if param.IsBool() {
   701  		autoescape = param.Bool()
   702  	}
   703  
   704  	return AsValue(filterUrlizeHelper(in.String(), autoescape, -1)), nil
   705  }
   706  
   707  func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) {
   708  	return AsValue(filterUrlizeHelper(in.String(), true, param.Integer())), nil
   709  }
   710  
   711  func filterStringformat(in *Value, param *Value) (*Value, *Error) {
   712  	return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil
   713  }
   714  
   715  var re_striptags = regexp.MustCompile("<[^>]*?>")
   716  
   717  func filterStriptags(in *Value, param *Value) (*Value, *Error) {
   718  	s := in.String()
   719  
   720  	// Strip all tags
   721  	s = re_striptags.ReplaceAllString(s, "")
   722  
   723  	return AsValue(strings.TrimSpace(s)), nil
   724  }
   725  
   726  // https://en.wikipedia.org/wiki/Phoneword
   727  var filterPhone2numericMap = map[string]string{
   728  	"a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5",
   729  	"l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8",
   730  	"w": "9", "x": "9", "y": "9", "z": "9",
   731  }
   732  
   733  func filterPhone2numeric(in *Value, param *Value) (*Value, *Error) {
   734  	sin := in.String()
   735  	for k, v := range filterPhone2numericMap {
   736  		sin = strings.Replace(sin, k, v, -1)
   737  		sin = strings.Replace(sin, strings.ToUpper(k), v, -1)
   738  	}
   739  	return AsValue(sin), nil
   740  }
   741  
   742  func filterPluralize(in *Value, param *Value) (*Value, *Error) {
   743  	if in.IsNumber() {
   744  		// Works only on numbers
   745  		if param.Len() > 0 {
   746  			endings := strings.Split(param.String(), ",")
   747  			if len(endings) > 2 {
   748  				return nil, &Error{
   749  					Sender:   "filter:pluralize",
   750  					ErrorMsg: "You cannot pass more than 2 arguments to filter 'pluralize'.",
   751  				}
   752  			}
   753  			if len(endings) == 1 {
   754  				// 1 argument
   755  				if in.Integer() != 1 {
   756  					return AsValue(endings[0]), nil
   757  				}
   758  			} else {
   759  				if in.Integer() != 1 {
   760  					// 2 arguments
   761  					return AsValue(endings[1]), nil
   762  				}
   763  				return AsValue(endings[0]), nil
   764  			}
   765  		} else {
   766  			if in.Integer() != 1 {
   767  				// return default 's'
   768  				return AsValue("s"), nil
   769  			}
   770  		}
   771  
   772  		return AsValue(""), nil
   773  	} else {
   774  		return nil, &Error{
   775  			Sender:   "filter:pluralize",
   776  			ErrorMsg: "Filter 'pluralize' does only work on numbers.",
   777  		}
   778  	}
   779  }
   780  
   781  func filterRandom(in *Value, param *Value) (*Value, *Error) {
   782  	if !in.CanSlice() || in.Len() <= 0 {
   783  		return in, nil
   784  	}
   785  	i := rand.Intn(in.Len())
   786  	return in.Index(i), nil
   787  }
   788  
   789  func filterRemovetags(in *Value, param *Value) (*Value, *Error) {
   790  	s := in.String()
   791  	tags := strings.Split(param.String(), ",")
   792  
   793  	// Strip only specific tags
   794  	for _, tag := range tags {
   795  		re := regexp.MustCompile(fmt.Sprintf("</?%s/?>", tag))
   796  		s = re.ReplaceAllString(s, "")
   797  	}
   798  
   799  	return AsValue(strings.TrimSpace(s)), nil
   800  }
   801  
   802  func filterRjust(in *Value, param *Value) (*Value, *Error) {
   803  	return AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", param.Integer()), in.String())), nil
   804  }
   805  
   806  func filterSlice(in *Value, param *Value) (*Value, *Error) {
   807  	comp := strings.Split(param.String(), ":")
   808  	if len(comp) != 2 {
   809  		return nil, &Error{
   810  			Sender:   "filter:slice",
   811  			ErrorMsg: "Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]",
   812  		}
   813  	}
   814  
   815  	if !in.CanSlice() {
   816  		return in, nil
   817  	}
   818  
   819  	from := AsValue(comp[0]).Integer()
   820  	to := in.Len()
   821  
   822  	if from > to {
   823  		from = to
   824  	}
   825  
   826  	vto := AsValue(comp[1]).Integer()
   827  	if vto >= from && vto <= in.Len() {
   828  		to = vto
   829  	}
   830  
   831  	return in.Slice(from, to), nil
   832  }
   833  
   834  func filterTitle(in *Value, param *Value) (*Value, *Error) {
   835  	if !in.IsString() {
   836  		return AsValue(""), nil
   837  	}
   838  	return AsValue(strings.Title(strings.ToLower(in.String()))), nil
   839  }
   840  
   841  func filterWordcount(in *Value, param *Value) (*Value, *Error) {
   842  	return AsValue(len(strings.Fields(in.String()))), nil
   843  }
   844  
   845  func filterWordwrap(in *Value, param *Value) (*Value, *Error) {
   846  	words := strings.Fields(in.String())
   847  	words_len := len(words)
   848  	wrap_at := param.Integer()
   849  	if wrap_at <= 0 {
   850  		return in, nil
   851  	}
   852  
   853  	linecount := words_len/wrap_at + words_len%wrap_at
   854  	lines := make([]string, 0, linecount)
   855  	for i := 0; i < linecount; i++ {
   856  		lines = append(lines, strings.Join(words[wrap_at*i:min(wrap_at*(i+1), words_len)], " "))
   857  	}
   858  	return AsValue(strings.Join(lines, "\n")), nil
   859  }
   860  
   861  func filterYesno(in *Value, param *Value) (*Value, *Error) {
   862  	choices := map[int]string{
   863  		0: "yes",
   864  		1: "no",
   865  		2: "maybe",
   866  	}
   867  	param_string := param.String()
   868  	custom_choices := strings.Split(param_string, ",")
   869  	if len(param_string) > 0 {
   870  		if len(custom_choices) > 3 {
   871  			return nil, &Error{
   872  				Sender:   "filter:yesno",
   873  				ErrorMsg: fmt.Sprintf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", param_string),
   874  			}
   875  		}
   876  		if len(custom_choices) < 2 {
   877  			return nil, &Error{
   878  				Sender:   "filter:yesno",
   879  				ErrorMsg: fmt.Sprintf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", param_string),
   880  			}
   881  		}
   882  
   883  		// Map to the options now
   884  		choices[0] = custom_choices[0]
   885  		choices[1] = custom_choices[1]
   886  		if len(custom_choices) == 3 {
   887  			choices[2] = custom_choices[2]
   888  		}
   889  	}
   890  
   891  	// maybe
   892  	if in.IsNil() {
   893  		return AsValue(choices[2]), nil
   894  	}
   895  
   896  	// yes
   897  	if in.IsTrue() {
   898  		return AsValue(choices[0]), nil
   899  	}
   900  
   901  	// no
   902  	return AsValue(choices[1]), nil
   903  }