1 package django
2
3 import (
4 "bytes"
5 "fmt"
6 "math/rand"
7 "regexp"
8 "strconv"
9 "strings"
10 "time"
11 "unicode/utf8"
12
13 "github.com/pkg/errors"
14
15 "github.com/noirbizarre/gonja/exec"
16 u "github.com/noirbizarre/gonja/utils"
17 )
18
19 func init() {
20 rand.Seed(time.Now().Unix())
21 }
22
23 var Filters = exec.FilterSet{
24 "escapejs": filterEscapejs,
25 "add": filterAdd,
26 "addslashes": filterAddslashes,
27 "capfirst": filterCapfirst,
28 "cut": filterCut,
29 "date": filterDate,
30 "default_if_none": filterDefaultIfNone,
31 "floatformat": filterFloatformat,
32 "get_digit": filterGetdigit,
33 "iriencode": filterIriencode,
34 "length_is": filterLengthis,
35 "linebreaks": filterLinebreaks,
36 "linebreaksbr": filterLinebreaksbr,
37 "linenumbers": filterLinenumbers,
38 "ljust": filterLjust,
39 "make_list": filterMakelist,
40 "phone2numeric": filterPhone2numeric,
41 "pluralize": filterPluralize,
42 "removetags": filterRemovetags,
43 "rjust": filterRjust,
44 "split": filterSplit,
45 "stringformat": filterStringformat,
46 "time": filterDate,
47 "truncatechars": filterTruncatechars,
48 "truncatechars_html": filterTruncatecharsHTML,
49 "truncatewords": filterTruncatewords,
50 "truncatewords_html": filterTruncatewordsHTML,
51 "yesno": filterYesno,
52 }
53
54 func filterTruncatecharsHelper(s string, newLen int) string {
55 runes := []rune(s)
56 if newLen < len(runes) {
57 if newLen >= 3 {
58 return fmt.Sprintf("%s...", string(runes[:newLen-3]))
59 }
60
61 return string(runes[:newLen])
62 }
63 return string(runes)
64 }
65
66 func filterTruncateHTMLHelper(value string, newOutput *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) {
67 vLen := len(value)
68 var tagStack []string
69 idx := 0
70
71 for idx < vLen && !cond() {
72 c, s := utf8.DecodeRuneInString(value[idx:])
73 if c == utf8.RuneError {
74 idx += s
75 continue
76 }
77
78 if c == '<' {
79 newOutput.WriteRune(c)
80 idx += s
81
82 if idx+1 < vLen {
83 if value[idx] == '/' {
84
85
86 newOutput.WriteString("/")
87
88 tag := ""
89 idx++
90
91 for idx < vLen {
92 c2, size2 := utf8.DecodeRuneInString(value[idx:])
93 if c2 == utf8.RuneError {
94 idx += size2
95 continue
96 }
97
98
99 if c2 == '>' {
100 idx++
101 break
102 }
103 tag += string(c2)
104 idx += size2
105 }
106
107 if len(tagStack) > 0 {
108
109
110 for i := len(tagStack) - 1; i >= 0; i-- {
111 if tagStack[i] == tag {
112
113 tagStack[i] = tagStack[len(tagStack)-1]
114 tagStack = tagStack[:len(tagStack)-1]
115 break
116 }
117 }
118 }
119
120 newOutput.WriteString(tag)
121 newOutput.WriteString(">")
122 } else {
123
124
125 tag := ""
126
127 params := false
128 for idx < vLen {
129 c2, size2 := utf8.DecodeRuneInString(value[idx:])
130 if c2 == utf8.RuneError {
131 idx += size2
132 continue
133 }
134
135 newOutput.WriteRune(c2)
136
137
138 if c2 == '>' {
139 idx++
140 break
141 }
142
143 if !params {
144 if c2 == ' ' {
145 params = true
146 } else {
147 tag += string(c2)
148 }
149 }
150
151 idx += size2
152 }
153
154
155 tagStack = append(tagStack, tag)
156 }
157 }
158 } else {
159 idx = fn(c, s, idx)
160 }
161 }
162
163 finalize()
164
165 for i := len(tagStack) - 1; i >= 0; i-- {
166 tag := tagStack[i]
167
168 newOutput.WriteString(fmt.Sprintf("</%s>", tag))
169 }
170 }
171
172 func filterTruncatechars(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
173 s := in.String()
174 newLen := params.Args[0].Integer()
175 return exec.AsValue(filterTruncatecharsHelper(s, newLen))
176 }
177
178 func filterTruncatecharsHTML(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
179 value := in.String()
180 newLen := u.Max(params.Args[0].Integer()-3, 0)
181
182 newOutput := bytes.NewBuffer(nil)
183
184 textcounter := 0
185
186 filterTruncateHTMLHelper(value, newOutput, func() bool {
187 return textcounter >= newLen
188 }, func(c rune, s int, idx int) int {
189 textcounter++
190 newOutput.WriteRune(c)
191
192 return idx + s
193 }, func() {
194 if textcounter >= newLen && textcounter < len(value) {
195 newOutput.WriteString("...")
196 }
197 })
198
199 return exec.AsSafeValue(newOutput.String())
200 }
201
202 func filterTruncatewords(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
203 words := strings.Fields(in.String())
204 n := params.Args[0].Integer()
205 if n <= 0 {
206 return exec.AsValue("")
207 }
208 nlen := u.Min(len(words), n)
209 out := make([]string, 0, nlen)
210 for i := 0; i < nlen; i++ {
211 out = append(out, words[i])
212 }
213
214 if n < len(words) {
215 out = append(out, "...")
216 }
217
218 return exec.AsValue(strings.Join(out, " "))
219 }
220
221 func filterTruncatewordsHTML(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
222 value := in.String()
223 newLen := u.Max(params.Args[0].Integer(), 0)
224
225 newOutput := bytes.NewBuffer(nil)
226
227 wordcounter := 0
228
229 filterTruncateHTMLHelper(value, newOutput, func() bool {
230 return wordcounter >= newLen
231 }, func(_ rune, _ int, idx int) int {
232
233 wordFound := false
234
235 for idx < len(value) {
236 c2, size2 := utf8.DecodeRuneInString(value[idx:])
237 if c2 == utf8.RuneError {
238 idx += size2
239 continue
240 }
241
242 if c2 == '<' {
243
244 return idx
245 }
246
247 newOutput.WriteRune(c2)
248 idx += size2
249
250 if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
251
252 break
253 } else {
254 wordFound = true
255 }
256 }
257
258 if wordFound {
259 wordcounter++
260 }
261
262 return idx
263 }, func() {
264 if wordcounter >= newLen {
265 newOutput.WriteString("...")
266 }
267 })
268
269 return exec.AsSafeValue(newOutput.String())
270 }
271
272 func filterEscapejs(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
273 sin := in.String()
274
275 var b bytes.Buffer
276
277 idx := 0
278 for idx < len(sin) {
279 c, size := utf8.DecodeRuneInString(sin[idx:])
280 if c == utf8.RuneError {
281 idx += size
282 continue
283 }
284
285 if c == '\\' {
286
287 if idx+1 < len(sin) {
288 switch sin[idx+1] {
289 case 'r':
290 b.WriteString(fmt.Sprintf(`\u%04X`, '\r'))
291 idx += 2
292 continue
293 case 'n':
294 b.WriteString(fmt.Sprintf(`\u%04X`, '\n'))
295 idx += 2
296 continue
297
305 }
306 }
307 }
308
309 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' {
310 b.WriteRune(c)
311 } else {
312 b.WriteString(fmt.Sprintf(`\u%04X`, c))
313 }
314
315 idx += size
316 }
317
318 return exec.AsValue(b.String())
319 }
320
321 func filterAdd(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
322 param := params.Args[0]
323 if in.IsNumber() && param.IsNumber() {
324 if in.IsFloat() || param.IsFloat() {
325 return exec.AsValue(in.Float() + param.Float())
326 }
327 return exec.AsValue(in.Integer() + param.Integer())
328 }
329
330
331 return exec.AsValue(in.String() + param.String())
332 }
333
334 func filterAddslashes(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
335 output := strings.Replace(in.String(), "\\", "\\\\", -1)
336 output = strings.Replace(output, "\"", "\\\"", -1)
337 output = strings.Replace(output, "'", "\\'", -1)
338 return exec.AsValue(output)
339 }
340
341 func filterCut(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
342 return exec.AsValue(strings.Replace(in.String(), params.Args[0].String(), "", -1))
343 }
344
345 func filterLengthis(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
346 return exec.AsValue(in.Len() == params.Args[0].Integer())
347 }
348
349 func filterDefaultIfNone(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
350 if in.IsError() || in.IsNil() {
351 return params.Args[0]
352 }
353 return in
354 }
355
356 func filterFloatformat(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
357 val := in.Float()
358 param := params.First()
359
360 decimals := -1
361 if !param.IsNil() {
362
363 decimals = param.Integer()
364 }
365
366
367
368 trim := !param.IsNumber()
369
370 if decimals <= 0 {
371
372
373 decimals = -decimals
374 trim = true
375 }
376
377 if trim {
378
379 if float64(int(val)) == val {
380 return exec.AsValue(in.Integer())
381 }
382 }
383
384 return exec.AsValue(strconv.FormatFloat(val, 'f', decimals, 64))
385 }
386
387 func filterGetdigit(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
388 if len(params.Args) > 1 {
389 return exec.AsValue(errors.New("'getdigit' filter expect one and only one argument"))
390
391
392
393
394 }
395 param := params.First()
396 i := param.Integer()
397 l := len(in.String())
398 if i <= 0 || i > l {
399 return in
400 }
401 return exec.AsValue(in.String()[l-i] - 48)
402 }
403
404 func filterIriencode(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
405 return exec.AsValue(u.IRIEncode(in.String()))
406 }
407
408 func filterMakelist(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
409 s := in.String()
410 result := make([]string, 0, len(s))
411 for _, c := range s {
412 result = append(result, string(c))
413 }
414 return exec.AsValue(result)
415 }
416
417 func filterCapfirst(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
418 if in.Len() <= 0 {
419 return exec.AsValue("")
420 }
421 t := in.String()
422 r, size := utf8.DecodeRuneInString(t)
423 return exec.AsValue(strings.ToUpper(string(r)) + t[size:])
424 }
425
426 func filterDate(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
427 t, isTime := in.Interface().(time.Time)
428 if !isTime {
429 return exec.AsValue(errors.New("filter input argument must be of type 'time.Time'"))
430
431
432
433
434 }
435 return exec.AsValue(t.Format(params.Args[0].String()))
436 }
437
438 func filterLinebreaks(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
439 if in.Len() == 0 {
440 return in
441 }
442
443 var b bytes.Buffer
444
445
446
447 lines := strings.Split(in.String(), "\n")
448 lenlines := len(lines)
449
450 opened := false
451
452 for idx, line := range lines {
453
454 if !opened {
455 b.WriteString("<p>")
456 opened = true
457 }
458
459 b.WriteString(line)
460
461 if idx < lenlines-1 && strings.TrimSpace(lines[idx]) != "" {
462
463 if strings.TrimSpace(lines[idx+1]) == "" {
464
465 if opened {
466 b.WriteString("</p>")
467 opened = false
468 }
469 } else {
470 b.WriteString("<br />")
471 }
472 }
473 }
474
475 if opened {
476 b.WriteString("</p>")
477 }
478
479 return exec.AsValue(b.String())
480 }
481
482 func filterSplit(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
483 chunks := strings.Split(in.String(), params.Args[0].String())
484
485 return exec.AsValue(chunks)
486 }
487
488 func filterLinebreaksbr(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
489 return exec.AsValue(strings.Replace(in.String(), "\n", "<br />", -1))
490 }
491
492 func filterLinenumbers(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
493 lines := strings.Split(in.String(), "\n")
494 output := make([]string, 0, len(lines))
495 for idx, line := range lines {
496 output = append(output, fmt.Sprintf("%d. %s", idx+1, line))
497 }
498 return exec.AsValue(strings.Join(output, "\n"))
499 }
500
501 func filterLjust(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
502 times := params.Args[0].Integer() - in.Len()
503 if times < 0 {
504 times = 0
505 }
506 return exec.AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times)))
507 }
508
509 func filterStringformat(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
510 return exec.AsValue(fmt.Sprintf(params.Args[0].String(), in.Interface()))
511 }
512
513
514 var filterPhone2numericMap = map[string]string{
515 "a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5",
516 "l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8",
517 "w": "9", "x": "9", "y": "9", "z": "9",
518 }
519
520 func filterPhone2numeric(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
521 sin := in.String()
522 for k, v := range filterPhone2numericMap {
523 sin = strings.Replace(sin, k, v, -1)
524 sin = strings.Replace(sin, strings.ToUpper(k), v, -1)
525 }
526 return exec.AsValue(sin)
527 }
528
529 func filterPluralize(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
530 param := params.First()
531 if in.IsNumber() {
532
533 if param.Len() > 0 {
534 endings := strings.Split(param.String(), ",")
535 if len(endings) > 2 {
536 return exec.AsValue(errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"))
537
538
539
540
541 }
542 if len(endings) == 1 {
543
544 if in.Integer() != 1 {
545 return exec.AsValue(endings[0])
546 }
547 } else {
548 if in.Integer() != 1 {
549
550 return exec.AsValue(endings[1])
551 }
552 return exec.AsValue(endings[0])
553 }
554 } else {
555 if in.Integer() != 1 {
556
557 return exec.AsValue("s")
558 }
559 }
560
561 return exec.AsValue("")
562 }
563
564
565
566
567 return exec.AsValue(errors.New("filter 'pluralize' does only work on numbers"))
568 }
569
570 func filterRemovetags(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
571 s := in.String()
572 tags := strings.Split(params.Args[0].String(), ",")
573
574
575 for _, tag := range tags {
576 re := regexp.MustCompile(fmt.Sprintf("</?%s/?>", tag))
577 s = re.ReplaceAllString(s, "")
578 }
579
580 return exec.AsValue(strings.TrimSpace(s))
581 }
582
583 func filterRjust(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
584 return exec.AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", params.Args[0].Integer()), in.String()))
585 }
586
587 func filterYesno(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
588 if len(params.Args) > 1 {
589
590
591
592
593 return exec.AsValue(errors.New("'getdigit' filter expect one and only one argument"))
594 }
595 choices := map[int]string{
596 0: "yes",
597 1: "no",
598 2: "maybe",
599 }
600 param := params.First()
601 paramString := param.String()
602 customChoices := strings.Split(paramString, ",")
603 if len(paramString) > 0 {
604 if len(customChoices) > 3 {
605 return exec.AsValue(errors.Errorf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", paramString))
606
607
608
609
610 }
611 if len(customChoices) < 2 {
612
613
614
615
616 return exec.AsValue(errors.Errorf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", paramString))
617 }
618
619
620 choices[0] = customChoices[0]
621 choices[1] = customChoices[1]
622 if len(customChoices) == 3 {
623 choices[2] = customChoices[2]
624 }
625 }
626
627
628 if in.IsNil() {
629 return exec.AsValue(choices[2])
630 }
631
632
633 if in.IsTrue() {
634 return exec.AsValue(choices[0])
635 }
636
637
638 return exec.AsValue(choices[1])
639 }
640
View as plain text