[Zope-Checkins] CVS: Zope3/lib/python/Zope/PageTemplate - Expressions.py:1.1.2.9
Shane Hathaway
shane@cvs.zope.org
Wed, 13 Mar 2002 22:49:26 -0500
Update of /cvs-repository/Zope3/lib/python/Zope/PageTemplate
In directory cvs.zope.org:/tmp/cvs-serv9280
Modified Files:
Tag: Zope-3x-branch
Expressions.py
Log Message:
- Updated license.
- Moved engine creation to EngineConfig.
- Brought in sync, somewhat, with the trunk.
- Added a parameter to PathExpr.__init__ that allows you to plug in a
traversal function.
=== Zope3/lib/python/Zope/PageTemplate/Expressions.py 1.1.2.8 => 1.1.2.9 ===
-#
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
-
-"""Page Template Expression Engine
-
-Page Template-specific implementation of TALES, with handlers
-for Python expressions, string literals, and paths.
+##############################################################################
+"""Basic Page Template expression types.
"""
__version__='$Revision$'[11:-2]
import re, sys
-from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
- TALESError, Undefined, Default
+from Zope.Exceptions import Unauthorized
+from TALES import ExpressionEngine, CompilerError, RegistrationError
+from TALES import _valid_name, _parse_expr, NAME_RE, Undefined
+from PythonExpr import PythonExpr
-_engine = None
-def getEngine():
- global _engine
- if _engine is None:
- _engine = Engine()
- installHandlers(_engine)
- _engine._nocatch = (TALESError, 'Redirect')
- return _engine
-
-def installHandlers(engine):
- reg = engine.registerType
- pe = PathExpr
- for pt in ('standard', 'path', 'exists', 'nocall'):
- reg(pt, pe)
- reg('string', StringExpr)
- reg('python', PythonExpr)
- reg('not', NotExpr)
- reg('defer', DeferExpr)
-
-if sys.modules.has_key('Zope'):
- # Apply security restrictions to expressions.
- from Zope.App.Security.SecurityManagement import getSecurityManager
- from Zope.Exceptions import Unauthorized
- from ZRPythonExpr import PythonExpr, _SecureModuleImporter#, call_with_ns
- SecureModuleImporter = _SecureModuleImporter()
-else:
- # No security available.
- from PythonExpr import getSecurityManager, PythonExpr
- from Zope.Exceptions import Unauthorized
-
-## def call_with_ns(f, ns, arg=1):
-## if arg == 2:
-## return f(None, ns)
-## else:
-## return f(ns)
-
- class SecureModuleImporter:
- def __getitem__(self, module):
- __import__(module)
- return sys.modules[module]
- SecureModuleImporter = SecureModuleImporter()
+Undefs = (Undefined, AttributeError, KeyError,
+ TypeError, IndexError, Unauthorized)
-def render(ob, ns):
- """
- Calls the object, possibly a document template, or just returns it if
- not callable. (From DT_Util.py)
+def simpleTraverse(object, path_items):
+ """Traverses a sequence of names, first trying attributes then items.
"""
-## if hasattr(ob, '__render_with_namespace__'):
-## ob = call_with_ns(ob.__render_with_namespace__, ns)
-## elif hasattr(ob, "__call__"):
- if hasattr(ob, "__call__"):
- # We don't use callable(ob) since ExtensionClass-based content
- # will return true even if they don't define a __call__()
- # method; this is the same false positive as classic-classes.
- ob = ob()
- return ob
-
+ for name in path_items:
+ if hasattr(object, name):
+ object = getattr(object, name)
+ elif hasattr(object, '__getitem__'):
+ object = object[name]
+ else:
+ raise NotFoundError, name
+ return object
-class PathExpr:
- def __init__(self, name, expr, engine):
- self._s = expr
- self._name = name
- self._paths = map(self._prepPath, expr.split('|'))
- def _prepPath(self, path):
- path = path.strip().split('/')
- base = path.pop(0)
+class SubPathExpr:
+ def __init__(self, path, traverser):
+ self._path = path = str(path).strip().split('/')
+ self._base = base = path.pop(0)
+ self._traverser = traverser
if not _valid_name(base):
raise CompilerError, 'Invalid variable name "%s"' % base
# Parse path
- dp = []
+ self._dp = dp = []
for i in range(len(path)):
e = path[i]
- if e.startswith('?') and _valid_name(e[1:]):
+ if e[:1] == '?' and _valid_name(e[1:]):
dp.append((i, e[1:]))
dp.reverse()
- return base, path, dp
- def _eval(self, econtext, securityManager,
- list=list, isinstance=isinstance, StringType=type(''),
- render=render):
+ def _eval(self, econtext,
+ list=list, isinstance=isinstance, StringType=type('')):
vars = econtext.vars
- exists = 0
- for base, path, dp in self._paths:
- # Expand dynamic path parts from right to left.
- if dp:
- path = list(path) # Copy!
- for i, varname in dp:
- val = vars[varname]
- if isinstance(val, StringType):
- path[i] = val
- else:
- # If the value isn't a string, assume it's a sequence
- # of path names.
- path[i:i+1] = list(val)
- try:
- __traceback_info__ = base
- if base == 'CONTEXTS':
- ob = econtext.contexts
+ path = self._path
+ if self._dp:
+ path = list(path) # Copy!
+ for i, varname in self._dp:
+ val = vars[varname]
+ if isinstance(val, StringType):
+ path[i] = val
else:
- ob = vars[base]
- if isinstance(ob, DeferWrapper):
- ob = ob()
- if path:
- ob = restrictedTraverse(ob, path, securityManager)
- exists = 1
+ # If the value isn't a string, assume it's a sequence
+ # of path names.
+ path[i:i+1] = list(val)
+ base = self._base
+ if base == 'CONTEXTS': # Special base name
+ ob = econtext.contexts
+ else:
+ ob = vars[base]
+ if isinstance(ob, DeferWrapper):
+ ob = ob()
+ if path:
+ ob = self._traverser(ob, path)
+ return ob
+
+
+
+class PathExpr:
+ """One or more subpath expressions, separated by '|'.
+ """
+
+ # _default_type_names contains the expression type names this
+ # class is usually registered for.
+ _default_type_names = (
+ 'standard',
+ 'path',
+ 'exists',
+ 'nocall',
+ )
+
+ def __init__(self, name, expr, engine, traverser=simpleTraverse):
+ self._s = expr
+ self._name = name
+ paths = expr.split('|')
+ self._subexprs = []
+ add = self._subexprs.append
+ for i in range(len(paths)):
+ path = paths[i].lstrip()
+ if _parse_expr(path):
+ # This part is the start of another expression type,
+ # so glue it back together and compile it.
+ add(engine.compile('|'.join(paths[i:]).lstrip()))
break
- except Undefined, e:
- ob = e
- except (AttributeError, KeyError, TypeError, IndexError,
- Unauthorized), e:
- ob = Undefined(self._s, sys.exc_info())
+ add(SubPathExpr(path, traverser)._eval)
- if self._name == 'exists':
- # All we wanted to know is whether one of the paths exist.
- return exists
- if self._name == 'nocall' or isinstance(ob, StringType):
+ def _exists(self, econtext):
+ for expr in self._subexprs:
+ try:
+ expr(econtext)
+ except Undefs:
+ pass
+ else:
+ return 1
+ return 0
+
+ def _eval(self, econtext):
+ for expr in self._subexprs[:-1]:
+ # Try all but the last subexpression, skipping undefined ones.
+ try:
+ ob = expr(econtext)
+ except Undefs:
+ pass
+ else:
+ break
+ else:
+ # On the last subexpression allow exceptions through.
+ ob = self._subexprs[-1](econtext)
+
+ if self._name == 'nocall':
return ob
- # Return the rendered object
- return render(ob, vars)
+
+ # Call the object if it is callable.
+ if hasattr(ob, '__call__'):
+ return ob()
+ return ob
def __call__(self, econtext):
- return self._eval(econtext, getSecurityManager())
+ if self._name == 'exists':
+ return self._exists(econtext)
+ return self._eval(econtext)
def __str__(self):
- return '%s expression %s' % (self._name, `self._s`)
+ return '%s expression (%s)' % (self._name, `self._s`)
def __repr__(self):
- return '%s:%s' % (self._name, `self._s`)
+ return '<PathExpr %s:%s>' % (self._name, `self._s`)
-
-_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/%(n)s)*)}' % {'n': NAME_RE})
+
+
+_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
class StringExpr:
def __init__(self, name, expr, engine):
@@ -161,6 +162,8 @@
expr = expr.replace('%', '%%')
self._vars = vars = []
if '$' in expr:
+ # Use whatever expr type is registered as "path".
+ path_type = engine.getTypes()['path']
parts = []
for exp in expr.split('$$'):
if parts: parts.append('$')
@@ -168,8 +171,8 @@
while m is not None:
parts.append(exp[:m.start()])
parts.append('%s')
- vars.append(PathExpr('path', m.group(1) or m.group(2),
- engine))
+ vars.append(path_type(
+ 'path', m.group(1) or m.group(2), engine))
exp = exp[m.end():]
m = _interp.search(exp)
if '$' in exp:
@@ -183,27 +186,27 @@
vvals = []
for var in self._vars:
v = var(econtext)
- if isinstance(v, Exception):
- raise v
vvals.append(v)
return self._expr % tuple(vvals)
def __str__(self):
- return 'string expression %s' % `self._s`
+ return 'string expression (%s)' % `self._s`
def __repr__(self):
- return 'string:%s' % `self._s`
+ return '<StringExpr %s>' % `self._s`
+
class NotExpr:
- def __init__(self, name, expr, compiler):
+ def __init__(self, name, expr, engine):
self._s = expr = expr.lstrip()
- self._c = compiler.compile(expr)
-
+ self._c = engine.compile(expr)
+
def __call__(self, econtext):
return not econtext.evaluateBoolean(self._c)
def __repr__(self):
- return 'not:%s' % `self._s`
+ return '<NotExpr %s>' % `self._s`
+
class DeferWrapper:
def __init__(self, expr, econtext):
@@ -216,6 +219,7 @@
def __call__(self):
return self._expr(self._econtext)
+
class DeferExpr:
def __init__(self, name, expr, compiler):
self._s = expr = expr.lstrip()
@@ -225,62 +229,15 @@
return DeferWrapper(self._c, econtext)
def __repr__(self):
- return 'defer:%s' % `self._s`
+ return '<DeferExpr %s>' % `self._s`
-def restrictedTraverse(self, path, securityManager,
- get=getattr, has=hasattr, N=None, M=object()):
+class SimpleModuleImporter:
+ """Minimal module importer with no security."""
+ def __getitem__(self, module):
+ mod = __import__(module)
+ path = module.split('.')
+ for name in path[1:]:
+ mod = getattr(mod, name)
+ return mod
- i = 0
- if not path[0]:
- # If the path starts with an empty string, go to the root first.
- self = self.getPhysicalRoot()
- if not securityManager.validateValue(self):
- raise Unauthorized, name
- i = 1
-
- plen = len(path)
- REQUEST={'TraversalRequestNameStack': path}
- validate = securityManager.validate
- object = self
- while i < plen:
- __traceback_info__ = (path, i)
- name = path[i]
- i = i + 1
-
- if name[0] == '_':
- # Never allowed in a URL.
- raise AttributeError, name
-
- if name == '..':
- o = get(object, 'aq_parent', M)
- if o is not M:
- validate(name, o)
- object = o
- continue
-
- t = get(object, '__bobo_traverse__', N)
- if t is not N:
- o = t(REQUEST, name)
-
-## container = None
-## if has(o, 'im_self'):
-## container = o.im_self
-## elif (has(get(object, 'aq_base', object), name)
-## and get(object, name) == o):
-## container = object
- validate(name, o)
- else:
- o = get(object, name, M)
- if o is not M:
- # Check security.
- validate(name, o)
- else:
- try:
- o = object[name]
- except (AttributeError, TypeError):
- raise AttributeError, name
- validate(name, o)
- object = o
-
- return object