/
lexer.py
executable file
·102 lines (85 loc) · 4.01 KB
/
lexer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from django.apps import apps
from django.utils.html import conditional_escape
from django.utils.encoding import force_text
from mako.lexer import Lexer
from mako import parsetree, ast
from ..util import log
from ..tags import is_autoescape
###########################################################
### DMP hook for expression filters. This allows DMP
### to filter expressions ${...} after all other filters
### have been run.
###
### Currently, the use of this hook is HTML autoescaping.
### Django autoescapes by default, while Mako does not.
### DMP injects autoescaping to be consistent with Django.
###
MAKO_ESCAPE_REPLACEMENTS = {
'h': 'django.utils.html.escape', # uses Django's escape rather than Mako's, which works better with marks
}
class DMPLexer(Lexer):
'''
Subclass of Mako's Lexer, which is used during compilation of
templates. This subclass injects ExpressionPostProcessor
as the final filter on every expression. Overriding append_node()
is a hack, but it's the only way I can find to hook into Mako's
compile process without modifying Mako directly.
'''
def append_node(self, nodecls, *args, **kwargs):
# fyi, this method runs on template compilation (not on template render)
if nodecls == parsetree.Expression:
# when an Expression, args[1] is a comma-separated string of filters
# parse the filters and make any DMP replacements for them
try:
# this is Mako's ast, not the python one
filters = [ MAKO_ESCAPE_REPLACEMENTS.get(f, f) for f in ast.ArgumentList(args[1]).args ]
except Exception as e:
log.warning('An error occurred when compiling the filters on an expression; allowing through so Mako can handle it (%s)', e)
filters = []
extra = {} # extra info sent to the expression processor
# if we have the 'n' filter, send that to the expression processor
if 'n' in filters:
extra['n_filter_on'] = True
# add the expression processor as the last filter to be run
# then recreate the args tuple
filters.append('django_mako_plus.ExpressionPostProcessor(self' + \
(", extra={}".format(extra) if len(extra) > 0 else '') + \
')')
args = args[:1] + (','.join(filters),) + args[2:]
return super().append_node(nodecls, *args, **kwargs)
# this is used read-only, so it can be in __init__ signature
EMPTY_DICT = {}
class ExpressionPostProcessor(object):
'''
Object that is called as the final filter on every template
expression ${...}.
Right now this object does autoescaping. However, it is placed
on *every* expression so we have a hook for future post-processing
of expressions.
See the creation of this object in DMPLexer above for more info.
'''
def __init__(self, tself, extra=EMPTY_DICT):
# check whether it's on for this block
self.html_escape = is_autoescape(tself.context)
# the 'n' filter turns off our normal html escaping
if extra.get('n_filter_on', False):
self.html_escape = False
# mark_safe() is handled in Django's conditional_escape(), so no need to deal with it
# check the global setting
dmp = apps.get_app_config('django_mako_plus')
if not dmp.options['AUTOESCAPE']:
self.html_escape = False
def __call__(self, text):
'''
Mako calls this after evaluating the expression and applying
all other filters.
Right now this html escapes the expression, unless autoescape
is toggled off.
'''
# we apply this across the board, even if the `n` filter is present because
# DMP always creates unicode (see adapter.py where render_unicode() is used)
text = force_text(text)
# html encoding
if self.html_escape:
text = conditional_escape(text) # internally, this honors mark_safe()
return text