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 """TALES
15 An implementation of a generic TALES engine
17 Modified for Roundup 0.5 release:
19 - changed imports to import from roundup.cgi
20 """
22 __version__='$Revision: 1.5 $'[11:-2]
24 import re, sys
25 from roundup.cgi import ZTUtils
26 from MultiMapping import MultiMapping
28 StringType = type('')
30 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
31 _parse_expr = re.compile(r"(%s):" % NAME_RE).match
32 _valid_name = re.compile('%s$' % NAME_RE).match
34 class TALESError(Exception):
35 """Error during TALES expression evaluation"""
37 class Undefined(TALESError):
38 '''Exception raised on traversal of an undefined path'''
40 class RegistrationError(Exception):
41 '''TALES Type Registration Error'''
43 class CompilerError(Exception):
44 '''TALES Compiler Error'''
46 class Default:
47 '''Retain Default'''
48 Default = Default()
50 _marker = []
52 class SafeMapping(MultiMapping):
53 '''Mapping with security declarations and limited method exposure.
55 Since it subclasses MultiMapping, this class can be used to wrap
56 one or more mapping objects. Restricted Python code will not be
57 able to mutate the SafeMapping or the wrapped mappings, but will be
58 able to read any value.
59 '''
60 __allow_access_to_unprotected_subobjects__ = 1
61 push = pop = None
63 _push = MultiMapping.push
64 _pop = MultiMapping.pop
66 def has_get(self, key, _marker=[]):
67 v = self.get(key, _marker)
68 return v is not _marker, v
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 = context
76 def next(self):
77 if ZTUtils.Iterator.next(self):
78 self._context.setLocal(self.name, self.item)
79 return 1
80 return 0
83 class ErrorInfo:
84 """Information about an exception passed to an on-error handler."""
85 __allow_access_to_unprotected_subobjects__ = 1
87 def __init__(self, err, position=(None, None)):
88 if isinstance(err, Exception):
89 self.type = err.__class__
90 self.value = err
91 else:
92 self.type = err
93 self.value = None
94 self.lineno = position[0]
95 self.offset = position[1]
98 class Engine:
99 '''Expression Engine
101 An instance of this class keeps a mutable collection of expression
102 type handlers. It can compile expression strings by delegating to
103 these handlers. It can provide an expression Context, which is
104 capable of holding state and evaluating compiled expressions.
105 '''
106 Iterator = Iterator
108 def __init__(self, Iterator=None):
109 self.types = {}
110 if Iterator is not None:
111 self.Iterator = Iterator
113 def registerType(self, name, handler):
114 if not _valid_name(name):
115 raise RegistrationError, 'Invalid Expression type "%s".' % name
116 types = self.types
117 if types.has_key(name):
118 raise RegistrationError, (
119 'Multiple registrations for Expression type "%s".' %
120 name)
121 types[name] = handler
123 def getTypes(self):
124 return self.types
126 def compile(self, expression):
127 m = _parse_expr(expression)
128 if m:
129 type = m.group(1)
130 expr = expression[m.end():]
131 else:
132 type = "standard"
133 expr = expression
134 try:
135 handler = self.types[type]
136 except KeyError:
137 raise CompilerError, (
138 'Unrecognized expression type "%s".' % type)
139 return handler(type, expr, self)
141 def getContext(self, contexts=None, **kwcontexts):
142 if contexts is not None:
143 if kwcontexts:
144 kwcontexts.update(contexts)
145 else:
146 kwcontexts = contexts
147 return Context(self, kwcontexts)
149 def getCompilerError(self):
150 return CompilerError
152 class Context:
153 '''Expression Context
155 An instance of this class holds context information that it can
156 use to evaluate compiled expressions.
157 '''
159 _context_class = SafeMapping
160 position = (None, None)
161 source_file = None
163 def __init__(self, compiler, contexts):
164 self._compiler = compiler
165 self.contexts = contexts
166 contexts['nothing'] = None
167 contexts['default'] = Default
169 self.repeat_vars = rv = {}
170 # Wrap this, as it is visible to restricted code
171 contexts['repeat'] = rep = self._context_class(rv)
172 contexts['loop'] = rep # alias
174 self.global_vars = gv = contexts.copy()
175 self.local_vars = lv = {}
176 self.vars = self._context_class(gv, lv)
178 # Keep track of what needs to be popped as each scope ends.
179 self._scope_stack = []
181 def getCompiler(self):
182 return self._compiler
184 def beginScope(self):
185 self._scope_stack.append([self.local_vars.copy()])
187 def endScope(self):
188 scope = self._scope_stack.pop()
189 self.local_vars = lv = scope[0]
190 v = self.vars
191 v._pop()
192 v._push(lv)
193 # Pop repeat variables, if any
194 i = len(scope) - 1
195 while i:
196 name, value = scope[i]
197 if value is None:
198 del self.repeat_vars[name]
199 else:
200 self.repeat_vars[name] = value
201 i = i - 1
203 def setLocal(self, name, value):
204 self.local_vars[name] = value
206 def setGlobal(self, name, value):
207 self.global_vars[name] = value
209 def setRepeat(self, name, expr):
210 expr = self.evaluate(expr)
211 if not expr:
212 return self._compiler.Iterator(name, (), self)
213 it = self._compiler.Iterator(name, expr, self)
214 old_value = self.repeat_vars.get(name)
215 self._scope_stack[-1].append((name, old_value))
216 self.repeat_vars[name] = it
217 return it
219 def evaluate(self, expression,
220 isinstance=isinstance, StringType=StringType):
221 if isinstance(expression, StringType):
222 expression = self._compiler.compile(expression)
223 __traceback_supplement__ = (
224 TALESTracebackSupplement, self, expression)
225 v = expression(self)
226 return v
228 evaluateValue = evaluate
229 evaluateBoolean = evaluate
231 def evaluateText(self, expr):
232 text = self.evaluate(expr)
233 if text is Default or text is None:
234 return text
235 return str(text)
237 def evaluateStructure(self, expr):
238 return self.evaluate(expr)
239 evaluateStructure = evaluate
241 def evaluateMacro(self, expr):
242 # XXX Should return None or a macro definition
243 return self.evaluate(expr)
244 evaluateMacro = evaluate
246 def createErrorInfo(self, err, position):
247 return ErrorInfo(err, position)
249 def getDefault(self):
250 return Default
252 def setSourceFile(self, source_file):
253 self.source_file = source_file
255 def setPosition(self, position):
256 self.position = position
260 class TALESTracebackSupplement:
261 """Implementation of ITracebackSupplement"""
262 def __init__(self, context, expression):
263 self.context = context
264 self.source_url = context.source_file
265 self.line = context.position[0]
266 self.column = context.position[1]
267 self.expression = repr(expression)
269 def getInfo(self, as_html=0):
270 import pprint
271 data = self.context.contexts.copy()
272 s = pprint.pformat(data)
273 if not as_html:
274 return ' - Names:\n %s' % string.replace(s, '\n', '\n ')
275 else:
276 from cgi import escape
277 return '<b>Names:</b><pre>%s</pre>' % (escape(s))
278 return None
282 class SimpleExpr:
283 '''Simple example of an expression type handler'''
284 def __init__(self, name, expr, engine):
285 self._name = name
286 self._expr = expr
287 def __call__(self, econtext):
288 return self._name, self._expr
289 def __repr__(self):
290 return '<SimpleExpr %s %s>' % (self._name, `self._expr`)