Commit | Line | Data |
---|---|---|
c638d827 CR |
1 | ############################################################################## |
2 | # | |
3 | # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. | |
4 | # | |
5 | # This software is subject to the provisions of the Zope Public License, | |
6 | # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. | |
7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |
8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |
10 | # FOR A PARTICULAR PURPOSE | |
11 | # | |
12 | ############################################################################## | |
13 | # Modified for Roundup: | |
14 | # | |
15 | # 1. changed imports to import from roundup.cgi | |
16 | # 2. implemented ustr as str (removes import from DocumentTemplate) | |
17 | # 3. removed import and use of Unauthorized from zExceptions | |
18 | """TALES | |
19 | ||
20 | An implementation of a generic TALES engine | |
21 | """ | |
22 | ||
23 | __version__='$Revision: 1.9 $'[11:-2] | |
24 | ||
25 | import re, sys | |
26 | from roundup.cgi import ZTUtils | |
27 | from weakref import ref | |
28 | from MultiMapping import MultiMapping | |
29 | from GlobalTranslationService import getGlobalTranslationService | |
30 | ||
31 | ustr = str | |
32 | ||
33 | StringType = type('') | |
34 | ||
35 | NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*" | |
36 | _parse_expr = re.compile(r"(%s):" % NAME_RE).match | |
37 | _valid_name = re.compile('%s$' % NAME_RE).match | |
38 | ||
39 | class TALESError(Exception): | |
40 | """Error during TALES expression evaluation""" | |
41 | ||
42 | class Undefined(TALESError): | |
43 | '''Exception raised on traversal of an undefined path''' | |
44 | ||
45 | class RegistrationError(Exception): | |
46 | '''TALES Type Registration Error''' | |
47 | ||
48 | class CompilerError(Exception): | |
49 | '''TALES Compiler Error''' | |
50 | ||
51 | class Default: | |
52 | '''Retain Default''' | |
53 | Default = Default() | |
54 | ||
55 | class SafeMapping(MultiMapping): | |
56 | '''Mapping with security declarations and limited method exposure. | |
57 | ||
58 | Since it subclasses MultiMapping, this class can be used to wrap | |
59 | one or more mapping objects. Restricted Python code will not be | |
60 | able to mutate the SafeMapping or the wrapped mappings, but will be | |
61 | able to read any value. | |
62 | ''' | |
63 | __allow_access_to_unprotected_subobjects__ = 1 | |
64 | push = pop = None | |
65 | ||
66 | _push = MultiMapping.push | |
67 | _pop = MultiMapping.pop | |
68 | ||
69 | ||
70 | class Iterator(ZTUtils.Iterator): | |
71 | def __init__(self, name, seq, context): | |
72 | ZTUtils.Iterator.__init__(self, seq) | |
73 | self.name = name | |
74 | self._context_ref = ref(context) | |
75 | ||
76 | def next(self): | |
77 | if ZTUtils.Iterator.next(self): | |
78 | context = self._context_ref() | |
79 | if context is not None: | |
80 | context.setLocal(self.name, self.item) | |
81 | return 1 | |
82 | return 0 | |
83 | ||
84 | ||
85 | class ErrorInfo: | |
86 | """Information about an exception passed to an on-error handler.""" | |
87 | __allow_access_to_unprotected_subobjects__ = 1 | |
88 | ||
89 | def __init__(self, err, position=(None, None)): | |
90 | if isinstance(err, Exception): | |
91 | self.type = err.__class__ | |
92 | self.value = err | |
93 | else: | |
94 | self.type = err | |
95 | self.value = None | |
96 | self.lineno = position[0] | |
97 | self.offset = position[1] | |
98 | ||
99 | ||
100 | class Engine: | |
101 | '''Expression Engine | |
102 | ||
103 | An instance of this class keeps a mutable collection of expression | |
104 | type handlers. It can compile expression strings by delegating to | |
105 | these handlers. It can provide an expression Context, which is | |
106 | capable of holding state and evaluating compiled expressions. | |
107 | ''' | |
108 | Iterator = Iterator | |
109 | ||
110 | def __init__(self, Iterator=None): | |
111 | self.types = {} | |
112 | if Iterator is not None: | |
113 | self.Iterator = Iterator | |
114 | ||
115 | def registerType(self, name, handler): | |
116 | if not _valid_name(name): | |
117 | raise RegistrationError, 'Invalid Expression type "%s".' % name | |
118 | types = self.types | |
119 | if types.has_key(name): | |
120 | raise RegistrationError, ( | |
121 | 'Multiple registrations for Expression type "%s".' % | |
122 | name) | |
123 | types[name] = handler | |
124 | ||
125 | def getTypes(self): | |
126 | return self.types | |
127 | ||
128 | def compile(self, expression): | |
129 | m = _parse_expr(expression) | |
130 | if m: | |
131 | type = m.group(1) | |
132 | expr = expression[m.end():] | |
133 | else: | |
134 | type = "standard" | |
135 | expr = expression | |
136 | try: | |
137 | handler = self.types[type] | |
138 | except KeyError: | |
139 | raise CompilerError, ( | |
140 | 'Unrecognized expression type "%s".' % type) | |
141 | return handler(type, expr, self) | |
142 | ||
143 | def getContext(self, contexts=None, **kwcontexts): | |
144 | if contexts is not None: | |
145 | if kwcontexts: | |
146 | kwcontexts.update(contexts) | |
147 | else: | |
148 | kwcontexts = contexts | |
149 | return Context(self, kwcontexts) | |
150 | ||
151 | def getCompilerError(self): | |
152 | return CompilerError | |
153 | ||
154 | class Context: | |
155 | '''Expression Context | |
156 | ||
157 | An instance of this class holds context information that it can | |
158 | use to evaluate compiled expressions. | |
159 | ''' | |
160 | ||
161 | _context_class = SafeMapping | |
162 | position = (None, None) | |
163 | source_file = None | |
164 | ||
165 | def __init__(self, compiler, contexts): | |
166 | self._compiler = compiler | |
167 | self.contexts = contexts | |
168 | contexts['nothing'] = None | |
169 | contexts['default'] = Default | |
170 | ||
171 | self.repeat_vars = rv = {} | |
172 | # Wrap this, as it is visible to restricted code | |
173 | contexts['repeat'] = rep = self._context_class(rv) | |
174 | contexts['loop'] = rep # alias | |
175 | ||
176 | self.global_vars = gv = contexts.copy() | |
177 | self.local_vars = lv = {} | |
178 | self.vars = self._context_class(gv, lv) | |
179 | ||
180 | # Keep track of what needs to be popped as each scope ends. | |
181 | self._scope_stack = [] | |
182 | ||
183 | def getCompiler(self): | |
184 | return self._compiler | |
185 | ||
186 | def beginScope(self): | |
187 | self._scope_stack.append([self.local_vars.copy()]) | |
188 | ||
189 | def endScope(self): | |
190 | scope = self._scope_stack.pop() | |
191 | self.local_vars = lv = scope[0] | |
192 | v = self.vars | |
193 | v._pop() | |
194 | v._push(lv) | |
195 | # Pop repeat variables, if any | |
196 | i = len(scope) - 1 | |
197 | while i: | |
198 | name, value = scope[i] | |
199 | if value is None: | |
200 | del self.repeat_vars[name] | |
201 | else: | |
202 | self.repeat_vars[name] = value | |
203 | i = i - 1 | |
204 | ||
205 | def setLocal(self, name, value): | |
206 | self.local_vars[name] = value | |
207 | ||
208 | def setGlobal(self, name, value): | |
209 | self.global_vars[name] = value | |
210 | ||
211 | def setRepeat(self, name, expr): | |
212 | expr = self.evaluate(expr) | |
213 | if not expr: | |
214 | return self._compiler.Iterator(name, (), self) | |
215 | it = self._compiler.Iterator(name, expr, self) | |
216 | old_value = self.repeat_vars.get(name) | |
217 | self._scope_stack[-1].append((name, old_value)) | |
218 | self.repeat_vars[name] = it | |
219 | return it | |
220 | ||
221 | def evaluate(self, expression, | |
222 | isinstance=isinstance, StringType=StringType): | |
223 | if isinstance(expression, StringType): | |
224 | expression = self._compiler.compile(expression) | |
225 | __traceback_supplement__ = ( | |
226 | TALESTracebackSupplement, self, expression) | |
227 | return expression(self) | |
228 | ||
229 | evaluateValue = evaluate | |
230 | evaluateBoolean = evaluate | |
231 | ||
232 | def evaluateText(self, expr): | |
233 | text = self.evaluate(expr) | |
234 | if text is Default or text is None: | |
235 | return text | |
236 | if isinstance(text, unicode): | |
237 | return text | |
238 | else: | |
239 | return ustr(text) | |
240 | ||
241 | def evaluateStructure(self, expr): | |
242 | return self.evaluate(expr) | |
243 | evaluateStructure = evaluate | |
244 | ||
245 | def evaluateMacro(self, expr): | |
246 | # XXX Should return None or a macro definition | |
247 | return self.evaluate(expr) | |
248 | evaluateMacro = evaluate | |
249 | ||
250 | def createErrorInfo(self, err, position): | |
251 | return ErrorInfo(err, position) | |
252 | ||
253 | def getDefault(self): | |
254 | return Default | |
255 | ||
256 | def setSourceFile(self, source_file): | |
257 | self.source_file = source_file | |
258 | ||
259 | def setPosition(self, position): | |
260 | self.position = position | |
261 | ||
262 | def translate(self, domain, msgid, mapping=None, | |
263 | context=None, target_language=None, default=None): | |
264 | if context is None: | |
265 | context = self.contexts.get('here') | |
266 | return getGlobalTranslationService().translate( | |
267 | domain, msgid, mapping=mapping, | |
268 | context=context, | |
269 | default=default, | |
270 | target_language=target_language) | |
271 | ||
272 | class TALESTracebackSupplement: | |
273 | """Implementation of ITracebackSupplement""" | |
274 | def __init__(self, context, expression): | |
275 | self.context = context | |
276 | self.source_url = context.source_file | |
277 | self.line = context.position[0] | |
278 | self.column = context.position[1] | |
279 | self.expression = repr(expression) | |
280 | ||
281 | def getInfo(self, as_html=0): | |
282 | import pprint | |
283 | data = self.context.contexts.copy() | |
284 | s = pprint.pformat(data) | |
285 | if not as_html: | |
286 | return ' - Names:\n %s' % s.replace('\n', '\n ') | |
287 | else: | |
288 | from cgi import escape | |
289 | return '<b>Names:</b><pre>%s</pre>' % (escape(s)) | |
290 | ||
291 | ||
292 | class SimpleExpr: | |
293 | '''Simple example of an expression type handler''' | |
294 | def __init__(self, name, expr, engine): | |
295 | self._name = name | |
296 | self._expr = expr | |
297 | def __call__(self, econtext): | |
298 | return self._name, self._expr | |
299 | def __repr__(self): | |
300 | return '<SimpleExpr %s %s>' % (self._name, `self._expr`) |