[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