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
16 """
18 __version__='$Revision: 1.1 $'[11:-2]
20 import re, sys
21 from roundup.cgi import ZTUtils
22 from MultiMapping import MultiMapping
24 StringType = type('')
26 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
27 _parse_expr = re.compile(r"(%s):" % NAME_RE).match
28 _valid_name = re.compile('%s$' % NAME_RE).match
30 class TALESError(Exception):
31 """Error during TALES expression evaluation"""
33 class Undefined(TALESError):
34 '''Exception raised on traversal of an undefined path'''
36 class RegistrationError(Exception):
37 '''TALES Type Registration Error'''
39 class CompilerError(Exception):
40 '''TALES Compiler Error'''
42 class Default:
43 '''Retain Default'''
44 Default = Default()
46 _marker = []
48 class SafeMapping(MultiMapping):
49 '''Mapping with security declarations and limited method exposure.
51 Since it subclasses MultiMapping, this class can be used to wrap
52 one or more mapping objects. Restricted Python code will not be
53 able to mutate the SafeMapping or the wrapped mappings, but will be
54 able to read any value.
55 '''
56 __allow_access_to_unprotected_subobjects__ = 1
57 push = pop = None
59 _push = MultiMapping.push
60 _pop = MultiMapping.pop
62 def has_get(self, key, _marker=[]):
63 v = self.get(key, _marker)
64 return v is not _marker, v
66 class Iterator(ZTUtils.Iterator):
67 def __init__(self, name, seq, context):
68 ZTUtils.Iterator.__init__(self, seq)
69 self.name = name
70 self._context = context
72 def next(self):
73 if ZTUtils.Iterator.next(self):
74 self._context.setLocal(self.name, self.item)
75 return 1
76 return 0
79 class ErrorInfo:
80 """Information about an exception passed to an on-error handler."""
81 __allow_access_to_unprotected_subobjects__ = 1
83 def __init__(self, err, position=(None, None)):
84 if isinstance(err, Exception):
85 self.type = err.__class__
86 self.value = err
87 else:
88 self.type = err
89 self.value = None
90 self.lineno = position[0]
91 self.offset = position[1]
94 class Engine:
95 '''Expression Engine
97 An instance of this class keeps a mutable collection of expression
98 type handlers. It can compile expression strings by delegating to
99 these handlers. It can provide an expression Context, which is
100 capable of holding state and evaluating compiled expressions.
101 '''
102 Iterator = Iterator
104 def __init__(self, Iterator=None):
105 self.types = {}
106 if Iterator is not None:
107 self.Iterator = Iterator
109 def registerType(self, name, handler):
110 if not _valid_name(name):
111 raise RegistrationError, 'Invalid Expression type "%s".' % name
112 types = self.types
113 if types.has_key(name):
114 raise RegistrationError, (
115 'Multiple registrations for Expression type "%s".' %
116 name)
117 types[name] = handler
119 def getTypes(self):
120 return self.types
122 def compile(self, expression):
123 m = _parse_expr(expression)
124 if m:
125 type = m.group(1)
126 expr = expression[m.end():]
127 else:
128 type = "standard"
129 expr = expression
130 try:
131 handler = self.types[type]
132 except KeyError:
133 raise CompilerError, (
134 'Unrecognized expression type "%s".' % type)
135 return handler(type, expr, self)
137 def getContext(self, contexts=None, **kwcontexts):
138 if contexts is not None:
139 if kwcontexts:
140 kwcontexts.update(contexts)
141 else:
142 kwcontexts = contexts
143 return Context(self, kwcontexts)
145 def getCompilerError(self):
146 return CompilerError
148 class Context:
149 '''Expression Context
151 An instance of this class holds context information that it can
152 use to evaluate compiled expressions.
153 '''
155 _context_class = SafeMapping
156 position = (None, None)
157 source_file = None
159 def __init__(self, engine, contexts):
160 self._engine = engine
161 self.contexts = contexts
162 contexts['nothing'] = None
163 contexts['default'] = Default
165 self.repeat_vars = rv = {}
166 # Wrap this, as it is visible to restricted code
167 contexts['repeat'] = rep = self._context_class(rv)
168 contexts['loop'] = rep # alias
170 self.global_vars = gv = contexts.copy()
171 self.local_vars = lv = {}
172 self.vars = self._context_class(gv, lv)
174 # Keep track of what needs to be popped as each scope ends.
175 self._scope_stack = []
177 def beginScope(self):
178 self._scope_stack.append([self.local_vars.copy()])
180 def endScope(self):
181 scope = self._scope_stack.pop()
182 self.local_vars = lv = scope[0]
183 v = self.vars
184 v._pop()
185 v._push(lv)
186 # Pop repeat variables, if any
187 i = len(scope) - 1
188 while i:
189 name, value = scope[i]
190 if value is None:
191 del self.repeat_vars[name]
192 else:
193 self.repeat_vars[name] = value
194 i = i - 1
196 def setLocal(self, name, value):
197 self.local_vars[name] = value
199 def setGlobal(self, name, value):
200 self.global_vars[name] = value
202 def setRepeat(self, name, expr):
203 expr = self.evaluate(expr)
204 if not expr:
205 return self._engine.Iterator(name, (), self)
206 it = self._engine.Iterator(name, expr, self)
207 old_value = self.repeat_vars.get(name)
208 self._scope_stack[-1].append((name, old_value))
209 self.repeat_vars[name] = it
210 return it
212 def evaluate(self, expression,
213 isinstance=isinstance, StringType=StringType):
214 if isinstance(expression, StringType):
215 expression = self._engine.compile(expression)
216 __traceback_supplement__ = (
217 TALESTracebackSupplement, self, expression)
218 v = expression(self)
219 return v
221 evaluateValue = evaluate
223 def evaluateBoolean(self, expr):
224 return not not self.evaluate(expr)
226 def evaluateText(self, expr, None=None):
227 text = self.evaluate(expr)
228 if text is Default or text is None:
229 return text
230 return str(text)
232 def evaluateStructure(self, expr):
233 return self.evaluate(expr)
234 evaluateStructure = evaluate
236 def evaluateMacro(self, expr):
237 # XXX Should return None or a macro definition
238 return self.evaluate(expr)
239 evaluateMacro = evaluate
241 def createErrorInfo(self, err, position):
242 return ErrorInfo(err, position)
244 def getDefault(self):
245 return Default
247 def setSourceFile(self, source_file):
248 self.source_file = source_file
250 def setPosition(self, position):
251 self.position = position
255 class TALESTracebackSupplement:
256 """Implementation of ITracebackSupplement"""
257 def __init__(self, context, expression):
258 self.context = context
259 self.source_url = context.source_file
260 self.line = context.position[0]
261 self.column = context.position[1]
262 self.expression = repr(expression)
264 def getInfo(self, as_html=0):
265 import pprint
266 data = self.context.contexts.copy()
267 s = pprint.pformat(data)
268 if not as_html:
269 return ' - Names:\n %s' % string.replace(s, '\n', '\n ')
270 else:
271 from cgi import escape
272 return '<b>Names:</b><pre>%s</pre>' % (escape(s))
273 return None
277 class SimpleExpr:
278 '''Simple example of an expression type handler'''
279 def __init__(self, name, expr, engine):
280 self._name = name
281 self._expr = expr
282 def __call__(self, econtext):
283 return self._name, self._expr
284 def __repr__(self):
285 return '<SimpleExpr %s %s>' % (self._name, `self._expr`)