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
20 An implementation of a generic TALES engine
21 """
23 __version__='$Revision: 1.9 $'[11:-2]
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
31 ustr = str
33 StringType = type('')
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
39 class TALESError(Exception):
40 """Error during TALES expression evaluation"""
42 class Undefined(TALESError):
43 '''Exception raised on traversal of an undefined path'''
45 class RegistrationError(Exception):
46 '''TALES Type Registration Error'''
48 class CompilerError(Exception):
49 '''TALES Compiler Error'''
51 class Default:
52 '''Retain Default'''
53 Default = Default()
55 class SafeMapping(MultiMapping):
56 '''Mapping with security declarations and limited method exposure.
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
66 _push = MultiMapping.push
67 _pop = MultiMapping.pop
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)
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
85 class ErrorInfo:
86 """Information about an exception passed to an on-error handler."""
87 __allow_access_to_unprotected_subobjects__ = 1
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]
100 class Engine:
101 '''Expression Engine
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
110 def __init__(self, Iterator=None):
111 self.types = {}
112 if Iterator is not None:
113 self.Iterator = Iterator
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
125 def getTypes(self):
126 return self.types
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)
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)
151 def getCompilerError(self):
152 return CompilerError
154 class Context:
155 '''Expression Context
157 An instance of this class holds context information that it can
158 use to evaluate compiled expressions.
159 '''
161 _context_class = SafeMapping
162 position = (None, None)
163 source_file = None
165 def __init__(self, compiler, contexts):
166 self._compiler = compiler
167 self.contexts = contexts
168 contexts['nothing'] = None
169 contexts['default'] = Default
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
176 self.global_vars = gv = contexts.copy()
177 self.local_vars = lv = {}
178 self.vars = self._context_class(gv, lv)
180 # Keep track of what needs to be popped as each scope ends.
181 self._scope_stack = []
183 def getCompiler(self):
184 return self._compiler
186 def beginScope(self):
187 self._scope_stack.append([self.local_vars.copy()])
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
205 def setLocal(self, name, value):
206 self.local_vars[name] = value
208 def setGlobal(self, name, value):
209 self.global_vars[name] = value
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
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)
229 evaluateValue = evaluate
230 evaluateBoolean = evaluate
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)
241 def evaluateStructure(self, expr):
242 return self.evaluate(expr)
243 evaluateStructure = evaluate
245 def evaluateMacro(self, expr):
246 # XXX Should return None or a macro definition
247 return self.evaluate(expr)
248 evaluateMacro = evaluate
250 def createErrorInfo(self, err, position):
251 return ErrorInfo(err, position)
253 def getDefault(self):
254 return Default
256 def setSourceFile(self, source_file):
257 self.source_file = source_file
259 def setPosition(self, position):
260 self.position = position
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)
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)
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))
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`)