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