Code

svn repository setup
[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 ##############################################################################
13 # Modified for Roundup:
14
15 # 1. removed all Zope-specific code (doesn't even try to import that stuff now)
16 # 2. removed all Acquisition
17 # 3. removed blocking of leading-underscore URL components
19 """Page Template Expression Engine
21 Page Template-specific implementation of TALES, with handlers
22 for Python expressions, string literals, and paths.
23 """
25 __version__='$Revision: 1.12 $'[11:-2]
27 import re, sys
28 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
29      Undefined, Default, _parse_expr
32 _engine = None
33 def getEngine():
34     global _engine
35     if _engine is None:
36         from PathIterator import Iterator
37         _engine = Engine(Iterator)
38         installHandlers(_engine)
39     return _engine
41 def installHandlers(engine):
42     reg = engine.registerType
43     pe = PathExpr
44     for pt in ('standard', 'path', 'exists', 'nocall'):
45         reg(pt, pe)
46     reg('string', StringExpr)
47     reg('python', PythonExpr)
48     reg('not', NotExpr)
49     reg('defer', DeferExpr)
51 from PythonExpr import getSecurityManager, PythonExpr
52 guarded_getattr = getattr
53 try:
54     from zExceptions import Unauthorized
55 except ImportError:
56     Unauthorized = "Unauthorized"
58 def acquisition_security_filter(orig, inst, name, v, real_validate):
59     if real_validate(orig, inst, name, v):
60         return 1
61     raise Unauthorized, name
63 def call_with_ns(f, ns, arg=1):
64     if arg==2:
65         return f(None, ns)
66     else:
67         return f(ns)
69 class _SecureModuleImporter:
70     """Simple version of the importer for use with trusted code."""
71     __allow_access_to_unprotected_subobjects__ = 1
72     def __getitem__(self, module):
73         __import__(module)
74         return sys.modules[module]
76 SecureModuleImporter = _SecureModuleImporter()
78 Undefs = (Undefined, AttributeError, KeyError,
79           TypeError, IndexError, Unauthorized)
81 def render(ob, ns):
82     """
83     Calls the object, possibly a document template, or just returns it if
84     not callable.  (From DT_Util.py)
85     """
86     if hasattr(ob, '__render_with_namespace__'):
87         ob = call_with_ns(ob.__render_with_namespace__, ns)
88     else:
89         base = ob
90         if callable(base):
91             try:
92                 if getattr(base, 'isDocTemp', 0):
93                     ob = call_with_ns(ob, ns, 2)
94                 else:
95                     ob = ob()
96             except AttributeError, n:
97                 if str(n) != '__call__':
98                     raise
99     return ob
101 class SubPathExpr:
102     def __init__(self, path):
103         self._path = path = path.strip().split('/')
104         self._base = base = path.pop(0)
105         if base and not _valid_name(base):
106             raise CompilerError, 'Invalid variable name "%s"' % base
107         # Parse path
108         self._dp = dp = []
109         for i in range(len(path)):
110             e = path[i]
111             if e[:1] == '?' and _valid_name(e[1:]):
112                 dp.append((i, e[1:]))
113         dp.reverse()
115     def _eval(self, econtext,
116               list=list, isinstance=isinstance, StringType=type('')):
117         vars = econtext.vars
118         path = self._path
119         if self._dp:
120             path = list(path) # Copy!
121             for i, varname in self._dp:
122                 val = vars[varname]
123                 if isinstance(val, StringType):
124                     path[i] = val
125                 else:
126                     # If the value isn't a string, assume it's a sequence
127                     # of path names.
128                     path[i:i+1] = list(val)
129         base = self._base
130         __traceback_info__ = 'path expression "%s"'%('/'.join(self._path))
131         if base == 'CONTEXTS' or not base:
132             ob = econtext.contexts
133         else:
134             ob = vars[base]
135         if isinstance(ob, DeferWrapper):
136             ob = ob()
137         if path:
138             ob = restrictedTraverse(ob, path, getSecurityManager())
139         return ob
141 class PathExpr:
142     def __init__(self, name, expr, engine):
143         self._s = expr
144         self._name = name
145         self._hybrid = 0
146         paths = expr.split('|')
147         self._subexprs = []
148         add = self._subexprs.append
149         for i in range(len(paths)):
150             path = paths[i].lstrip()
151             if _parse_expr(path):
152                 # This part is the start of another expression type,
153                 # so glue it back together and compile it.
154                 add(engine.compile(('|'.join(paths[i:]).lstrip())))
155                 self._hybrid = 1
156                 break
157             add(SubPathExpr(path)._eval)
159     def _exists(self, econtext):
160         for expr in self._subexprs:
161             try:
162                 expr(econtext)
163             except Undefs:
164                 pass
165             else:
166                 return 1
167         return 0
169     def _eval(self, econtext,
170               isinstance=isinstance, StringType=type(''), render=render):
171         for expr in self._subexprs[:-1]:
172             # Try all but the last subexpression, skipping undefined ones.
173             try:
174                 ob = expr(econtext)
175             except Undefs:
176                 pass
177             else:
178                 break
179         else:
180             # On the last subexpression allow exceptions through, and
181             # don't autocall if the expression was not a subpath.
182             ob = self._subexprs[-1](econtext)
183             if self._hybrid:
184                 return ob
186         if self._name == 'nocall' or isinstance(ob, StringType):
187             return ob
188         # Return the rendered object
189         return render(ob, econtext.vars)
191     def __call__(self, econtext):
192         if self._name == 'exists':
193             return self._exists(econtext)
194         return self._eval(econtext)
196     def __str__(self):
197         return '%s expression %s' % (self._name, `self._s`)
199     def __repr__(self):
200         return '%s:%s' % (self._name, `self._s`)
203 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
205 class StringExpr:
206     def __init__(self, name, expr, engine):
207         self._s = expr
208         if '%' in expr:
209             expr = expr.replace('%', '%%')
210         self._vars = vars = []
211         if '$' in expr:
212             parts = []
213             for exp in expr.split('$$'):
214                 if parts: parts.append('$')
215                 m = _interp.search(exp)
216                 while m is not None:
217                     parts.append(exp[:m.start()])
218                     parts.append('%s')
219                     vars.append(PathExpr('path', m.group(1) or m.group(2),
220                                          engine))
221                     exp = exp[m.end():]
222                     m = _interp.search(exp)
223                 if '$' in exp:
224                     raise CompilerError, (
225                         '$ must be doubled or followed by a simple path')
226                 parts.append(exp)
227             expr = ''.join(parts)
228         self._expr = expr
230     def __call__(self, econtext):
231         vvals = []
232         for var in self._vars:
233             v = var(econtext)
234             # I hope this isn't in use anymore.
235             ## if isinstance(v, Exception):
236             ##     raise v
237             vvals.append(v)
238         return self._expr % tuple(vvals)
240     def __str__(self):
241         return 'string expression %s' % `self._s`
243     def __repr__(self):
244         return 'string:%s' % `self._s`
246 class NotExpr:
247     def __init__(self, name, expr, compiler):
248         self._s = expr = expr.lstrip()
249         self._c = compiler.compile(expr)
251     def __call__(self, econtext):
252         # We use the (not x) and 1 or 0 formulation to avoid changing
253         # the representation of the result in Python 2.3, where the
254         # result of "not" becomes an instance of bool.
255         return (not econtext.evaluateBoolean(self._c)) and 1 or 0
257     def __repr__(self):
258         return 'not:%s' % `self._s`
260 class DeferWrapper:
261     def __init__(self, expr, econtext):
262         self._expr = expr
263         self._econtext = econtext
265     def __str__(self):
266         return str(self())
268     def __call__(self):
269         return self._expr(self._econtext)
271 class DeferExpr:
272     def __init__(self, name, expr, compiler):
273         self._s = expr = expr.lstrip()
274         self._c = compiler.compile(expr)
276     def __call__(self, econtext):
277         return DeferWrapper(self._c, econtext)
279     def __repr__(self):
280         return 'defer:%s' % `self._s`
282 class TraversalError:
283     def __init__(self, path, name):
284         self.path = path
285         self.name = name
289 def restrictedTraverse(object, path, securityManager,
290                        get=getattr, has=hasattr, N=None, M=[],
291                        TupleType=type(()) ):
293     REQUEST = {'path': path}
294     REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
295     path.reverse()
296     validate = securityManager.validate
297     __traceback_info__ = REQUEST
298     done = []
299     while path:
300         name = path.pop()
301         __traceback_info__ = TraversalError(done, name)
303         if isinstance(name, TupleType):
304             object = object(*name)
305             continue
307         if not name:
308             # Skip directly to item access
309             o = object[name]
310             # Check access to the item.
311             if not validate(object, object, name, o):
312                 raise Unauthorized, name
313             object = o
314             continue
316         # Try an attribute.
317         o = guarded_getattr(object, name, M)
318         if o is M:
319             # Try an item.
320             try:
321                 # XXX maybe in Python 2.2 we can just check whether
322                 # the object has the attribute "__getitem__"
323                 # instead of blindly catching exceptions.
324                 o = object[name]
325             except AttributeError, exc:
326                 if str(exc).find('__getitem__') >= 0:
327                     # The object does not support the item interface.
328                     # Try to re-raise the original attribute error.
329                     # XXX I think this only happens with
330                     # ExtensionClass instances.
331                     guarded_getattr(object, name)
332                 raise
333             except TypeError, exc:
334                 if str(exc).find('unsubscriptable') >= 0:
335                     # The object does not support the item interface.
336                     # Try to re-raise the original attribute error.
337                     # XXX This is sooooo ugly.
338                     guarded_getattr(object, name)
339                 raise
340         done.append((name, o))
341         object = o
343     return object