Code

8a391b6df09fbd000e2c03ee434c25e48b47c189
[roundup.git] / roundup / cgi / PageTemplates / Expressions.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 ##############################################################################
14 """Page Template Expression Engine
16 Page Template-specific implementation of TALES, with handlers
17 for Python expressions, string literals, and paths.
20 Modified for Roundup 0.5 release:
22 - Removed all Zope-specific code (doesn't even try to import that stuff now)
23 - Removed all Acquisition
24 - Made traceback info more informative
26 """
28 __version__='$Revision: 1.8 $'[11:-2]
30 import re, sys
31 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
32      Undefined, Default, _parse_expr
33 from string import strip, split, join, replace, lstrip
35 _engine = None
36 def getEngine():
37     global _engine
38     if _engine is None:
39         from PathIterator import Iterator
40         _engine = Engine(Iterator)
41         installHandlers(_engine)
42     return _engine
44 def installHandlers(engine):
45     reg = engine.registerType
46     pe = PathExpr
47     for pt in ('standard', 'path', 'exists', 'nocall'):
48         reg(pt, pe)
49     reg('string', StringExpr)
50     reg('python', PythonExpr)
51     reg('not', NotExpr)
52     reg('defer', DeferExpr)
54 from PythonExpr import getSecurityManager, PythonExpr
55 try:
56     from zExceptions import Unauthorized
57 except ImportError:
58     Unauthorized = "Unauthorized"
59 def call_with_ns(f, ns, arg=1):
60     if arg==2:
61         return f(None, ns)
62     else:
63         return f(ns)
65 class _SecureModuleImporter:
66     """Simple version of the importer for use with trusted code."""
67     __allow_access_to_unprotected_subobjects__ = 1
68     def __getitem__(self, module):
69         __import__(module)
70         return sys.modules[module]
72 Undefs = (Undefined, AttributeError, KeyError,
73           TypeError, IndexError, Unauthorized)
75 def render(ob, ns):
76     """
77     Calls the object, possibly a document template, or just returns it if
78     not callable.  (From DT_Util.py)
79     """
80     if hasattr(ob, '__render_with_namespace__'):
81         ob = call_with_ns(ob.__render_with_namespace__, ns)
82     else:
83         base = ob
84         if callable(base):
85             try:
86                 if getattr(base, 'isDocTemp', 0):
87                     ob = call_with_ns(ob, ns, 2)
88                 else:
89                     ob = ob()
90             except AttributeError, n:
91                 if str(n) != '__call__':
92                     raise
93     return ob
95 class SubPathExpr:
96     def __init__(self, path):
97         self._path = path = split(strip(path), '/')
98         self._base = base = path.pop(0)
99         if not _valid_name(base):
100             raise CompilerError, 'Invalid variable name "%s"' % base
101         # Parse path
102         self._dp = dp = []
103         for i in range(len(path)):
104             e = path[i]
105             if e[:1] == '?' and _valid_name(e[1:]):
106                 dp.append((i, e[1:]))
107         dp.reverse()
109     def _eval(self, econtext,
110               list=list, isinstance=isinstance, StringType=type('')):
111         vars = econtext.vars
112         path = self._path
113         if self._dp:
114             path = list(path) # Copy!
115             for i, varname in self._dp:
116                 val = vars[varname]
117                 if isinstance(val, StringType):
118                     path[i] = val
119                 else:
120                     # If the value isn't a string, assume it's a sequence
121                     # of path names.
122                     path[i:i+1] = list(val)
123         base = self._base
124         __traceback_info__ = 'path expression "%s"'%('/'.join(self._path))
125         if base == 'CONTEXTS':
126             ob = econtext.contexts
127         else:
128             ob = vars[base]
129         if isinstance(ob, DeferWrapper):
130             ob = ob()
131         if path:
132             ob = restrictedTraverse(ob, path, getSecurityManager())
133         return ob
135 class PathExpr:
136     def __init__(self, name, expr, engine):
137         self._s = expr
138         self._name = name
139         self._hybrid = 0
140         paths = split(expr, '|')
141         self._subexprs = []
142         add = self._subexprs.append
143         for i in range(len(paths)):
144             path = lstrip(paths[i])
145             if _parse_expr(path):
146                 # This part is the start of another expression type,
147                 # so glue it back together and compile it.
148                 add(engine.compile(lstrip(join(paths[i:], '|'))))
149                 self._hybrid = 1
150                 break
151             add(SubPathExpr(path)._eval)
153     def _exists(self, econtext):
154         for expr in self._subexprs:
155             try:
156                 expr(econtext)
157             except Undefs:
158                 pass
159             else:
160                 return 1
161         return 0
163     def _eval(self, econtext,
164               isinstance=isinstance, StringType=type(''), render=render):
165         for expr in self._subexprs[:-1]:
166             # Try all but the last subexpression, skipping undefined ones.
167             try:
168                 ob = expr(econtext)
169             except Undefs:
170                 pass
171             else:
172                 break
173         else:
174             # On the last subexpression allow exceptions through, and
175             # don't autocall if the expression was not a subpath.
176             ob = self._subexprs[-1](econtext)
177             if self._hybrid:
178                 return ob
180         if self._name == 'nocall' or isinstance(ob, StringType):
181             return ob
182         # Return the rendered object
183         return render(ob, econtext.vars)
185     def __call__(self, econtext):
186         if self._name == 'exists':
187             return self._exists(econtext)
188         return self._eval(econtext)
190     def __str__(self):
191         return '%s expression %s' % (self._name, `self._s`)
193     def __repr__(self):
194         return '%s:%s' % (self._name, `self._s`)
196             
197 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/%(n)s)*)}' % {'n': NAME_RE})
199 class StringExpr:
200     def __init__(self, name, expr, engine):
201         self._s = expr
202         if '%' in expr:
203             expr = replace(expr, '%', '%%')
204         self._vars = vars = []
205         if '$' in expr:
206             parts = []
207             for exp in split(expr, '$$'):
208                 if parts: parts.append('$')
209                 m = _interp.search(exp)
210                 while m is not None:
211                     parts.append(exp[:m.start()])
212                     parts.append('%s')
213                     vars.append(PathExpr('path', m.group(1) or m.group(2),
214                                          engine))
215                     exp = exp[m.end():]
216                     m = _interp.search(exp)
217                 if '$' in exp:
218                     raise CompilerError, (
219                         '$ must be doubled or followed by a simple path')
220                 parts.append(exp)
221             expr = join(parts, '')
222         self._expr = expr
223         
224     def __call__(self, econtext):
225         vvals = []
226         for var in self._vars:
227             v = var(econtext)
228             if isinstance(v, Exception):
229                 raise v
230             vvals.append(v)
231         return self._expr % tuple(vvals)
233     def __str__(self):
234         return 'string expression %s' % `self._s`
236     def __repr__(self):
237         return 'string:%s' % `self._s`
239 class NotExpr:
240     def __init__(self, name, expr, compiler):
241         self._s = expr = lstrip(expr)
242         self._c = compiler.compile(expr)
243         
244     def __call__(self, econtext):
245         return not econtext.evaluateBoolean(self._c)
247     def __repr__(self):
248         return 'not:%s' % `self._s`
250 class DeferWrapper:
251     def __init__(self, expr, econtext):
252         self._expr = expr
253         self._econtext = econtext
255     def __str__(self):
256         return str(self())
258     def __call__(self):
259         return self._expr(self._econtext)
261 class DeferExpr:
262     def __init__(self, name, expr, compiler):
263         self._s = expr = lstrip(expr)
264         self._c = compiler.compile(expr)
265         
266     def __call__(self, econtext):
267         return DeferWrapper(self._c, econtext)
269     def __repr__(self):
270         return 'defer:%s' % `self._s`
272 class TraversalError:
273     def __init__(self, path, name):
274         self.path = path
275         self.name = name
277 def restrictedTraverse(self, path, securityManager,
278                        get=getattr, has=hasattr, N=None, M=[],
279                        TupleType=type(()) ):
281     REQUEST = {'path': path}
282     REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
283     if not path[0]:
284         # If the path starts with an empty string, go to the root first.
285         self = self.getPhysicalRoot()
286         path.pop(0)
288     path.reverse()
289     object = self
290     #print 'TRAVERSE', (object, path)
291     done = []
292     while path:
293         name = path.pop()
294         __traceback_info__ = TraversalError(done, name)
296 #        if isinstance(name, TupleType):
297 #            object = apply(object, name)
298 #            continue
300 #        if name[0] == '_':
301 #            # Never allowed in a URL.
302 #            raise AttributeError, name
304         # Try an attribute.
305         o = get(object, name, M)
306 #       print '...', (object, name, M, o)
307         if o is M:
308             # Try an item.
309 #           print '... try an item'
310             try:
311                 # XXX maybe in Python 2.2 we can just check whether
312                 # the object has the attribute "__getitem__"
313                 # instead of blindly catching exceptions.
314                 o = object[name]
315             except AttributeError, exc:
316                 if str(exc).find('__getitem__') >= 0:
317                     # The object does not support the item interface.
318                     # Try to re-raise the original attribute error.
319                     # XXX I think this only happens with
320                     # ExtensionClass instances.
321                     get(object, name)
322                 raise
323             except TypeError, exc:
324                 if str(exc).find('unsubscriptable') >= 0:
325                     # The object does not support the item interface.
326                     # Try to re-raise the original attribute error.
327                     # XXX This is sooooo ugly.
328                     get(object, name)
329                 raise
330         #print '... object is now', `o`
331         object = o
332         done.append((name, o))
334     return object