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