ac8aec9e638160a6c2bc998c4775b8a997f3e5f8
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 """
21 __docformat__ = 'restructuredtext'
23 __version__='$Revision: 1.6 $'[11:-2]
25 import re, sys
26 from roundup.cgi import ZTUtils
27 from MultiMapping import MultiMapping
29 StringType = type('')
31 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
32 _parse_expr = re.compile(r"(%s):" % NAME_RE).match
33 _valid_name = re.compile('%s$' % NAME_RE).match
35 class TALESError(Exception):
36 """Error during TALES expression evaluation"""
38 class Undefined(TALESError):
39 '''Exception raised on traversal of an undefined path'''
41 class RegistrationError(Exception):
42 '''TALES Type Registration Error'''
44 class CompilerError(Exception):
45 '''TALES Compiler Error'''
47 class Default:
48 '''Retain Default'''
49 Default = Default()
51 _marker = []
53 class SafeMapping(MultiMapping):
54 '''Mapping with security declarations and limited method exposure.
56 Since it subclasses MultiMapping, this class can be used to wrap
57 one or more mapping objects. Restricted Python code will not be
58 able to mutate the SafeMapping or the wrapped mappings, but will be
59 able to read any value.
60 '''
61 __allow_access_to_unprotected_subobjects__ = 1
62 push = pop = None
64 _push = MultiMapping.push
65 _pop = MultiMapping.pop
67 def has_get(self, key, _marker=[]):
68 v = self.get(key, _marker)
69 return v is not _marker, v
71 class Iterator(ZTUtils.Iterator):
72 def __init__(self, name, seq, context):
73 ZTUtils.Iterator.__init__(self, seq)
74 self.name = name
75 self._context = context
77 def next(self):
78 if ZTUtils.Iterator.next(self):
79 self._context.setLocal(self.name, self.item)
80 return 1
81 return 0
84 class ErrorInfo:
85 """Information about an exception passed to an on-error handler."""
86 __allow_access_to_unprotected_subobjects__ = 1
88 def __init__(self, err, position=(None, None)):
89 if isinstance(err, Exception):
90 self.type = err.__class__
91 self.value = err
92 else:
93 self.type = err
94 self.value = None
95 self.lineno = position[0]
96 self.offset = position[1]
99 class Engine:
100 '''Expression Engine
102 An instance of this class keeps a mutable collection of expression
103 type handlers. It can compile expression strings by delegating to
104 these handlers. It can provide an expression Context, which is
105 capable of holding state and evaluating compiled expressions.
106 '''
107 Iterator = Iterator
109 def __init__(self, Iterator=None):
110 self.types = {}
111 if Iterator is not None:
112 self.Iterator = Iterator
114 def registerType(self, name, handler):
115 if not _valid_name(name):
116 raise RegistrationError, 'Invalid Expression type "%s".' % name
117 types = self.types
118 if types.has_key(name):
119 raise RegistrationError, (
120 'Multiple registrations for Expression type "%s".' %
121 name)
122 types[name] = handler
124 def getTypes(self):
125 return self.types
127 def compile(self, expression):
128 m = _parse_expr(expression)
129 if m:
130 type = m.group(1)
131 expr = expression[m.end():]
132 else:
133 type = "standard"
134 expr = expression
135 try:
136 handler = self.types[type]
137 except KeyError:
138 raise CompilerError, (
139 'Unrecognized expression type "%s".' % type)
140 return handler(type, expr, self)
142 def getContext(self, contexts=None, **kwcontexts):
143 if contexts is not None:
144 if kwcontexts:
145 kwcontexts.update(contexts)
146 else:
147 kwcontexts = contexts
148 return Context(self, kwcontexts)
150 def getCompilerError(self):
151 return CompilerError
153 class Context:
154 '''Expression Context
156 An instance of this class holds context information that it can
157 use to evaluate compiled expressions.
158 '''
160 _context_class = SafeMapping
161 position = (None, None)
162 source_file = None
164 def __init__(self, compiler, contexts):
165 self._compiler = compiler
166 self.contexts = contexts
167 contexts['nothing'] = None
168 contexts['default'] = Default
170 self.repeat_vars = rv = {}
171 # Wrap this, as it is visible to restricted code
172 contexts['repeat'] = rep = self._context_class(rv)
173 contexts['loop'] = rep # alias
175 self.global_vars = gv = contexts.copy()
176 self.local_vars = lv = {}
177 self.vars = self._context_class(gv, lv)
179 # Keep track of what needs to be popped as each scope ends.
180 self._scope_stack = []
182 def getCompiler(self):
183 return self._compiler
185 def beginScope(self):
186 self._scope_stack.append([self.local_vars.copy()])
188 def endScope(self):
189 scope = self._scope_stack.pop()
190 self.local_vars = lv = scope[0]
191 v = self.vars
192 v._pop()
193 v._push(lv)
194 # Pop repeat variables, if any
195 i = len(scope) - 1
196 while i:
197 name, value = scope[i]
198 if value is None:
199 del self.repeat_vars[name]
200 else:
201 self.repeat_vars[name] = value
202 i = i - 1
204 def setLocal(self, name, value):
205 self.local_vars[name] = value
207 def setGlobal(self, name, value):
208 self.global_vars[name] = value
210 def setRepeat(self, name, expr):
211 expr = self.evaluate(expr)
212 if not expr:
213 return self._compiler.Iterator(name, (), self)
214 it = self._compiler.Iterator(name, expr, self)
215 old_value = self.repeat_vars.get(name)
216 self._scope_stack[-1].append((name, old_value))
217 self.repeat_vars[name] = it
218 return it
220 def evaluate(self, expression,
221 isinstance=isinstance, StringType=StringType):
222 if isinstance(expression, StringType):
223 expression = self._compiler.compile(expression)
224 __traceback_supplement__ = (
225 TALESTracebackSupplement, self, expression)
226 v = expression(self)
227 return v
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 return str(text)
238 def evaluateStructure(self, expr):
239 return self.evaluate(expr)
240 evaluateStructure = evaluate
242 def evaluateMacro(self, expr):
243 # XXX Should return None or a macro definition
244 return self.evaluate(expr)
245 evaluateMacro = evaluate
247 def createErrorInfo(self, err, position):
248 return ErrorInfo(err, position)
250 def getDefault(self):
251 return Default
253 def setSourceFile(self, source_file):
254 self.source_file = source_file
256 def setPosition(self, position):
257 self.position = position
261 class TALESTracebackSupplement:
262 """Implementation of ITracebackSupplement"""
263 def __init__(self, context, expression):
264 self.context = context
265 self.source_url = context.source_file
266 self.line = context.position[0]
267 self.column = context.position[1]
268 self.expression = repr(expression)
270 def getInfo(self, as_html=0):
271 import pprint
272 data = self.context.contexts.copy()
273 s = pprint.pformat(data)
274 if not as_html:
275 return ' - Names:\n %s' % string.replace(s, '\n', '\n ')
276 else:
277 from cgi import escape
278 return '<b>Names:</b><pre>%s</pre>' % (escape(s))
279 return None
283 class SimpleExpr:
284 '''Simple example of an expression type handler'''
285 def __init__(self, name, expr, engine):
286 self._name = name
287 self._expr = expr
288 def __call__(self, econtext):
289 return self._name, self._expr
290 def __repr__(self):
291 return '<SimpleExpr %s %s>' % (self._name, `self._expr`)