[Zope-Checkins] CVS: Zope3/lib/python/Zope/PageTemplate - Engine.py:1.2 Expressions.py:1.2 PageTemplate.py:1.2 PageTemplateFile.py:1.2 PythonExpr.py:1.2 SafeMapping.py:1.2 TALES.py:1.2 __init__.py:1.2

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:30:00 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/PageTemplate
In directory cvs.zope.org:/tmp/cvs-serv20468/lib/python/Zope/PageTemplate

Added Files:
	Engine.py Expressions.py PageTemplate.py PageTemplateFile.py 
	PythonExpr.py SafeMapping.py TALES.py __init__.py 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.

=== Zope3/lib/python/Zope/PageTemplate/Engine.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+# 
+##############################################################################
+"""Expression engine configuration and registration.
+
+Each expression engine can have its own expression types and base names.
+
+$Id$
+"""
+
+from TALES import ExpressionEngine, RegistrationError
+from Expressions import PathExpr, StringExpr, NotExpr, DeferExpr
+from Expressions import SimpleModuleImporter
+from PythonExpr import PythonExpr
+
+def Engine():
+    e = ExpressionEngine()
+    reg = e.registerType
+    for pt in PathExpr._default_type_names:
+        reg(pt, PathExpr)
+    reg('string', StringExpr)
+    reg('python', PythonExpr)
+    reg('not', NotExpr)
+    reg('defer', DeferExpr)
+    e.registerBaseName('modules', SimpleModuleImporter())
+    return e
+
+Engine = Engine()
+


=== Zope3/lib/python/Zope/PageTemplate/Expressions.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+# 
+##############################################################################
+"""Basic Page Template expression types.
+
+$Id$
+"""
+__metaclass__ = type # All classes are new style when run with Python 2.2+
+
+import re, sys
+
+from TALES import ExpressionEngine, CompilerError, RegistrationError
+from TALES import _valid_name, _parse_expr, NAME_RE, Undefined
+from PythonExpr import PythonExpr
+
+Undefs = (Undefined, AttributeError, KeyError,
+          TypeError, IndexError)
+
+_marker = object()
+
+def simpleTraverse(object, path_items, econtext):
+    """Traverses a sequence of names, first trying attributes then items.
+    """
+
+    for name in path_items:
+        next = getattr(object, name, _marker)
+        if next is not _marker:
+            object = next
+        elif hasattr(object, '__getitem__'):
+            object = object[name]
+        else:
+            raise NameError, name
+    return object
+
+
+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
+        self._dp = dp = []
+        for i in range(len(path)):
+            e = path[i]
+            if e[:1] == '?' and _valid_name(e[1:]):
+                dp.append((i, e[1:]))
+        dp.reverse()
+
+    def _eval(self, econtext,
+              list=list, isinstance=isinstance, StringType=type('')):
+        vars = econtext.vars
+        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:
+                    # 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, econtext)
+        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
+            add(SubPathExpr(path, traverser)._eval)
+
+    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
+
+        # Call the object if it is callable.
+        if hasattr(ob, '__call__'):
+            return ob()
+        return ob
+
+    def __call__(self, econtext):
+        if self._name == 'exists':
+            return self._exists(econtext)
+        return self._eval(econtext)
+
+    def __str__(self):
+        return '%s expression (%s)' % (self._name, `self._s`)
+
+    def __repr__(self):
+        return '<PathExpr %s:%s>' % (self._name, `self._s`)
+
+
+
+_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
+
+class StringExpr:
+    def __init__(self, name, expr, engine):
+        self._s = expr
+        if '%' in expr:
+            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('$')
+                m = _interp.search(exp)
+                while m is not None:
+                    parts.append(exp[:m.start()])
+                    parts.append('%s')
+                    vars.append(path_type(
+                        'path', m.group(1) or m.group(2), engine))
+                    exp = exp[m.end():]
+                    m = _interp.search(exp)
+                if '$' in exp:
+                    raise CompilerError, (
+                        '$ must be doubled or followed by a simple path')
+                parts.append(exp)
+            expr = ''.join(parts)
+        self._expr = expr
+        
+    def __call__(self, econtext):
+        vvals = []
+        for var in self._vars:
+            v = var(econtext)
+            vvals.append(v)
+        return self._expr % tuple(vvals)
+
+    def __str__(self):
+        return 'string expression (%s)' % `self._s`
+
+    def __repr__(self):
+        return '<StringExpr %s>' % `self._s`
+
+
+class NotExpr:
+    def __init__(self, name, expr, engine):
+        self._s = expr = expr.lstrip()
+        self._c = engine.compile(expr)
+
+    def __call__(self, econtext):
+        return int(not econtext.evaluateBoolean(self._c))
+
+    def __repr__(self):
+        return '<NotExpr %s>' % `self._s`
+
+
+class DeferWrapper:
+    def __init__(self, expr, econtext):
+        self._expr = expr
+        self._econtext = econtext
+
+    def __str__(self):
+        return str(self())
+
+    def __call__(self):
+        return self._expr(self._econtext)
+
+
+class DeferExpr:
+    def __init__(self, name, expr, compiler):
+        self._s = expr = expr.lstrip()
+        self._c = compiler.compile(expr)
+        
+    def __call__(self, econtext):
+        return DeferWrapper(self._c, econtext)
+
+    def __repr__(self):
+        return '<DeferExpr %s>' % `self._s`
+
+
+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
+


