Code

missed one
[roundup.git] / PageTemplates / TALES.py
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)
135     
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`)