Code

Adding PageTemplates to the dist
[roundup.git] / 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.
18 """
20 __version__='$Revision: 1.1 $'[11:-2]
22 import re, sys
23 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
24      Undefined, Default, _parse_expr
25 from string import strip, split, join, replace, lstrip
26 from Acquisition import aq_base, aq_inner, aq_parent
29 _engine = None
30 def getEngine():
31     global _engine
32     if _engine is None:
33         from PathIterator import Iterator
34         _engine = Engine(Iterator)
35         installHandlers(_engine)
36     return _engine
38 def installHandlers(engine):
39     reg = engine.registerType
40     pe = PathExpr
41     for pt in ('standard', 'path', 'exists', 'nocall'):
42         reg(pt, pe)
43     reg('string', StringExpr)
44     reg('python', PythonExpr)
45     reg('not', NotExpr)
46     reg('defer', DeferExpr)
48 if sys.modules.has_key('Zope'):
49     import AccessControl
50     from AccessControl import getSecurityManager
51     try:
52         from AccessControl import Unauthorized
53     except ImportError:
54         Unauthorized = "Unauthorized"
55     if hasattr(AccessControl, 'full_read_guard'):
56         from ZRPythonExpr import PythonExpr, _SecureModuleImporter, \
57              call_with_ns
58     else:
59         from ZPythonExpr import PythonExpr, _SecureModuleImporter, \
60              call_with_ns
61 else:
62     from PythonExpr import getSecurityManager, PythonExpr
63     try:
64         from zExceptions import Unauthorized
65     except ImportError:
66         Unauthorized = "Unauthorized"
67     def call_with_ns(f, ns, arg=1):
68         if arg==2:
69             return f(None, ns)
70         else:
71             return f(ns)
73     class _SecureModuleImporter:
74         """Simple version of the importer for use with trusted code."""
75         __allow_access_to_unprotected_subobjects__ = 1
76         def __getitem__(self, module):
77             __import__(module)
78             return sys.modules[module]
80 SecureModuleImporter = _SecureModuleImporter()
82 Undefs = (Undefined, AttributeError, KeyError,
83           TypeError, IndexError, Unauthorized)
85 def render(ob, ns):
86     """
87     Calls the object, possibly a document template, or just returns it if
88     not callable.  (From DT_Util.py)
89     """
90     if hasattr(ob, '__render_with_namespace__'):
91         ob = call_with_ns(ob.__render_with_namespace__, ns)
92     else:
93         base = aq_base(ob)
94         if callable(base):
95             try:
96                 if getattr(base, 'isDocTemp', 0):
97                     ob = call_with_ns(ob, ns, 2)
98                 else:
99                     ob = ob()
100             except AttributeError, n:
101                 if str(n) != '__call__':
102                     raise
103     return ob
105 class SubPathExpr:
106     def __init__(self, path):
107         self._path = path = split(strip(path), '/')
108         self._base = base = path.pop(0)
109         if not _valid_name(base):
110             raise CompilerError, 'Invalid variable name "%s"' % base
111         # Parse path
112         self._dp = dp = []
113         for i in range(len(path)):
114             e = path[i]
115             if e[:1] == '?' and _valid_name(e[1:]):
116                 dp.append((i, e[1:]))
117         dp.reverse()
119     def _eval(self, econtext,
120               list=list, isinstance=isinstance, StringType=type('')):
121         vars = econtext.vars
122         path = self._path
123         if self._dp:
124             path = list(path) # Copy!
125             for i, varname in self._dp:
126                 val = vars[varname]
127                 if isinstance(val, StringType):
128                     path[i] = val
129                 else:
130                     # If the value isn't a string, assume it's a sequence
131                     # of path names.
132                     path[i:i+1] = list(val)
133         __traceback_info__ = base = self._base
134         if base == 'CONTEXTS':
135             ob = econtext.contexts
136         else:
137             ob = vars[base]
138         if isinstance(ob, DeferWrapper):
139             ob = ob()
140         if path:
141             ob = restrictedTraverse(ob, path, getSecurityManager())
142         return ob
144 class PathExpr:
145     def __init__(self, name, expr, engine):
146         self._s = expr
147         self._name = name
148         paths = split(expr, '|')
149         self._subexprs = []
150         add = self._subexprs.append
151         for i in range(len(paths)):
152             path = lstrip(paths[i])
153             if _parse_expr(path):
154                 # This part is the start of another expression type,
155                 # so glue it back together and compile it.
156                 add(engine.compile(lstrip(join(paths[i:], '|'))))
157                 break
158             add(SubPathExpr(path)._eval)
160     def _exists(self, econtext):
161         for expr in self._subexprs:
162             try:
163                 expr(econtext)
164             except Undefs:
165                 pass
166             else:
167                 return 1
168         return 0
170     def _eval(self, econtext,
171               isinstance=isinstance, StringType=type(''), render=render):
172         for expr in self._subexprs[:-1]:
173             # Try all but the last subexpression, skipping undefined ones.
174             try:
175                 ob = expr(econtext)
176             except Undefs:
177                 pass
178             else:
179                 break
180         else:
181             # On the last subexpression allow exceptions through.
182             ob = self._subexprs[-1](econtext)
184         if self._name == 'nocall' or isinstance(ob, StringType):
185             return ob
186         # Return the rendered object
187         return render(ob, econtext.vars)
189     def __call__(self, econtext):
190         if self._name == 'exists':
191             return self._exists(econtext)
192         return self._eval(econtext)
194     def __str__(self):
195         return '%s expression %s' % (self._name, `self._s`)
197     def __repr__(self):
198         return '%s:%s' % (self._name, `self._s`)
200             
201 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/%(n)s)*)}' % {'n': NAME_RE})
203 class StringExpr:
204     def __init__(self, name, expr, engine):
205         self._s = expr
206         if '%' in expr:
207             expr = replace(expr, '%', '%%')
208         self._vars = vars = []
209         if '$' in expr:
210             parts = []
211             for exp in split(expr, '$$'):
212                 if parts: parts.append('$')
213                 m = _interp.search(exp)
214                 while m is not None:
215                     parts.append(exp[:m.start()])
216                     parts.append('%s')
217                     vars.append(PathExpr('path', m.group(1) or m.group(2),
218                                          engine))
219                     exp = exp[m.end():]
220                     m = _interp.search(exp)
221                 if '$' in exp:
222                     raise CompilerError, (
223                         '$ must be doubled or followed by a simple path')
224                 parts.append(exp)
225             expr = join(parts, '')
226         self._expr = expr
227         
228     def __call__(self, econtext):
229         vvals = []
230         for var in self._vars:
231             v = var(econtext)
232             if isinstance(v, Exception):
233                 raise v
234             vvals.append(v)
235         return self._expr % tuple(vvals)
237     def __str__(self):
238         return 'string expression %s' % `self._s`
240     def __repr__(self):
241         return 'string:%s' % `self._s`
243 class NotExpr:
244     def __init__(self, name, expr, compiler):
245         self._s = expr = lstrip(expr)
246         self._c = compiler.compile(expr)
247         
248     def __call__(self, econtext):
249         return not econtext.evaluateBoolean(self._c)
251     def __repr__(self):
252         return 'not:%s' % `self._s`
254 class DeferWrapper:
255     def __init__(self, expr, econtext):
256         self._expr = expr
257         self._econtext = econtext
259     def __str__(self):
260         return str(self())
262     def __call__(self):
263         return self._expr(self._econtext)
265 class DeferExpr:
266     def __init__(self, name, expr, compiler):
267         self._s = expr = lstrip(expr)
268         self._c = compiler.compile(expr)
269         
270     def __call__(self, econtext):
271         return DeferWrapper(self._c, econtext)
273     def __repr__(self):
274         return 'defer:%s' % `self._s`
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         if not securityManager.validateValue(self):
287             raise Unauthorized, name
288         path.pop(0)
289         
290     path.reverse()
291     validate = securityManager.validate
292     object = self
293     #print 'TRAVERSE', (object, path)
294     while path:
295         __traceback_info__ = REQUEST
296         name = path.pop()
298         if isinstance(name, TupleType):
299             object = apply(object, name)
300             continue
302         if name[0] == '_':
303             # Never allowed in a URL.
304             raise AttributeError, name
306         if name=='..':
307             o = get(object, 'aq_parent', M)
308             if o is not M:
309                 if not validate(object, object, name, o):
310                     raise Unauthorized, name
311                 object=o
312                 continue
314         t = get(object, '__bobo_traverse__', N)
315         if t is not N:
316             o=t(REQUEST, name)
317                     
318             container = None
319             if has(o, 'im_self'):
320                 container = o.im_self
321             elif (has(get(object, 'aq_base', object), name)
322                 and get(object, name) == o):
323                 container = object
324             if not validate(object, container, name, o):
325                 raise Unauthorized, name
326         else:
327             # Try an attribute.
328             o = get(object, name, M)
329         #    print '...', (object, name, M, o)
330             if o is not M:
331                 # Check access to the attribute.
332                 if has(object, 'aq_acquire'):
333                     object.aq_acquire(
334                         name, validate2, validate)
335                 else:
336                     if not validate(object, object, name, o):
337                         raise Unauthorized, name
338             else:
339                 # Try an item.
340                 try:
341                     # XXX maybe in Python 2.2 we can just check whether
342                     # the object has the attribute "__getitem__"
343                     # instead of blindly catching exceptions.
344         #            print 'Try an item', (object, name)
345                     o = object[name]
346                 except AttributeError, exc:
347                     if str(exc).find('__getitem__') >= 0:
348                         # The object does not support the item interface.
349                         # Try to re-raise the original attribute error.
350                         # XXX I think this only happens with
351                         # ExtensionClass instances.
352                         get(object, name)
353                     raise
354                 except TypeError, exc:
355                     if str(exc).find('unsubscriptable') >= 0:
356                         # The object does not support the item interface.
357                         # Try to re-raise the original attribute error.
358                         # XXX This is sooooo ugly.
359                         get(object, name)
360                     raise
361                 else:
362                     # Check access to the item.
363                     if not validate(object, object, name, o):
364                         raise Unauthorized, name
365         #print '... object is now', `o`
366         object = o
368     return object
371 def validate2(orig, inst, name, v, real_validate):
372     if not real_validate(orig, inst, name, v):
373         raise Unauthorized, name
374     return 1