Code

Adding PageTemplates to the dist
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Fri, 30 Aug 2002 08:27:34 +0000 (08:27 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Fri, 30 Aug 2002 08:27:34 +0000 (08:27 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1001 57a73879-2fb5-44c3-a270-3262357dd7e2

PageTemplates/Expressions.py [new file with mode: 0644]
PageTemplates/PageTemplate.py [new file with mode: 0755]
PageTemplates/PathIterator.py [new file with mode: 0644]
PageTemplates/PythonExpr.py [new file with mode: 0644]
PageTemplates/README.txt [new file with mode: 0644]
PageTemplates/TALES.py [new file with mode: 0644]
PageTemplates/__init__.py [new file with mode: 0644]

diff --git a/PageTemplates/Expressions.py b/PageTemplates/Expressions.py
new file mode 100644 (file)
index 0000000..b2adad2
--- /dev/null
@@ -0,0 +1,375 @@
+##############################################################################
+#
+# Copyright (c) 2001 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 Expression Engine
+
+Page Template-specific implementation of TALES, with handlers
+for Python expressions, string literals, and paths.
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import re, sys
+from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
+     Undefined, Default, _parse_expr
+from string import strip, split, join, replace, lstrip
+from Acquisition import aq_base, aq_inner, aq_parent
+
+
+_engine = None
+def getEngine():
+    global _engine
+    if _engine is None:
+        from PathIterator import Iterator
+        _engine = Engine(Iterator)
+        installHandlers(_engine)
+    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'):
+    import AccessControl
+    from AccessControl import getSecurityManager
+    try:
+        from AccessControl import Unauthorized
+    except ImportError:
+        Unauthorized = "Unauthorized"
+    if hasattr(AccessControl, 'full_read_guard'):
+        from ZRPythonExpr import PythonExpr, _SecureModuleImporter, \
+             call_with_ns
+    else:
+        from ZPythonExpr import PythonExpr, _SecureModuleImporter, \
+             call_with_ns
+else:
+    from PythonExpr import getSecurityManager, PythonExpr
+    try:
+        from zExceptions import Unauthorized
+    except ImportError:
+        Unauthorized = "Unauthorized"
+    def call_with_ns(f, ns, arg=1):
+        if arg==2:
+            return f(None, ns)
+        else:
+            return f(ns)
+
+    class _SecureModuleImporter:
+        """Simple version of the importer for use with trusted code."""
+        __allow_access_to_unprotected_subobjects__ = 1
+        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)
+    """
+    if hasattr(ob, '__render_with_namespace__'):
+        ob = call_with_ns(ob.__render_with_namespace__, ns)
+    else:
+        base = aq_base(ob)
+        if callable(base):
+            try:
+                if getattr(base, 'isDocTemp', 0):
+                    ob = call_with_ns(ob, ns, 2)
+                else:
+                    ob = ob()
+            except AttributeError, n:
+                if str(n) != '__call__':
+                    raise
+    return ob
+
+class SubPathExpr:
+    def __init__(self, path):
+        self._path = path = split(strip(path), '/')
+        self._base = base = path.pop(0)
+        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)
+        __traceback_info__ = base = self._base
+        if base == 'CONTEXTS':
+            ob = econtext.contexts
+        else:
+            ob = vars[base]
+        if isinstance(ob, DeferWrapper):
+            ob = ob()
+        if path:
+            ob = restrictedTraverse(ob, path, getSecurityManager())
+        return ob
+
+class PathExpr:
+    def __init__(self, name, expr, engine):
+        self._s = expr
+        self._name = name
+        paths = split(expr, '|')
+        self._subexprs = []
+        add = self._subexprs.append
+        for i in range(len(paths)):
+            path = lstrip(paths[i])
+            if _parse_expr(path):
+                # This part is the start of another expression type,
+                # so glue it back together and compile it.
+                add(engine.compile(lstrip(join(paths[i:], '|'))))
+                break
+            add(SubPathExpr(path)._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,
+              isinstance=isinstance, StringType=type(''), render=render):
+        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' or isinstance(ob, StringType):
+            return ob
+        # Return the rendered object
+        return render(ob, econtext.vars)
+
+    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 '%s:%s' % (self._name, `self._s`)
+
+            
+_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/%(n)s)*)}' % {'n': NAME_RE})
+
+class StringExpr:
+    def __init__(self, name, expr, engine):
+        self._s = expr
+        if '%' in expr:
+            expr = replace(expr, '%', '%%')
+        self._vars = vars = []
+        if '$' in expr:
+            parts = []
+            for exp in split(expr, '$$'):
+                if parts: parts.append('$')
+                m = _interp.search(exp)
+                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))
+                    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)
+            if isinstance(v, Exception):
+                raise v
+            vvals.append(v)
+        return self._expr % tuple(vvals)
+
+    def __str__(self):
+        return 'string expression %s' % `self._s`
+
+    def __repr__(self):
+        return 'string:%s' % `self._s`
+
+class NotExpr:
+    def __init__(self, name, expr, compiler):
+        self._s = expr = lstrip(expr)
+        self._c = compiler.compile(expr)
+        
+    def __call__(self, econtext):
+        return not econtext.evaluateBoolean(self._c)
+
+    def __repr__(self):
+        return 'not:%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 = lstrip(expr)
+        self._c = compiler.compile(expr)
+        
+    def __call__(self, econtext):
+        return DeferWrapper(self._c, econtext)
+
+    def __repr__(self):
+        return 'defer:%s' % `self._s`
+
+
+def restrictedTraverse(self, path, securityManager,
+                       get=getattr, has=hasattr, N=None, M=[],
+                       TupleType=type(()) ):
+
+    REQUEST = {'path': path}
+    REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
+    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
+        path.pop(0)
+        
+    path.reverse()
+    validate = securityManager.validate
+    object = self
+    #print 'TRAVERSE', (object, path)
+    while path:
+        __traceback_info__ = REQUEST
+        name = path.pop()
+
+        if isinstance(name, TupleType):
+            object = apply(object, name)
+            continue
+
+        if name[0] == '_':
+            # Never allowed in a URL.
+            raise AttributeError, name
+
+        if name=='..':
+            o = get(object, 'aq_parent', M)
+            if o is not M:
+                if not validate(object, object, name, o):
+                    raise Unauthorized, name
+                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
+            if not validate(object, container, name, o):
+                raise Unauthorized, name
+        else:
+            # Try an attribute.
+            o = get(object, name, M)
+        #    print '...', (object, name, M, o)
+            if o is not M:
+                # Check access to the attribute.
+                if has(object, 'aq_acquire'):
+                    object.aq_acquire(
+                        name, validate2, validate)
+                else:
+                    if not validate(object, object, name, o):
+                        raise Unauthorized, name
+            else:
+                # Try an item.
+                try:
+                    # XXX maybe in Python 2.2 we can just check whether
+                    # the object has the attribute "__getitem__"
+                    # instead of blindly catching exceptions.
+        #            print 'Try an item', (object, name)
+                    o = object[name]
+                except AttributeError, exc:
+                    if str(exc).find('__getitem__') >= 0:
+                        # The object does not support the item interface.
+                        # Try to re-raise the original attribute error.
+                        # XXX I think this only happens with
+                        # ExtensionClass instances.
+                        get(object, name)
+                    raise
+                except TypeError, exc:
+                    if str(exc).find('unsubscriptable') >= 0:
+                        # The object does not support the item interface.
+                        # Try to re-raise the original attribute error.
+                        # XXX This is sooooo ugly.
+                        get(object, name)
+                    raise
+                else:
+                    # Check access to the item.
+                    if not validate(object, object, name, o):
+                        raise Unauthorized, name
+        #print '... object is now', `o`
+        object = o
+
+    return object
+
+
+def validate2(orig, inst, name, v, real_validate):
+    if not real_validate(orig, inst, name, v):
+        raise Unauthorized, name
+    return 1
+
diff --git a/PageTemplates/PageTemplate.py b/PageTemplates/PageTemplate.py
new file mode 100755 (executable)
index 0000000..d97b2a5
--- /dev/null
@@ -0,0 +1,207 @@
+##############################################################################
+#
+# Copyright (c) 2001 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.
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import sys
+
+from TAL.TALParser import TALParser
+from TAL.HTMLTALParser import HTMLTALParser
+from TAL.TALGenerator import TALGenerator
+from TAL.TALInterpreter import TALInterpreter
+from Expressions import getEngine
+from string import join, strip, rstrip, split, replace, lower, find
+from cStringIO import StringIO
+from ExtensionClass import Base
+from ComputedAttribute import ComputedAttribute
+
+
+class PageTemplate(Base):
+    "Page Templates using TAL, TALES, and METAL"
+     
+    content_type = 'text/html'
+    expand = 0
+    _v_errors = ()
+    _v_warnings = ()
+    _v_program = None
+    _v_macros = None
+    _v_cooked = 0
+    id = '(unknown)'
+    _text = ''
+    _error_start = '<!-- Page Template Diagnostics'
+
+    def macros(self):
+        return self.pt_macros()
+    macros = ComputedAttribute(macros, 1)
+
+    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):
+        c = {'template': self,
+             'options': {},
+             'nothing': None,
+             'request': None,
+             'modules': ModuleImporter,
+             }
+        parent = getattr(self, 'aq_parent', None)
+        if parent is not None:
+            c['here'] = parent
+            c['container'] = self.aq_inner.aq_parent
+            while parent is not None:
+                self = parent
+                parent = getattr(self, 'aq_parent', None)
+            c['root'] = self
+        return c
+    
+    def pt_render(self, source=0, extra_context={}):
+        """Render this Page Template"""
+        if not self._v_cooked:
+            self._cook()
+
+        __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
+
+        if self._v_errors:
+            raise PTRuntimeError, 'Page Template %s has errors.' % self.id
+        output = StringIO()
+        c = self.pt_getContext()
+        c.update(extra_context)
+
+        TALInterpreter(self._v_program, self._v_macros,
+                       getEngine().getContext(c),
+                       output,
+                       tal=not source, strictinsert=0)()
+        return output.getvalue()
+
+    def __call__(self, *args, **kwargs):
+        if not kwargs.has_key('args'):
+            kwargs['args'] = args
+        return self.pt_render(extra_context={'options': kwargs})
+
+    def pt_errors(self):
+        if not self._v_cooked:
+            self._cook()
+        err = self._v_errors
+        if err:
+            return err
+        if not self.expand: return
+        try:
+            self.pt_render(source=1)
+        except:
+            return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
+        
+    def pt_warnings(self):
+        if not self._v_cooked:
+            self._cook()
+        return self._v_warnings
+
+    def pt_macros(self):
+        if not self._v_cooked:
+            self._cook()
+        if self._v_errors:
+            __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
+            raise PTRuntimeError, 'Page Template %s has errors.' % self.id
+        return self._v_macros
+
+    def pt_source_file(self):
+        return None  # Unknown.
+
+    def write(self, text):
+        assert type(text) is type('')
+        if text[:len(self._error_start)] == self._error_start:
+            errend = find(text, '-->')
+            if errend >= 0:
+                text = text[errend + 4:]
+        if self._text != text:
+            self._text = text
+        self._cook()
+
+    def read(self):
+        if not self._v_cooked:
+            self._cook()
+        if not self._v_errors:
+            if not self.expand:
+                return self._text
+            try:
+                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,
+                                      join(self._v_errors, '\n '),
+                                      self._text))
+
+    def _cook(self):
+        """Compile the TAL and METAL statments.
+
+        Cooking must not fail due to compilation errors in templates.
+        """
+        source_file = self.pt_source_file()
+        if self.html():
+            gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
+            parser = HTMLTALParser(gen)
+        else:
+            gen = TALGenerator(getEngine(), source_file=source_file)
+            parser = TALParser(gen)
+
+        self._v_errors = ()
+        try:
+            parser.parseString(self._text)
+            self._v_program, self._v_macros = parser.getCode()
+        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(getattr(self, 'aq_base', self), 'is_html'):
+            return self.content_type == 'text/html'
+        return self.is_html
+
+class _ModuleImporter:
+    def __getitem__(self, module):
+        mod = __import__(module)
+        path = split(module, '.')
+        for name in path[1:]:
+            mod = getattr(mod, name)
+        return mod
+
+ModuleImporter = _ModuleImporter()
+
+class PTRuntimeError(RuntimeError):
+    '''The Page Template has template errors that prevent it from rendering.'''
+    pass
+
+
+class PageTemplateTracebackSupplement:
+    #__implements__ = ITracebackSupplement
+
+    def __init__(self, pt):
+        self.object = pt
+        w = pt.pt_warnings()
+        e = pt.pt_errors()
+        if e:
+            w = list(w) + list(e)
+        self.warnings = w
+
diff --git a/PageTemplates/PathIterator.py b/PageTemplates/PathIterator.py
new file mode 100644 (file)
index 0000000..ded1d29
--- /dev/null
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+# 
+##############################################################################
+
+"""Path Iterator
+
+A TALES Iterator with the ability to use first() and last() on
+subpaths of elements.
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import TALES
+from Expressions import restrictedTraverse, Undefs, getSecurityManager
+from string import split
+
+class Iterator(TALES.Iterator):
+    def __bobo_traverse__(self, REQUEST, name):
+        if name in ('first', 'last'):
+            path = REQUEST['TraversalRequestNameStack']
+            names = list(path)
+            names.reverse()
+            path[:] = [tuple(names)]
+        return getattr(self, name)
+
+    def same_part(self, name, ob1, ob2):
+        if name is None:
+            return ob1 == ob2
+        if isinstance(name, type('')):
+            name = split(name, '/')
+        name = filter(None, name)
+        securityManager = getSecurityManager()
+        try:
+            ob1 = restrictedTraverse(ob1, name, securityManager)
+            ob2 = restrictedTraverse(ob2, name, securityManager)
+        except Undefs:
+            return 0
+        return ob1 == ob2
+
diff --git a/PageTemplates/PythonExpr.py b/PageTemplates/PythonExpr.py
new file mode 100644 (file)
index 0000000..afa40d2
--- /dev/null
@@ -0,0 +1,82 @@
+##############################################################################
+#
+# Copyright (c) 2001 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: 1.1 $'[11:-2]
+
+from TALES import CompilerError
+from string import strip, split, join, replace, lstrip
+from sys import exc_info
+
+class getSecurityManager:
+    '''Null security manager'''
+    def validate(self, *args, **kwargs):
+        return 1
+    addContext = removeContext = validateValue = validate
+
+class PythonExpr:
+    def __init__(self, name, expr, engine):
+        self.expr = expr = replace(strip(expr), '\n', ' ')
+        try:
+            d = {}
+            exec 'def f():\n return %s\n' % strip(expr) in d
+            self._f = d['f']
+        except:
+            raise CompilerError, ('Python expression error:\n'
+                                  '%s: %s') % exc_info()[:2]
+        self._get_used_names()
+
+    def _get_used_names(self):
+        self._f_varnames = vnames = []
+        for vname in self._f.func_code.co_names:
+            if vname[0] not in '$_':
+                vnames.append(vname)
+
+    def _bind_used_names(self, econtext):
+        # Bind template variables
+        names = {}
+        vars = econtext.vars
+        getType = econtext._engine.getTypes().get
+        for vname in self._f_varnames:
+            has, val = vars.has_get(vname)
+            if not has:
+                has = val = getType(vname)
+                if has:
+                    val = ExprTypeProxy(vname, val, econtext)
+            if has:
+                names[vname] = val
+        return names
+
+    def __call__(self, econtext):
+        __traceback_info__ = self.expr
+        f = self._f
+        f.func_globals.update(self._bind_used_names(econtext))        
+        return f()
+
+    def __str__(self):
+        return 'Python expression "%s"' % self.expr
+    def __repr__(self):
+        return '<PythonExpr %s>' % self.expr
+
+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)
+
diff --git a/PageTemplates/README.txt b/PageTemplates/README.txt
new file mode 100644 (file)
index 0000000..013f0ae
--- /dev/null
@@ -0,0 +1,7 @@
+See <a href="http://dev.zope.org/Wikis/DevSite/Projects/ZPT">the
+ZPT project Wiki</a> for more information about Page Templates, or
+<a href="http://www.zope.org/Members/4am/ZPT">the download page</a>
+for installation instructions and the most recent version of the software.
+
+This Product requires the TAL and ZTUtils packages to be installed in
+your Python path (not Products).  See the links above for more information.
diff --git a/PageTemplates/TALES.py b/PageTemplates/TALES.py
new file mode 100644 (file)
index 0000000..089ccbc
--- /dev/null
@@ -0,0 +1,285 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import re, sys, ZTUtils
+from MultiMapping import MultiMapping
+
+StringType = type('')
+
+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 expression evaluation"""
+
+class Undefined(TALESError):
+    '''Exception raised on traversal of an undefined path'''
+
+class RegistrationError(Exception):
+    '''TALES Type Registration Error'''
+
+class CompilerError(Exception):
+    '''TALES Compiler Error'''
+
+class Default:
+    '''Retain Default'''
+Default = Default()
+
+_marker = []
+
+class SafeMapping(MultiMapping):
+    '''Mapping with security declarations and limited method exposure.
+
+    Since it subclasses MultiMapping, this class can be used to wrap
+    one or more mapping objects.  Restricted Python code will not be
+    able to mutate the SafeMapping or the wrapped mappings, but will be
+    able to read any value.
+    '''
+    __allow_access_to_unprotected_subobjects__ = 1
+    push = pop = None
+
+    _push = MultiMapping.push
+    _pop = MultiMapping.pop
+
+    def has_get(self, key, _marker=[]):
+        v = self.get(key, _marker)
+        return v is not _marker, v
+
+class Iterator(ZTUtils.Iterator):
+    def __init__(self, name, seq, context):
+        ZTUtils.Iterator.__init__(self, seq)
+        self.name = name
+        self._context = context
+
+    def next(self):
+        if ZTUtils.Iterator.next(self):
+            self._context.setLocal(self.name, self.item)
+            return 1
+        return 0
+
+
+class ErrorInfo:
+    """Information about an exception passed to an on-error handler."""
+    __allow_access_to_unprotected_subobjects__ = 1
+
+    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 Engine:
+    '''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.
+    '''
+    Iterator = Iterator
+
+    def __init__(self, Iterator=None):
+        self.types = {}
+        if Iterator is not None:
+            self.Iterator = Iterator
+
+    def registerType(self, name, handler):
+        if not _valid_name(name):
+            raise RegistrationError, 'Invalid Expression type "%s".' % name
+        types = self.types
+        if types.has_key(name):
+            raise RegistrationError, (
+                'Multiple registrations for Expression type "%s".' %
+                name)
+        types[name] = handler
+
+    def getTypes(self):
+        return self.types
+
+    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.Iterator(name, (), self)
+        it = self._engine.Iterator(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, None=None):
+        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 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' % string.replace(s, '\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`)
+
diff --git a/PageTemplates/__init__.py b/PageTemplates/__init__.py
new file mode 100644 (file)
index 0000000..e1997a6
--- /dev/null
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+# 
+##############################################################################
+__doc__='''Package wrapper for Page Templates
+
+This wrapper allows the Page Template modules to be segregated in a
+separate package.
+
+$Id: __init__.py,v 1.1 2002-08-30 08:27:34 richard Exp $'''
+__version__='$$'[11:-2]
+
+
+# Placeholder for Zope Product data
+misc_ = {}
+
+def initialize(context):
+    # Import lazily, and defer initialization to the module
+    import ZopePageTemplate
+    ZopePageTemplate.initialize(context)