=== Zope3/lib/python/Zope/PageTemplate/PageTemplate.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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 module
+
+HTML- and XML-based template objects using TAL, TALES, and METAL.
+
+$Id$
+"""
+__metaclass__ = type # All classes are new style when run with Python 2.2+
+
+import sys
+from Zope.TAL.TALParser import TALParser
+from Zope.TAL.HTMLTALParser import HTMLTALParser
+from Zope.TAL.TALGenerator import TALGenerator
+from Zope.TAL.TALInterpreter import TALInterpreter
+from Engine import Engine
+from cStringIO import StringIO
+
+
+class MacroCollection:
+    def __get__(self, parent, type=None):
+        parent._cook_check()
+        return parent._v_macros
+
+
+_default_options = {}
+
+class PageTemplate:
+    """Page Templates using TAL, TALES, and METAL.
+
+    Subclassing
+    -----------
+
+    The following methods have certain internal responsibilities.
+
+    pt_getContext(**keywords)
+        Should ignore keyword arguments that it doesn't care about,
+        and construct the namespace passed to the TALES expression
+        engine.  This method is free to use the keyword arguments it
+        receives.
+
+    pt_render(namespace, source=0)
+        Responsible the TAL interpreter to perform the rendering.  The
+        namespace argument is a mapping which defines the top-level
+        namespaces passed to the TALES expression engine.
+
+    __call__(*args, **keywords)
+        Calls pt_getContext() to construct the top-level namespace
+        passed to the TALES expression engine, then calls pt_render()
+        to perform the rendering.
+    """
+
+    content_type = 'text/html'
+    expand = 1
+    _v_errors = ()
+    _v_warnings = ()
+    _v_program = None
+    _v_macros = None
+    _v_cooked = 0
+    _text = ''
+    _engine_name = 'default'
+    _error_start = '<!-- Page Template Diagnostics'
+
+    macros = MacroCollection()
+
+    def pt_edit(self, text, content_type):
+        if content_type:
+            self.content_type = str(content_type)
+        if hasattr(text, 'read'):
+            text = text.read()
+        self.write(text)
+
+    def pt_getContext(self, args=(), options=_default_options, **ignored):
+        rval = {'template': self,
+                'options': options,
+                'args': args,
+                'nothing': None,
+                }
+        rval.update(self.pt_getEngine().getBaseNames())
+        return rval
+
+    def __call__(self, *args, **kwargs):
+        return self.pt_render(self.pt_getContext(args, kwargs))
+
+    pt_getEngineContext = Engine.getContext
+
+    def pt_getEngine(self):
+        return Engine
+
+    def pt_render(self, namespace, source=0):
+        """Render this Page Template"""
+        self._cook_check()
+        __traceback_supplement__ = (PageTemplateTracebackSupplement,
+                                    self, namespace)
+        if self._v_errors:
+            raise PTRuntimeError(str(self._v_errors))
+        output = StringIO()
+
+        context = self.pt_getEngineContext(namespace)
+        TALInterpreter(self._v_program, self._v_macros,
+                       context, output, tal=not source, strictinsert=0)()
+        return output.getvalue()
+
+    def pt_errors(self, namespace):
+        self._cook_check()
+        err = self._v_errors
+        if err:
+            return err
+        try:
+            self.pt_render(namespace, source=1)
+        except:
+            return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
+
+    def pt_warnings(self):
+        self._cook_check()
+        return self._v_warnings
+
+    def write(self, text):
+        assert isinstance(text, str)
+        if text.startswith(self._error_start):
+            errend = text.find('-->')
+            if errend >= 0:
+                text = text[errend + 4:]
+        if self._text != text:
+            self._text = text
+        self._cook()
+
+    def read(self):
+        """Gets the source, sometimes with macros expanded."""
+        self._cook_check()
+        if not self._v_errors:
+            if not self.expand:
+                return self._text
+            try:
+                # XXX not clear how this ever gets called, but the
+                # first arg to pt_render() needs to change if it ever does.
+                return self.pt_render({}, source=1)
+            except:
+                return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
+                        (self._error_start, "%s: %s" % sys.exc_info()[:2],
+                         self._text) )
+
+        return ('%s\n %s\n-->\n%s' % (self._error_start,
+                                      '\n'.join(self._v_errors),
+                                      self._text))
+
+    def pt_source_file(self):
+        """To be overridden."""
+        return None
+
+    def _cook_check(self):
+        if not self._v_cooked:
+            self._cook()
+
+    def _cook(self):
+        """Compile the TAL and METAL statments.
+
+        Cooking must not fail due to compilation errors in templates.
+        """
+        engine = self.pt_getEngine()
+        source_file = self.pt_source_file()
+        if self.html():
+            gen = TALGenerator(engine, xml=0, source_file=source_file)
+            parser = HTMLTALParser(gen)
+        else:
+            gen = TALGenerator(engine, source_file=source_file)
+            parser = TALParser(gen)
+
+        self._v_errors = ()
+        try:
+            parser.parseString(self._text)
+            self._v_program, self._v_macros = parser.getCode()
+            self._v_macros = self._v_macros
+        except:
+            self._v_errors = ["Compilation failed",
+                              "%s: %s" % sys.exc_info()[:2]]
+        self._v_warnings = parser.getWarnings()
+        self._v_cooked = 1
+
+    def html(self):
+        if not hasattr(self, 'is_html'):
+            return self.content_type == 'text/html'
+        return self.is_html
+
+
+
+class PTRuntimeError(RuntimeError):
+    '''The Page Template has template errors that prevent it from rendering.'''
+    pass
+
+
+class PageTemplateTracebackSupplement:
+    #__implements__ = ITracebackSupplement
+
+    def __init__(self, pt, namespace):
+        self.manageable_object = pt
+        try:
+            w = pt.pt_warnings()
+        except: # We're already trying to report an error, don't make another.
+            w = ()
+        e = pt.pt_errors(namespace)
+        if e:
+            w = list(w) + list(e)
+        self.warnings = w


=== Zope3/lib/python/Zope/PageTemplate/PageTemplateFile.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+# 
+##############################################################################
+"""Filesystem Page Template module
+
+Zope object encapsulating a Page Template from the filesystem.
+"""
+
+__metatype__ = type
+
+__version__ = '$Revision$'[11:-2]
+
+import os, sys
+from zLOG import LOG, ERROR, INFO
+from PageTemplate import PageTemplate
+
+def package_home(gdict):
+    filename = gdict["__file__"]
+    return os.path.dirname(filename)
+
+class PageTemplateFile(PageTemplate):
+    "Zope wrapper for filesystem Page Template using TAL, TALES, and METAL"
+
+    _v_last_read = 0
+
+    def __init__(self, filename, _prefix=None):
+        if not isinstance(_prefix, str):
+            if _prefix is None:
+                _prefix = sys._getframe(1).f_globals
+            _prefix = package_home(_prefix)
+            
+        self.filename = os.path.join(_prefix, filename)
+
+    def _cook_check(self):
+        if self._v_last_read and not __debug__:
+            return
+        __traceback_info__ = self.filename
+        try:
+            mtime = os.path.getmtime(self.filename)
+        except OSError:
+            mtime = 0
+        if getattr(self, '_v_program', 0) and mtime == self._v_last_read:
+            return
+        self.pt_edit(open(self.filename), None)
+        self._cook()
+        if self._v_errors:
+            LOG('PageTemplateFile', ERROR, 'Error in template',
+                '\n'.join(self._v_errors))
+            return
+        self._v_last_read = mtime
+
+    def document_src(self, REQUEST=None):
+        """Return expanded document source."""
+
+        if REQUEST is not None:
+            REQUEST.getResponse().setHeader('Content-Type', self.content_type)
+        return self.read()
+
+    def pt_source_file(self):
+        return self.filename
+
+    def __getstate__(self):
+        raise TypeError("non-picklable object")


=== Zope3/lib/python/Zope/PageTemplate/PythonExpr.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+# 
+##############################################################################
+"""Generic Python Expression Handler"""
+
+__version__ = '$Revision$'[11:-2]
+
+class PythonExpr:
+    def __init__(self, name, expr, engine):
+        text = expr.replace('\n', ' ').strip()
+        self.text = text
+        # The next line can legally raise SyntaxError.
+        self._code = code = compile(text, '<string>', 'eval')
+        self._varnames = code.co_names
+
+    def _bind_used_names(self, econtext, builtins):
+        # Bind template variables
+        names = {}
+        vars = econtext.vars
+        marker = self
+        for vname in self._varnames:
+            val = vars.get(vname, marker)
+            if val is not marker:
+                names[vname] = val
+            elif vname not in builtins:
+                # Fall back to using expression types as variable values.
+                val = econtext._engine.getTypes().get(vname, marker)
+                if val is not marker:
+                    val = ExprTypeProxy(vname, val, econtext)
+                    names[vname] = val
+
+        names['__builtins__'] = builtins
+        return names
+
+    def __call__(self, econtext):
+        __traceback_info__ = self.text
+        vars = self._bind_used_names(econtext, __builtins__)
+        return eval(self._code, vars)
+
+    def __str__(self):
+        return 'Python expression "%s"' % self.text
+
+    def __repr__(self):
+        return '<PythonExpr %s>' % self.text
+
+
+class ExprTypeProxy:
+    '''Class that proxies access to an expression type handler'''
+    def __init__(self, name, handler, econtext):
+        self._name = name
+        self._handler = handler
+        self._econtext = econtext
+
+    def __call__(self, text):
+        return self._handler(self._name, text,
+                             self._econtext._engine)(self._econtext)


=== Zope3/lib/python/Zope/PageTemplate/SafeMapping.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+# 
+##############################################################################
+"""Simple variation of MultiMapping used to support layers of variable
+declarations in TAL."""
+
+
+class SafeMapping:
+    def __init__(self, *dicts):
+        self._mappings = list(dicts)
+        self._mappings.reverse()
+
+    def __getitem__(self, key):
+        for d in self._mappings:
+            if key in d:
+                return d[key]
+        raise KeyError, key
+
+    def __contains__(self, key):
+        for d in self._mappings:
+            if key in d:
+                return 1
+        return 0
+
+    has_key = __contains__
+
+    def get(self, key, default=None):
+        for d in self._mappings:
+            if key in d:
+                return d[key]
+        return default
+
+    def _push(self, dict):
+        self._mappings.insert(0, dict)
+
+    def _pop(self, count=1):
+        del self._mappings[:count]


=== Zope3/lib/python/Zope/PageTemplate/TALES.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+# 
+##############################################################################
+"""TALES
+
+An implementation of a generic TALES engine
+"""
+__metaclass__ = type # All classes are new style when run with Python 2.2+
+
+__version__ = '$Revision$'[11:-2]
+
+import re
+import sys
+from types import StringType
+
+import Zope.ZTUtils
+from SafeMapping import SafeMapping
+
+
+NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
+_parse_expr = re.compile(r"(%s):" % NAME_RE).match
+_valid_name = re.compile('%s$' % NAME_RE).match
+
+
+class TALESError(Exception):
+    """Error during TALES evaluation"""
+
+class Undefined(TALESError):
+    '''Exception raised on traversal of an undefined path'''
+
+class CompilerError(Exception):
+    '''TALES Compiler Error'''
+
+class RegistrationError(Exception):
+    '''Expression type or base name registration Error'''
+
+
+_default = object()
+
+_marker = object()
+
+
+class Iterator(Zope.ZTUtils.Iterator):
+    def __init__(self, name, seq, context):
+        Zope.ZTUtils.Iterator.__init__(self, seq)
+        self.name = name
+        self._context = context
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        if Zope.ZTUtils.Iterator.next(self):
+            self._context.setLocal(self.name, self.seq[self.index])
+            return 1
+        return 0
+
+
+
+class ErrorInfo:
+    """Information about an exception passed to an on-error handler."""
+    def __init__(self, err, position=(None, None)):
+        if isinstance(err, Exception):
+            self.type = err.__class__
+            self.value = err
+        else:
+            self.type = err
+            self.value = None
+        self.lineno = position[0]
+        self.offset = position[1]
+
+
+
+class ExpressionEngine:
+    '''Expression Engine
+
+    An instance of this class keeps a mutable collection of expression
+    type handlers.  It can compile expression strings by delegating to
+    these handlers.  It can provide an expression Context, which is
+    capable of holding state and evaluating compiled expressions.
+    '''
+    def __init__(self):
+        self.types = {}
+        self.base_names = {}
+        self.iteratorFactory = Iterator
+
+    def registerType(self, name, handler):
+        if not _valid_name(name):
+            raise RegistrationError, (
+                'Invalid expression type name "%s".' % name)
+        types = self.types
+        if name in types:
+            raise RegistrationError, (
+                'Multiple registrations for Expression type "%s".' %
+                name)
+        types[name] = handler
+
+    def getTypes(self):
+        return self.types
+
+    def registerBaseName(self, name, object):
+        if not _valid_name(name):
+            raise RegistrationError, 'Invalid base name "%s".' % name
+        base_names = self.base_names
+        if name in base_names:
+            raise RegistrationError, (
+                'Multiple registrations for base name "%s".' % name)
+        base_names[name] = object
+
+    def getBaseNames(self):
+        return self.base_names
+
+    def compile(self, expression):
+        m = _parse_expr(expression)
+        if m:
+            type = m.group(1)
+            expr = expression[m.end():]
+        else:
+            type = "standard"
+            expr = expression
+        try:
+            handler = self.types[type]
+        except KeyError:
+            raise CompilerError, (
+                'Unrecognized expression type "%s".' % type)
+        return handler(type, expr, self)
+    
+    def getContext(self, contexts=None, **kwcontexts):
+        if contexts is not None:
+            if kwcontexts:
+                kwcontexts.update(contexts)
+            else:
+                kwcontexts = contexts
+        return Context(self, kwcontexts)
+
+    def getCompilerError(self):
+        return CompilerError
+
+
+class Context:
+    '''Expression Context
+
+    An instance of this class holds context information that it can
+    use to evaluate compiled expressions.
+    '''
+
+    _context_class = SafeMapping
+    position = (None, None)
+    source_file = None
+
+    def __init__(self, engine, contexts):
+        self._engine = engine
+        self.contexts = contexts
+        contexts['nothing'] = None
+        contexts['default'] = _default
+
+        self.repeat_vars = rv = {}
+        # Wrap this, as it is visible to restricted code
+        contexts['repeat'] = rep =  self._context_class(rv)
+        contexts['loop'] = rep # alias
+
+        self.global_vars = gv = contexts.copy()
+        self.local_vars = lv = {}
+        self.vars = self._context_class(gv, lv)
+
+        # Keep track of what needs to be popped as each scope ends.
+        self._scope_stack = []
+
+    def beginScope(self):
+        self._scope_stack.append([self.local_vars.copy()])
+
+    def endScope(self):
+        scope = self._scope_stack.pop()
+        self.local_vars = lv = scope[0]
+        v = self.vars
+        v._pop()
+        v._push(lv)
+        # Pop repeat variables, if any
+        i = len(scope) - 1
+        while i:
+            name, value = scope[i]
+            if value is None:
+                del self.repeat_vars[name]
+            else:
+                self.repeat_vars[name] = value
+            i = i - 1
+
+    def setLocal(self, name, value):
+        self.local_vars[name] = value
+
+    def setGlobal(self, name, value):
+        self.global_vars[name] = value
+
+    def setRepeat(self, name, expr):
+        expr = self.evaluate(expr)
+        if not expr:
+            return self._engine.iteratorFactory(name, (), self)
+        it = self._engine.iteratorFactory(name, expr, self)
+        old_value = self.repeat_vars.get(name)
+        self._scope_stack[-1].append((name, old_value))
+        self.repeat_vars[name] = it
+        return it
+
+    def evaluate(self, expression,
+                 isinstance=isinstance, StringType=StringType):
+        if isinstance(expression, StringType):
+            expression = self._engine.compile(expression)
+        __traceback_supplement__ = (
+            TALESTracebackSupplement, self, expression)
+        v = expression(self)
+        return v
+
+    evaluateValue = evaluate
+
+    def evaluateBoolean(self, expr):
+        return not not self.evaluate(expr)
+
+    def evaluateText(self, expr):
+        text = self.evaluate(expr)
+        if text is _default or text is None:
+            return text
+        return str(text)
+
+    def evaluateStructure(self, expr):
+        return self.evaluate(expr)
+    evaluateStructure = evaluate
+
+    def evaluateMacro(self, expr):
+        # XXX Should return None or a macro definition
+        return self.evaluate(expr)
+    evaluateMacro = evaluate
+
+    def createErrorInfo(self, err, position):
+        return ErrorInfo(err, position)
+
+    def getDefault(self):
+        return _default
+
+    def setSourceFile(self, source_file):
+        self.source_file = source_file
+
+    def setPosition(self, position):
+        self.position = position
+
+
+class TALESTracebackSupplement:
+    """Implementation of Zope.Exceptions.ITracebackSupplement"""
+    def __init__(self, context, expression):
+        self.context = context
+        self.source_url = context.source_file
+        self.line = context.position[0]
+        self.column = context.position[1]
+        self.expression = repr(expression)
+        
+    def getInfo(self, as_html=0):
+        import pprint
+        data = self.context.contexts.copy()
+        s = pprint.pformat(data)
+        if not as_html:
+            return '   - Names:\n      %s' % s.replace('\n', '\n      ')
+        else:
+            from cgi import escape
+            return '<b>Names:</b><pre>%s</pre>' % (escape(s))
+        return None
+
+
+
+class SimpleExpr:
+    '''Simple example of an expression type handler'''
+    def __init__(self, name, expr, engine):
+        self._name = name
+        self._expr = expr
+    def __call__(self, econtext):
+        return self._name, self._expr
+    def __repr__(self):
+        return '<SimpleExpr %s %s>' % (self._name, `self._expr`)
+


=== Zope3/lib/python/Zope/PageTemplate/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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 Templates for Zope 3."""