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`)
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
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)
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)
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