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.2 $'[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, engine, contexts):
164 self._engine = engine
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 beginScope(self):
182 self._scope_stack.append([self.local_vars.copy()])
184 def endScope(self):
185 scope = self._scope_stack.pop()
186 self.local_vars = lv = scope[0]
187 v = self.vars
188 v._pop()
189 v._push(lv)
190 # Pop repeat variables, if any
191 i = len(scope) - 1
192 while i:
193 name, value = scope[i]
194 if value is None:
195 del self.repeat_vars[name]
196 else:
197 self.repeat_vars[name] = value
198 i = i - 1
200 def setLocal(self, name, value):
201 self.local_vars[name] = value
203 def setGlobal(self, name, value):
204 self.global_vars[name] = value
206 def setRepeat(self, name, expr):
207 expr = self.evaluate(expr)
208 if not expr:
209 return self._engine.Iterator(name, (), self)
210 it = self._engine.Iterator(name, expr, self)
211 old_value = self.repeat_vars.get(name)
212 self._scope_stack[-1].append((name, old_value))
213 self.repeat_vars[name] = it
214 return it
216 def evaluate(self, expression,
217 isinstance=isinstance, StringType=StringType):
218 if isinstance(expression, StringType):
219 expression = self._engine.compile(expression)
220 __traceback_supplement__ = (
221 TALESTracebackSupplement, self, expression)
222 v = expression(self)
223 return v
225 evaluateValue = evaluate
227 def evaluateBoolean(self, expr):
228 return not not self.evaluate(expr)
230 def evaluateText(self, expr, None=None):
231 text = self.evaluate(expr)
232 if text is Default or text is None:
233 return text
234 return str(text)
236 def evaluateStructure(self, expr):
237 return self.evaluate(expr)
238 evaluateStructure = evaluate
240 def evaluateMacro(self, expr):
241 # XXX Should return None or a macro definition
242 return self.evaluate(expr)
243 evaluateMacro = evaluate
245 def createErrorInfo(self, err, position):
246 return ErrorInfo(err, position)
248 def getDefault(self):
249 return Default
251 def setSourceFile(self, source_file):
252 self.source_file = source_file
254 def setPosition(self, position):
255 self.position = position
259 class TALESTracebackSupplement:
260 """Implementation of ITracebackSupplement"""
261 def __init__(self, context, expression):
262 self.context = context
263 self.source_url = context.source_file
264 self.line = context.position[0]
265 self.column = context.position[1]
266 self.expression = repr(expression)
268 def getInfo(self, as_html=0):
269 import pprint
270 data = self.context.contexts.copy()
271 s = pprint.pformat(data)
272 if not as_html:
273 return ' - Names:\n %s' % string.replace(s, '\n', '\n ')
274 else:
275 from cgi import escape
276 return '<b>Names:</b><pre>%s</pre>' % (escape(s))
277 return None
281 class SimpleExpr:
282 '''Simple example of an expression type handler'''
283 def __init__(self, name, expr, engine):
284 self._name = name
285 self._expr = expr
286 def __call__(self, econtext):
287 return self._name, self._expr
288 def __repr__(self):
289 return '<SimpleExpr %s %s>' % (self._name, `self._expr`)