Code

moved
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 5 Sep 2002 00:37:09 +0000 (00:37 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 5 Sep 2002 00:37:09 +0000 (00:37 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1068 57a73879-2fb5-44c3-a270-3262357dd7e2

58 files changed:
PageTemplates/.cvsignore [deleted file]
PageTemplates/Expressions.py [deleted file]
PageTemplates/PageTemplate.py [deleted file]
PageTemplates/PathIterator.py [deleted file]
PageTemplates/PythonExpr.py [deleted file]
PageTemplates/README.txt [deleted file]
PageTemplates/TALES.py [deleted file]
PageTemplates/__init__.py [deleted file]
TAL/.cvsignore [deleted file]
TAL/HTMLParser.py [deleted file]
TAL/HTMLTALParser.py [deleted file]
TAL/README.txt [deleted file]
TAL/TALDefs.py [deleted file]
TAL/TALGenerator.py [deleted file]
TAL/TALInterpreter.py [deleted file]
TAL/TALParser.py [deleted file]
TAL/XMLParser.py [deleted file]
TAL/__init__.py [deleted file]
TAL/markupbase.py [deleted file]
TODO.txt
ZTUtils/.cvsignore [deleted file]
ZTUtils/Batch.py [deleted file]
ZTUtils/CHANGES.txt [deleted file]
ZTUtils/HISTORY.txt [deleted file]
ZTUtils/Iterator.py [deleted file]
ZTUtils/SimpleTree.py [deleted file]
ZTUtils/Tree.py [deleted file]
ZTUtils/Zope.py [deleted file]
ZTUtils/__init__.py [deleted file]
doc/installation.txt
roundup/backends/back_anydbm.py
roundup/backends/back_gadfly.py
roundup/cgi/PageTemplates/.cvsignore [new file with mode: 0644]
roundup/cgi/PageTemplates/ComputedAttribute.py [new file with mode: 0644]
roundup/cgi/PageTemplates/Expressions.py [new file with mode: 0644]
roundup/cgi/PageTemplates/MultiMapping.py [new file with mode: 0644]
roundup/cgi/PageTemplates/PageTemplate.py [new file with mode: 0755]
roundup/cgi/PageTemplates/PathIterator.py [new file with mode: 0644]
roundup/cgi/PageTemplates/PythonExpr.py [new file with mode: 0644]
roundup/cgi/PageTemplates/README.txt [new file with mode: 0644]
roundup/cgi/PageTemplates/TALES.py [new file with mode: 0644]
roundup/cgi/PageTemplates/__init__.py [new file with mode: 0644]
roundup/cgi/TAL/.cvsignore [new file with mode: 0644]
roundup/cgi/TAL/HTMLParser.py [new file with mode: 0644]
roundup/cgi/TAL/HTMLTALParser.py [new file with mode: 0644]
roundup/cgi/TAL/README.txt [new file with mode: 0644]
roundup/cgi/TAL/TALDefs.py [new file with mode: 0644]
roundup/cgi/TAL/TALGenerator.py [new file with mode: 0644]
roundup/cgi/TAL/TALInterpreter.py [new file with mode: 0644]
roundup/cgi/TAL/TALParser.py [new file with mode: 0644]
roundup/cgi/TAL/XMLParser.py [new file with mode: 0644]
roundup/cgi/TAL/__init__.py [new file with mode: 0644]
roundup/cgi/TAL/markupbase.py [new file with mode: 0644]
roundup/cgi/ZTUtils/.cvsignore [new file with mode: 0644]
roundup/cgi/ZTUtils/Batch.py [new file with mode: 0644]
roundup/cgi/ZTUtils/Iterator.py [new file with mode: 0644]
roundup/cgi/ZTUtils/__init__.py [new file with mode: 0644]
roundup/cgi/templating.py

diff --git a/PageTemplates/.cvsignore b/PageTemplates/.cvsignore
deleted file mode 100644 (file)
index 0d20b64..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.pyc
diff --git a/PageTemplates/Expressions.py b/PageTemplates/Expressions.py
deleted file mode 100644 (file)
index b2adad2..0000000
+++ /dev/null
@@ -1,375 +0,0 @@
-##############################################################################
-#
-# 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
deleted file mode 100755 (executable)
index d97b2a5..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-##############################################################################
-#
-# 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
deleted file mode 100644 (file)
index ded1d29..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-##############################################################################
-#
-# 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
deleted file mode 100644 (file)
index afa40d2..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-##############################################################################
-#
-# 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
deleted file mode 100644 (file)
index 013f0ae..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-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
deleted file mode 100644 (file)
index 089ccbc..0000000
+++ /dev/null
@@ -1,285 +0,0 @@
-##############################################################################
-#
-# 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
deleted file mode 100644 (file)
index e1997a6..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-##############################################################################
-#
-# 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)
diff --git a/TAL/.cvsignore b/TAL/.cvsignore
deleted file mode 100644 (file)
index 0cd1d94..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-.path
-*.pyc
diff --git a/TAL/HTMLParser.py b/TAL/HTMLParser.py
deleted file mode 100644 (file)
index 5ab076b..0000000
+++ /dev/null
@@ -1,403 +0,0 @@
-"""A parser for HTML and XHTML."""
-
-# This file is based on sgmllib.py, but the API is slightly different.
-
-# XXX There should be a way to distinguish between PCDATA (parsed
-# character data -- the normal case), RCDATA (replaceable character
-# data -- only char and entity references and end tags are special)
-# and CDATA (character data -- only end tags are special).
-
-
-import markupbase
-import re
-import string
-
-# Regular expressions used for parsing
-
-interesting_normal = re.compile('[&<]')
-interesting_cdata = re.compile(r'<(/|\Z)')
-incomplete = re.compile('&[a-zA-Z#]')
-
-entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
-charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
-
-starttagopen = re.compile('<[a-zA-Z]')
-piclose = re.compile('>')
-endtagopen = re.compile('</')
-commentclose = re.compile(r'--\s*>')
-tagfind = re.compile('[a-zA-Z][-.a-zA-Z0-9:_]*')
-attrfind = re.compile(
-    r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*'
-    r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./:;+*%?!&$\(\)_#=~]*))?')
-
-locatestarttagend = re.compile(r"""
-  <[a-zA-Z][-.a-zA-Z0-9:_]*          # tag name
-  (?:\s+                             # whitespace before attribute name
-    (?:[a-zA-Z_][-.:a-zA-Z0-9_]*     # attribute name
-      (?:\s*=\s*                     # value indicator
-        (?:'[^']*'                   # LITA-enclosed value
-          |\"[^\"]*\"                # LIT-enclosed value
-          |[^'\">\s]+                # bare value
-         )
-       )?
-     )
-   )*
-  \s*                                # trailing whitespace
-""", re.VERBOSE)
-endendtag = re.compile('>')
-endtagfind = re.compile('</\s*([a-zA-Z][-.a-zA-Z0-9:_]*)\s*>')
-
-
-class HTMLParseError(Exception):
-    """Exception raised for all parse errors."""
-
-    def __init__(self, msg, position=(None, None)):
-        assert msg
-        self.msg = msg
-        self.lineno = position[0]
-        self.offset = position[1]
-
-    def __str__(self):
-        result = self.msg
-        if self.lineno is not None:
-            result = result + ", at line %d" % self.lineno
-        if self.offset is not None:
-            result = result + ", column %d" % (self.offset + 1)
-        return result
-
-
-def _contains_at(s, sub, pos):
-    return s[pos:pos+len(sub)] == sub
-
-
-class HTMLParser(markupbase.ParserBase):
-    """Find tags and other markup and call handler functions.
-
-    Usage:
-        p = HTMLParser()
-        p.feed(data)
-        ...
-        p.close()
-
-    Start tags are handled by calling self.handle_starttag() or
-    self.handle_startendtag(); end tags by self.handle_endtag().  The
-    data between tags is passed from the parser to the derived class
-    by calling self.handle_data() with the data as argument (the data
-    may be split up in arbitrary chunks).  Entity references are
-    passed by calling self.handle_entityref() with the entity
-    reference as the argument.  Numeric character references are
-    passed to self.handle_charref() with the string containing the
-    reference as the argument.
-    """
-
-    CDATA_CONTENT_ELEMENTS = ("script", "style")
-
-
-    def __init__(self):
-        """Initialize and reset this instance."""
-        self.reset()
-
-    def reset(self):
-        """Reset this instance.  Loses all unprocessed data."""
-        self.rawdata = ''
-        self.stack = []
-        self.lasttag = '???'
-        self.interesting = interesting_normal
-        markupbase.ParserBase.reset(self)
-
-    def feed(self, data):
-        """Feed data to the parser.
-
-        Call this as often as you want, with as little or as much text
-        as you want (may include '\n').
-        """
-        self.rawdata = self.rawdata + data
-        self.goahead(0)
-
-    def close(self):
-        """Handle any buffered data."""
-        self.goahead(1)
-
-    def error(self, message):
-        raise HTMLParseError(message, self.getpos())
-
-    __starttag_text = None
-
-    def get_starttag_text(self):
-        """Return full source of start tag: '<...>'."""
-        return self.__starttag_text
-
-    cdata_endtag = None
-
-    def set_cdata_mode(self, endtag=None):
-        self.cdata_endtag = endtag
-        self.interesting = interesting_cdata
-
-    def clear_cdata_mode(self):
-        self.cdata_endtag = None
-        self.interesting = interesting_normal
-
-    # Internal -- handle data as far as reasonable.  May leave state
-    # and data to be processed by a subsequent call.  If 'end' is
-    # true, force handling all data as if followed by EOF marker.
-    def goahead(self, end):
-        rawdata = self.rawdata
-        i = 0
-        n = len(rawdata)
-        while i < n:
-            match = self.interesting.search(rawdata, i) # < or &
-            if match:
-                j = match.start()
-            else:
-                j = n
-            if i < j: self.handle_data(rawdata[i:j])
-            i = self.updatepos(i, j)
-            if i == n: break
-            if rawdata[i] == '<':
-                if starttagopen.match(rawdata, i): # < + letter
-                    k = self.parse_starttag(i)
-                elif endtagopen.match(rawdata, i): # </
-                    k = self.parse_endtag(i)
-                elif _contains_at(rawdata, "<!--", i): # <!--
-                    k = self.parse_comment(i)
-                elif _contains_at(rawdata, "<!", i): # <!
-                    k = self.parse_declaration(i)
-                elif _contains_at(rawdata, "<?", i): # <?
-                    k = self.parse_pi(i)
-                elif _contains_at(rawdata, "<?", i): # <!
-                    k = self.parse_declaration(i)
-                elif (i + 1) < n:
-                    self.handle_data("<")
-                    k = i + 1
-                else:
-                    break
-                if k < 0:
-                    if end:
-                        self.error("EOF in middle of construct")
-                    break
-                i = self.updatepos(i, k)
-            elif rawdata[i:i+2] == "&#":
-                match = charref.match(rawdata, i)
-                if match:
-                    name = match.group()[2:-1]
-                    self.handle_charref(name)
-                    k = match.end()
-                    if rawdata[k-1] != ';':
-                        k = k - 1
-                    i = self.updatepos(i, k)
-                    continue
-                else:
-                    break
-            elif rawdata[i] == '&':
-                match = entityref.match(rawdata, i)
-                if match:
-                    name = match.group(1)
-                    self.handle_entityref(name)
-                    k = match.end()
-                    if rawdata[k-1] != ';':
-                        k = k - 1
-                    i = self.updatepos(i, k)
-                    continue
-                match = incomplete.match(rawdata, i)
-                if match:
-                    # match.group() will contain at least 2 chars
-                    rest = rawdata[i:]
-                    if end and match.group() == rest:
-                        self.error("EOF in middle of entity or char ref")
-                    # incomplete
-                    break
-                elif (i + 1) < n:
-                    # not the end of the buffer, and can't be confused
-                    # with some other construct
-                    self.handle_data("&")
-                    i = self.updatepos(i, i + 1)
-                else:
-                    break
-            else:
-                assert 0, "interesting.search() lied"
-        # end while
-        if end and i < n:
-            self.handle_data(rawdata[i:n])
-            i = self.updatepos(i, n)
-        self.rawdata = rawdata[i:]
-
-    # Internal -- parse comment, return end or -1 if not terminated
-    def parse_comment(self, i, report=1):
-        rawdata = self.rawdata
-        assert rawdata[i:i+4] == '<!--', 'unexpected call to parse_comment()'
-        match = commentclose.search(rawdata, i+4)
-        if not match:
-            return -1
-        if report:
-            j = match.start()
-            self.handle_comment(rawdata[i+4: j])
-        j = match.end()
-        return j
-
-    # Internal -- parse processing instr, return end or -1 if not terminated
-    def parse_pi(self, i):
-        rawdata = self.rawdata
-        assert rawdata[i:i+2] == '<?', 'unexpected call to parse_pi()'
-        match = piclose.search(rawdata, i+2) # >
-        if not match:
-            return -1
-        j = match.start()
-        self.handle_pi(rawdata[i+2: j])
-        j = match.end()
-        return j
-
-    # Internal -- handle starttag, return end or -1 if not terminated
-    def parse_starttag(self, i):
-        self.__starttag_text = None
-        endpos = self.check_for_whole_start_tag(i)
-        if endpos < 0:
-            return endpos
-        rawdata = self.rawdata
-        self.__starttag_text = rawdata[i:endpos]
-
-        # Now parse the data between i+1 and j into a tag and attrs
-        attrs = []
-        match = tagfind.match(rawdata, i+1)
-        assert match, 'unexpected call to parse_starttag()'
-        k = match.end()
-        self.lasttag = tag = string.lower(rawdata[i+1:k])
-
-        while k < endpos:
-            m = attrfind.match(rawdata, k)
-            if not m:
-                break
-            attrname, rest, attrvalue = m.group(1, 2, 3)
-            if not rest:
-                attrvalue = None
-            elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
-                 attrvalue[:1] == '"' == attrvalue[-1:]:
-                attrvalue = attrvalue[1:-1]
-                attrvalue = self.unescape(attrvalue)
-            attrs.append((string.lower(attrname), attrvalue))
-            k = m.end()
-
-        end = string.strip(rawdata[k:endpos])
-        if end not in (">", "/>"):
-            lineno, offset = self.getpos()
-            if "\n" in self.__starttag_text:
-                lineno = lineno + string.count(self.__starttag_text, "\n")
-                offset = len(self.__starttag_text) \
-                         - string.rfind(self.__starttag_text, "\n")
-            else:
-                offset = offset + len(self.__starttag_text)
-            self.error("junk characters in start tag: %s"
-                       % `rawdata[k:endpos][:20]`)
-        if end[-2:] == '/>':
-            # XHTML-style empty tag: <span attr="value" />
-            self.handle_startendtag(tag, attrs)
-        else:
-            self.handle_starttag(tag, attrs)
-            if tag in self.CDATA_CONTENT_ELEMENTS:
-                self.set_cdata_mode(tag)
-        return endpos
-
-    # Internal -- check to see if we have a complete starttag; return end
-    # or -1 if incomplete.
-    def check_for_whole_start_tag(self, i):
-        rawdata = self.rawdata
-        m = locatestarttagend.match(rawdata, i)
-        if m:
-            j = m.end()
-            next = rawdata[j:j+1]
-            if next == ">":
-                return j + 1
-            if next == "/":
-                s = rawdata[j:j+2]
-                if s == "/>":
-                    return j + 2
-                if s == "/":
-                    # buffer boundary
-                    return -1
-                # else bogus input
-                self.updatepos(i, j + 1)
-                self.error("malformed empty start tag")
-            if next == "":
-                # end of input
-                return -1
-            if next in ("abcdefghijklmnopqrstuvwxyz=/"
-                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
-                # end of input in or before attribute value, or we have the
-                # '/' from a '/>' ending
-                return -1
-            self.updatepos(i, j)
-            self.error("malformed start tag")
-        raise AssertionError("we should not get here!")
-
-    # Internal -- parse endtag, return end or -1 if incomplete
-    def parse_endtag(self, i):
-        rawdata = self.rawdata
-        assert rawdata[i:i+2] == "</", "unexpected call to parse_endtag"
-        match = endendtag.search(rawdata, i+1) # >
-        if not match:
-            return -1
-        j = match.end()
-        match = endtagfind.match(rawdata, i) # </ + tag + >
-        if not match:
-            self.error("bad end tag: %s" % `rawdata[i:j]`)
-        tag = string.lower(match.group(1))
-        if (  self.cdata_endtag is not None
-              and tag != self.cdata_endtag):
-            # Should be a mismatched end tag, but we'll treat it
-            # as text anyway, since most HTML authors aren't
-            # interested in the finer points of syntax.
-            self.handle_data(match.group(0))
-        else:
-            self.handle_endtag(tag)
-            self.clear_cdata_mode()
-        return j
-
-    # Overridable -- finish processing of start+end tag: <tag.../>
-    def handle_startendtag(self, tag, attrs):
-        self.handle_starttag(tag, attrs)
-        self.handle_endtag(tag)
-
-    # Overridable -- handle start tag
-    def handle_starttag(self, tag, attrs):
-        pass
-
-    # Overridable -- handle end tag
-    def handle_endtag(self, tag):
-        pass
-
-    # Overridable -- handle character reference
-    def handle_charref(self, name):
-        pass
-
-    # Overridable -- handle entity reference
-    def handle_entityref(self, name):
-        pass
-
-    # Overridable -- handle data
-    def handle_data(self, data):
-        pass
-
-    # Overridable -- handle comment
-    def handle_comment(self, data):
-        pass
-
-    # Overridable -- handle declaration
-    def handle_decl(self, decl):
-        pass
-
-    # Overridable -- handle processing instruction
-    def handle_pi(self, data):
-        pass
-
-    def unknown_decl(self, data):
-        self.error("unknown declaration: " + `data`)
-
-    # Internal -- helper to remove special character quoting
-    def unescape(self, s):
-        if '&' not in s:
-            return s
-        s = string.replace(s, "&lt;", "<")
-        s = string.replace(s, "&gt;", ">")
-        s = string.replace(s, "&apos;", "'")
-        s = string.replace(s, "&quot;", '"')
-        s = string.replace(s, "&amp;", "&") # Must be last
-        return s
diff --git a/TAL/HTMLTALParser.py b/TAL/HTMLTALParser.py
deleted file mode 100644 (file)
index 8d7f0db..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
-##############################################################################
-#
-# 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
-# 
-##############################################################################
-"""
-Parse HTML and compile to TALInterpreter intermediate code.
-"""
-
-import sys
-import string
-
-from TALGenerator import TALGenerator
-from TALDefs import ZOPE_METAL_NS, ZOPE_TAL_NS, METALError, TALError
-from HTMLParser import HTMLParser, HTMLParseError
-
-BOOLEAN_HTML_ATTRS = [
-    # List of Boolean attributes in HTML that may be given in
-    # minimized form (e.g. <img ismap> rather than <img ismap="">)
-    # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
-    "compact", "nowrap", "ismap", "declare", "noshade", "checked",
-    "disabled", "readonly", "multiple", "selected", "noresize",
-    "defer"
-    ]
-
-EMPTY_HTML_TAGS = [
-    # List of HTML tags with an empty content model; these are
-    # rendered in minimized form, e.g. <img />.
-    # From http://www.w3.org/TR/xhtml1/#dtds
-    "base", "meta", "link", "hr", "br", "param", "img", "area",
-    "input", "col", "basefont", "isindex", "frame",
-    ]
-
-PARA_LEVEL_HTML_TAGS = [
-    # List of HTML elements that close open paragraph-level elements
-    # and are themselves paragraph-level.
-    "h1", "h2", "h3", "h4", "h5", "h6", "p",
-    ]
-
-BLOCK_CLOSING_TAG_MAP = {
-    "tr": ("tr", "td", "th"),
-    "td": ("td", "th"),
-    "th": ("td", "th"),
-    "li": ("li",),
-    "dd": ("dd", "dt"),
-    "dt": ("dd", "dt"),
-    }
-
-BLOCK_LEVEL_HTML_TAGS = [
-    # List of HTML tags that denote larger sections than paragraphs.
-    "blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody",
-    "noframe", "ul", "ol", "li", "dl", "dt", "dd", "div",
-    ]
-
-TIGHTEN_IMPLICIT_CLOSE_TAGS = (PARA_LEVEL_HTML_TAGS
-                               + BLOCK_CLOSING_TAG_MAP.keys())
-
-
-class NestingError(HTMLParseError):
-    """Exception raised when elements aren't properly nested."""
-
-    def __init__(self, tagstack, endtag, position=(None, None)):
-        self.endtag = endtag
-        if tagstack:
-            if len(tagstack) == 1:
-                msg = ('Open tag <%s> does not match close tag </%s>'
-                       % (tagstack[0], endtag))
-            else:
-                msg = ('Open tags <%s> do not match close tag </%s>'
-                       % (string.join(tagstack, '>, <'), endtag))
-        else:
-            msg = 'No tags are open to match </%s>' % endtag
-        HTMLParseError.__init__(self, msg, position)
-
-class EmptyTagError(NestingError):
-    """Exception raised when empty elements have an end tag."""
-
-    def __init__(self, tag, position=(None, None)):
-        self.tag = tag
-        msg = 'Close tag </%s> should be removed' % tag
-        HTMLParseError.__init__(self, msg, position)
-
-class OpenTagError(NestingError):
-    """Exception raised when a tag is not allowed in another tag."""
-
-    def __init__(self, tagstack, tag, position=(None, None)):
-        self.tag = tag
-        msg = 'Tag <%s> is not allowed in <%s>' % (tag, tagstack[-1])
-        HTMLParseError.__init__(self, msg, position)
-
-class HTMLTALParser(HTMLParser):
-
-    # External API
-
-    def __init__(self, gen=None):
-        HTMLParser.__init__(self)
-        if gen is None:
-            gen = TALGenerator(xml=0)
-        self.gen = gen
-        self.tagstack = []
-        self.nsstack = []
-        self.nsdict = {'tal': ZOPE_TAL_NS, 'metal': ZOPE_METAL_NS}
-
-    def parseFile(self, file):
-        f = open(file)
-        data = f.read()
-        f.close()
-        self.parseString(data)
-
-    def parseString(self, data):
-        self.feed(data)
-        self.close()
-        while self.tagstack:
-            self.implied_endtag(self.tagstack[-1], 2)
-        assert self.nsstack == [], self.nsstack
-
-    def getCode(self):
-        return self.gen.getCode()
-
-    def getWarnings(self):
-        return ()
-
-    # Overriding HTMLParser methods
-
-    def handle_starttag(self, tag, attrs):
-        self.close_para_tags(tag)
-        self.scan_xmlns(attrs)
-        tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
-        self.tagstack.append(tag)
-        self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
-                                  self.getpos())
-        if tag in EMPTY_HTML_TAGS:
-            self.implied_endtag(tag, -1)
-
-    def handle_startendtag(self, tag, attrs):
-        self.close_para_tags(tag)
-        self.scan_xmlns(attrs)
-        tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
-        if taldict.get("content"):
-            self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
-                                      self.getpos())
-            self.gen.emitEndElement(tag, implied=-1)
-        else:
-            self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
-                                      self.getpos(), isend=1)
-        self.pop_xmlns()
-
-    def handle_endtag(self, tag):
-        if tag in EMPTY_HTML_TAGS:
-            # </img> etc. in the source is an error
-            raise EmptyTagError(tag, self.getpos())
-        self.close_enclosed_tags(tag)
-        self.gen.emitEndElement(tag)
-        self.pop_xmlns()
-        self.tagstack.pop()
-
-    def close_para_tags(self, tag):
-        if tag in EMPTY_HTML_TAGS:
-            return
-        close_to = -1
-        if BLOCK_CLOSING_TAG_MAP.has_key(tag):
-            blocks_to_close = BLOCK_CLOSING_TAG_MAP[tag]
-            for i in range(len(self.tagstack)):
-                t = self.tagstack[i]
-                if t in blocks_to_close:
-                    if close_to == -1:
-                        close_to = i
-                elif t in BLOCK_LEVEL_HTML_TAGS:
-                    close_to = -1
-        elif tag in PARA_LEVEL_HTML_TAGS + BLOCK_LEVEL_HTML_TAGS:
-            i = len(self.tagstack) - 1
-            while i >= 0:
-                closetag = self.tagstack[i]
-                if closetag in BLOCK_LEVEL_HTML_TAGS:
-                    break
-                if closetag in PARA_LEVEL_HTML_TAGS:
-                    if closetag != "p":
-                        raise OpenTagError(self.tagstack, tag, self.getpos())
-                    close_to = i
-                i = i - 1
-        if close_to >= 0:
-            while len(self.tagstack) > close_to:
-                self.implied_endtag(self.tagstack[-1], 1)
-
-    def close_enclosed_tags(self, tag):
-        if tag not in self.tagstack:
-            raise NestingError(self.tagstack, tag, self.getpos())
-        while tag != self.tagstack[-1]:
-            self.implied_endtag(self.tagstack[-1], 1)
-        assert self.tagstack[-1] == tag
-
-    def implied_endtag(self, tag, implied):
-        assert tag == self.tagstack[-1]
-        assert implied in (-1, 1, 2)
-        isend = (implied < 0)
-        if tag in TIGHTEN_IMPLICIT_CLOSE_TAGS:
-            # Pick out trailing whitespace from the program, and
-            # insert the close tag before the whitespace.
-            white = self.gen.unEmitWhitespace()
-        else:
-            white = None
-        self.gen.emitEndElement(tag, isend=isend, implied=implied)
-        if white:
-            self.gen.emitRawText(white)
-        self.tagstack.pop()
-        self.pop_xmlns()
-
-    def handle_charref(self, name):
-        self.gen.emitRawText("&#%s;" % name)
-
-    def handle_entityref(self, name):
-        self.gen.emitRawText("&%s;" % name)
-
-    def handle_data(self, data):
-        self.gen.emitRawText(data)
-
-    def handle_comment(self, data):
-        self.gen.emitRawText("<!--%s-->" % data)
-
-    def handle_decl(self, data):
-        self.gen.emitRawText("<!%s>" % data)
-
-    def handle_pi(self, data):
-        self.gen.emitRawText("<?%s>" % data)
-
-    # Internal thingies
-
-    def scan_xmlns(self, attrs):
-        nsnew = {}
-        for key, value in attrs:
-            if key[:6] == "xmlns:":
-                nsnew[key[6:]] = value
-        if nsnew:
-            self.nsstack.append(self.nsdict)
-            self.nsdict = self.nsdict.copy()
-            self.nsdict.update(nsnew)
-        else:
-            self.nsstack.append(self.nsdict)
-
-    def pop_xmlns(self):
-        self.nsdict = self.nsstack.pop()
-
-    def fixname(self, name):
-        if ':' in name:
-            prefix, suffix = string.split(name, ':', 1)
-            if prefix == 'xmlns':
-                nsuri = self.nsdict.get(suffix)
-                if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS):
-                    return name, name, prefix
-            else:
-                nsuri = self.nsdict.get(prefix)
-                if nsuri == ZOPE_TAL_NS:
-                    return name, suffix, 'tal'
-                elif nsuri == ZOPE_METAL_NS:
-                    return name, suffix,  'metal'
-        return name, name, 0
-
-    def process_ns(self, name, attrs):
-        attrlist = []
-        taldict = {}
-        metaldict = {}
-        name, namebase, namens = self.fixname(name)
-        for item in attrs:
-            key, value = item
-            key, keybase, keyns = self.fixname(key)
-            ns = keyns or namens # default to tag namespace
-            if ns and ns != 'unknown':
-                item = (key, value, ns)
-            if ns == 'tal':
-                if taldict.has_key(keybase):
-                    raise TALError("duplicate TAL attribute " +
-                                   `keybase`, self.getpos())
-                taldict[keybase] = value
-            elif ns == 'metal':
-                if metaldict.has_key(keybase):
-                    raise METALError("duplicate METAL attribute " +
-                                     `keybase`, self.getpos())
-                metaldict[keybase] = value
-            attrlist.append(item)
-        if namens in ('metal', 'tal'):
-            taldict['tal tag'] = namens
-        return name, attrlist, taldict, metaldict
diff --git a/TAL/README.txt b/TAL/README.txt
deleted file mode 100644 (file)
index 4a28816..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-TAL - Template Attribute Language
----------------------------------
-
-This is an implementation of TAL, the Zope Template Attribute
-Language.  For TAL, see the Zope Presentation Templates ZWiki:
-
-    http://dev.zope.org/Wikis/DevSite/Projects/ZPT/FrontPage
-
-It is not a Zope product nor is it designed exclusively to run inside
-of Zope, but if you have a Zope checkout that includes
-Products/ParsedXML, its Expat parser will be used.
-
-Prerequisites
--------------
-
-You need:
-
-- A recent checkout of Zope2; don't forget to run the wo_pcgi.py
-  script to compile everything.  (See above -- this is now optional.)
-
-- A recent checkout of the Zope2 product ParsedXML, accessible
-  throught <Zope2>/lib/python/Products/ParsedXML; don't forget to run
-  the setup.py script to compiles Expat.  (Again, optional.)
-
-- Python 1.5.2; the driver script refuses to work with other versions
-  unless you specify the -n option; this is done so that I don't
-  accidentally use Python 2.x features.
-
-- Create a .path file containing proper module search path; it should
-  point the <Zope2>/lib/python directory that you want to use.
-
-How To Play
------------
-
-(Don't forget to edit .path, see above!)
-
-The script driver.py takes an XML file with TAL markup as argument and
-writes the expanded version to standard output.  The filename argument
-defaults to tests/input/test01.xml.
-
-Regression test
----------------
-
-There are unit test suites in the 'tests' subdirectory; these can be
-run with tests/run.py.  This should print the testcase names plus
-progress info, followed by a final line saying "OK".  It requires that
-../unittest.py exists.
-
-There are a number of test files in the 'tests' subdirectory, named
-tests/input/test<number>.xml and tests/input/test<number>.html.  The
-Python script ./runtest.py calls driver.main() for each test file, and
-should print "<file> OK" for each one.  These tests are also run as
-part of the unit test suites, so tests/run.py is all you need.
-
-What's Here
------------
-
-DummyEngine.py         simple-minded TALES execution engine
-TALInterpreter.py      class to interpret intermediate code
-TALGenerator.py                class to generate intermediate code
-XMLParser.py           base class to parse XML, avoiding DOM
-TALParser.py           class to parse XML with TAL into intermediate code
-HTMLTALParser.py       class to parse HTML with TAL into intermediate code
-HTMLParser.py          HTML-parsing base class
-driver.py              script to demonstrate TAL expansion
-timer.py               script to time various processing phases
-setpath.py             hack to set sys.path and import ZODB
-__init__.py            empty file that makes this directory a package
-runtest.py             Python script to run file-comparison tests
-ndiff.py               helper for runtest.py to produce diffs
-tests/                 drectory with test files and output
-tests/run.py           Python script to run all tests
-
-Author and License
-------------------
-
-This code is written by Guido van Rossum (project lead), Fred Drake,
-and Tim Peters.  It is owned by Digital Creations and can be
-redistributed under the Zope Public License.
-
-TO DO
------
-
-(See also http://www.zope.org/Members/jim/ZPTIssueTracker .)
-
-- Need to remove leading whitespace and newline when omitting an
-  element (either through tal:replace with a value of nothing or
-  tal:condition with a false condition).
-
-- Empty TAL/METAL attributes are ignored: tal:replace="" is ignored
-  rather than causing an error.
-
-- HTMLTALParser.py and TALParser.py are silly names.  Should be
-  HTMLTALCompiler.py and XMLTALCompiler.py (or maybe shortened,
-  without "TAL"?)
-
-- Should we preserve case of tags and attribute names in HTML?
diff --git a/TAL/TALDefs.py b/TAL/TALDefs.py
deleted file mode 100644 (file)
index dbc0443..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-##############################################################################
-#
-# 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
-# 
-##############################################################################
-"""
-Common definitions used by TAL and METAL compilation an transformation.
-"""
-
-from types import ListType, TupleType
-
-TAL_VERSION = "1.3.2"
-
-XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace
-XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations
-
-ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal"
-ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal"
-
-NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*"
-
-KNOWN_METAL_ATTRIBUTES = [
-    "define-macro",
-    "use-macro",
-    "define-slot",
-    "fill-slot",
-    "slot"
-    ]
-
-KNOWN_TAL_ATTRIBUTES = [
-    "define",
-    "condition",
-    "content",
-    "replace",
-    "repeat",
-    "attributes",
-    "on-error",
-    "omit-tag",
-    "tal tag",
-    ]
-
-class TALError(Exception):
-
-    def __init__(self, msg, position=(None, None)):
-        assert msg != ""
-        self.msg = msg
-        self.lineno = position[0]
-        self.offset = position[1]
-
-    def __str__(self):
-        result = self.msg
-        if self.lineno is not None:
-            result = result + ", at line %d" % self.lineno
-        if self.offset is not None:
-            result = result + ", column %d" % (self.offset + 1)
-        return result
-
-class METALError(TALError):
-    pass
-
-class TALESError(TALError):
-    pass
-
-class ErrorInfo:
-
-    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]
-
-import re
-_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
-_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)
-del re
-
-def parseAttributeReplacements(arg):
-    dict = {}
-    for part in splitParts(arg):
-        m = _attr_re.match(part)
-        if not m:
-            raise TALError("Bad syntax in attributes:" + `part`)
-        name, expr = m.group(1, 2)
-        if dict.has_key(name):
-            raise TALError("Duplicate attribute name in attributes:" + `part`)
-        dict[name] = expr
-    return dict
-
-def parseSubstitution(arg, position=(None, None)):
-    m = _subst_re.match(arg)
-    if not m:
-        raise TALError("Bad syntax in substitution text: " + `arg`, position)
-    key, expr = m.group(1, 2)
-    if not key:
-        key = "text"
-    return key, expr
-
-def splitParts(arg):
-    # Break in pieces at undoubled semicolons and
-    # change double semicolons to singles:
-    import string
-    arg = string.replace(arg, ";;", "\0")
-    parts = string.split(arg, ';')
-    parts = map(lambda s, repl=string.replace: repl(s, "\0", ";"), parts)
-    if len(parts) > 1 and not string.strip(parts[-1]):
-        del parts[-1] # It ended in a semicolon
-    return parts
-
-def isCurrentVersion(program):
-    version = getProgramVersion(program)
-    return version == TAL_VERSION
-
-def getProgramMode(program):
-    version = getProgramVersion(program)
-    if (version == TAL_VERSION and isinstance(program[1], TupleType) and
-        len(program[1]) == 2):
-        opcode, mode = program[1]
-        if opcode == "mode":
-            return mode
-    return None
-
-def getProgramVersion(program):
-    if (len(program) >= 2 and
-        isinstance(program[0], TupleType) and len(program[0]) == 2):
-        opcode, version = program[0]
-        if opcode == "version":
-            return version
-    return None
-
-import cgi
-def quote(s, escape=cgi.escape):
-    return '"%s"' % escape(s, 1)
-del cgi
diff --git a/TAL/TALGenerator.py b/TAL/TALGenerator.py
deleted file mode 100644 (file)
index 0bfadea..0000000
+++ /dev/null
@@ -1,583 +0,0 @@
-##############################################################################
-#
-# 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
-# 
-##############################################################################
-"""
-Code generator for TALInterpreter intermediate code.
-"""
-
-import string
-import re
-import cgi
-
-from TALDefs import *
-
-class TALGenerator:
-
-    inMacroUse = 0
-    inMacroDef = 0
-    source_file = None
-    
-    def __init__(self, expressionCompiler=None, xml=1, source_file=None):
-        if not expressionCompiler:
-            from DummyEngine import DummyEngine
-            expressionCompiler = DummyEngine()
-        self.expressionCompiler = expressionCompiler
-        self.CompilerError = expressionCompiler.getCompilerError()
-        self.program = []
-        self.stack = []
-        self.todoStack = []
-        self.macros = {}
-        self.slots = {}
-        self.slotStack = []
-        self.xml = xml
-        self.emit("version", TAL_VERSION)
-        self.emit("mode", xml and "xml" or "html")
-        if source_file is not None:
-            self.source_file = source_file
-            self.emit("setSourceFile", source_file)
-
-    def getCode(self):
-        assert not self.stack
-        assert not self.todoStack
-        return self.optimize(self.program), self.macros
-
-    def optimize(self, program):
-        output = []
-        collect = []
-        rawseen = cursor = 0
-        if self.xml:
-            endsep = "/>"
-        else:
-            endsep = " />"
-        for cursor in xrange(len(program)+1):
-            try:
-                item = program[cursor]
-            except IndexError:
-                item = (None, None)
-            opcode = item[0]
-            if opcode == "rawtext":
-                collect.append(item[1])
-                continue
-            if opcode == "endTag":
-                collect.append("</%s>" % item[1])
-                continue
-            if opcode == "startTag":
-                if self.optimizeStartTag(collect, item[1], item[2], ">"):
-                    continue
-            if opcode == "startEndTag":
-                if self.optimizeStartTag(collect, item[1], item[2], endsep):
-                    continue
-            if opcode in ("beginScope", "endScope"):
-                # Push *Scope instructions in front of any text instructions;
-                # this allows text instructions separated only by *Scope
-                # instructions to be joined together.
-                output.append(self.optimizeArgsList(item))
-                continue
-            text = string.join(collect, "")
-            if text:
-                i = string.rfind(text, "\n")
-                if i >= 0:
-                    i = len(text) - (i + 1)
-                    output.append(("rawtextColumn", (text, i)))
-                else:
-                    output.append(("rawtextOffset", (text, len(text))))
-            if opcode != None:
-                output.append(self.optimizeArgsList(item))
-            rawseen = cursor+1
-            collect = []
-        return self.optimizeCommonTriple(output)
-
-    def optimizeArgsList(self, item):
-        if len(item) == 2:
-            return item
-        else:
-            return item[0], tuple(item[1:])
-
-    actionIndex = {"replace":0, "insert":1, "metal":2, "tal":3, "xmlns":4,
-                   0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
-    def optimizeStartTag(self, collect, name, attrlist, end):
-        if not attrlist:
-            collect.append("<%s%s" % (name, end))
-            return 1
-        opt = 1
-        new = ["<" + name]
-        for i in range(len(attrlist)):
-            item = attrlist[i]
-            if len(item) > 2:
-                opt = 0
-                name, value, action = item[:3]
-                action = self.actionIndex[action]
-                attrlist[i] = (name, value, action) + item[3:]
-            else:
-                if item[1] is None:
-                    s = item[0]
-                else:
-                    s = "%s=%s" % (item[0], quote(item[1]))
-                attrlist[i] = item[0], s
-            if item[1] is None:
-                new.append(" " + item[0])
-            else:
-                new.append(" %s=%s" % (item[0], quote(item[1])))
-        if opt:
-            new.append(end)
-            collect.extend(new)
-        return opt
-
-    def optimizeCommonTriple(self, program):
-        if len(program) < 3:
-            return program
-        output = program[:2]
-        prev2, prev1 = output
-        for item in program[2:]:
-            if (  item[0] == "beginScope"
-                  and prev1[0] == "setPosition"
-                  and prev2[0] == "rawtextColumn"):
-                position = output.pop()[1]
-                text, column = output.pop()[1]
-                prev1 = None, None
-                closeprev = 0
-                if output and output[-1][0] == "endScope":
-                    closeprev = 1
-                    output.pop()
-                item = ("rawtextBeginScope",
-                        (text, column, position, closeprev, item[1]))
-            output.append(item)
-            prev2 = prev1
-            prev1 = item
-        return output
-
-    def todoPush(self, todo):
-        self.todoStack.append(todo)
-
-    def todoPop(self):
-        return self.todoStack.pop()
-
-    def compileExpression(self, expr):
-        try:
-            return self.expressionCompiler.compile(expr)
-        except self.CompilerError, err:
-            raise TALError('%s in expression %s' % (err.args[0], `expr`),
-                           self.position)
-
-    def pushProgram(self):
-        self.stack.append(self.program)
-        self.program = []
-
-    def popProgram(self):
-        program = self.program
-        self.program = self.stack.pop()
-        return self.optimize(program)
-
-    def pushSlots(self):
-        self.slotStack.append(self.slots)
-        self.slots = {}
-
-    def popSlots(self):
-        slots = self.slots
-        self.slots = self.slotStack.pop()
-        return slots
-
-    def emit(self, *instruction):
-        self.program.append(instruction)
-
-    def emitStartTag(self, name, attrlist, isend=0):
-        if isend:
-            opcode = "startEndTag"
-        else:
-            opcode = "startTag"
-        self.emit(opcode, name, attrlist)
-
-    def emitEndTag(self, name):
-        if self.xml and self.program and self.program[-1][0] == "startTag":
-            # Minimize empty element
-            self.program[-1] = ("startEndTag",) + self.program[-1][1:]
-        else:
-            self.emit("endTag", name)
-
-    def emitOptTag(self, name, optTag, isend):
-        program = self.popProgram() #block
-        start = self.popProgram() #start tag
-        if (isend or not program) and self.xml:
-            # Minimize empty element
-            start[-1] = ("startEndTag",) + start[-1][1:]
-            isend = 1
-        cexpr = optTag[0]
-        if cexpr:
-            cexpr = self.compileExpression(optTag[0])
-        self.emit("optTag", name, cexpr, optTag[1], isend, start, program)
-        
-    def emitRawText(self, text):
-        self.emit("rawtext", text)
-
-    def emitText(self, text):
-        self.emitRawText(cgi.escape(text))
-
-    def emitDefines(self, defines):
-        for part in splitParts(defines):
-            m = re.match(
-                r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part)
-            if not m:
-                raise TALError("invalid define syntax: " + `part`,
-                               self.position)
-            scope, name, expr = m.group(1, 2, 3)
-            scope = scope or "local"
-            cexpr = self.compileExpression(expr)
-            if scope == "local":
-                self.emit("setLocal", name, cexpr)
-            else:
-                self.emit("setGlobal", name, cexpr)
-
-    def emitOnError(self, name, onError):
-        block = self.popProgram()
-        key, expr = parseSubstitution(onError)
-        cexpr = self.compileExpression(expr)
-        if key == "text":
-            self.emit("insertText", cexpr, [])
-        else:
-            assert key == "structure"
-            self.emit("insertStructure", cexpr, {}, [])
-        self.emitEndTag(name)
-        handler = self.popProgram()
-        self.emit("onError", block, handler)
-
-    def emitCondition(self, expr):
-        cexpr = self.compileExpression(expr)
-        program = self.popProgram()
-        self.emit("condition", cexpr, program)
-
-    def emitRepeat(self, arg):
-        m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg)
-        if not m:
-            raise TALError("invalid repeat syntax: " + `arg`,
-                           self.position)
-        name, expr = m.group(1, 2)
-        cexpr = self.compileExpression(expr)
-        program = self.popProgram()
-        self.emit("loop", name, cexpr, program)
-
-    def emitSubstitution(self, arg, attrDict={}):
-        key, expr = parseSubstitution(arg)
-        cexpr = self.compileExpression(expr)
-        program = self.popProgram()
-        if key == "text":
-            self.emit("insertText", cexpr, program)
-        else:
-            assert key == "structure"
-            self.emit("insertStructure", cexpr, attrDict, program)
-
-    def emitDefineMacro(self, macroName):
-        program = self.popProgram()
-        macroName = string.strip(macroName)
-        if self.macros.has_key(macroName):
-            raise METALError("duplicate macro definition: %s" % `macroName`,
-                             self.position)
-        if not re.match('%s$' % NAME_RE, macroName):
-            raise METALError("invalid macro name: %s" % `macroName`,
-                             self.position)
-        self.macros[macroName] = program
-        self.inMacroDef = self.inMacroDef - 1
-        self.emit("defineMacro", macroName, program)
-
-    def emitUseMacro(self, expr):
-        cexpr = self.compileExpression(expr)
-        program = self.popProgram()
-        self.inMacroUse = 0
-        self.emit("useMacro", expr, cexpr, self.popSlots(), program)
-
-    def emitDefineSlot(self, slotName):
-        program = self.popProgram()
-        slotName = string.strip(slotName)
-        if not re.match('%s$' % NAME_RE, slotName):
-            raise METALError("invalid slot name: %s" % `slotName`,
-                             self.position)
-        self.emit("defineSlot", slotName, program)
-
-    def emitFillSlot(self, slotName):
-        program = self.popProgram()
-        slotName = string.strip(slotName)
-        if self.slots.has_key(slotName):
-            raise METALError("duplicate fill-slot name: %s" % `slotName`,
-                             self.position)
-        if not re.match('%s$' % NAME_RE, slotName):
-            raise METALError("invalid slot name: %s" % `slotName`,
-                             self.position)
-        self.slots[slotName] = program
-        self.inMacroUse = 1
-        self.emit("fillSlot", slotName, program)
-
-    def unEmitWhitespace(self):
-        collect = []
-        i = len(self.program) - 1
-        while i >= 0:
-            item = self.program[i]
-            if item[0] != "rawtext":
-                break
-            text = item[1]
-            if not re.match(r"\A\s*\Z", text):
-                break
-            collect.append(text)
-            i = i-1
-        del self.program[i+1:]
-        if i >= 0 and self.program[i][0] == "rawtext":
-            text = self.program[i][1]
-            m = re.search(r"\s+\Z", text)
-            if m:
-                self.program[i] = ("rawtext", text[:m.start()])
-                collect.append(m.group())
-        collect.reverse()
-        return string.join(collect, "")
-
-    def unEmitNewlineWhitespace(self):
-        collect = []
-        i = len(self.program)
-        while i > 0:
-            i = i-1
-            item = self.program[i]
-            if item[0] != "rawtext":
-                break
-            text = item[1]
-            if re.match(r"\A[ \t]*\Z", text):
-                collect.append(text)
-                continue
-            m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text)
-            if not m:
-                break
-            text, rest = m.group(1, 2)
-            collect.reverse()
-            rest = rest + string.join(collect, "")
-            del self.program[i:]
-            if text:
-                self.emit("rawtext", text)
-            return rest
-        return None
-
-    def replaceAttrs(self, attrlist, repldict):
-        if not repldict:
-            return attrlist
-        newlist = []
-        for item in attrlist:
-            key = item[0]
-            if repldict.has_key(key):
-                item = item[:2] + ("replace", repldict[key])
-                del repldict[key]
-            newlist.append(item)
-        for key, value in repldict.items(): # Add dynamic-only attributes
-            item = (key, None, "insert", value)
-            newlist.append(item)
-        return newlist
-
-    def emitStartElement(self, name, attrlist, taldict, metaldict,
-                         position=(None, None), isend=0):
-        if not taldict and not metaldict:
-            # Handle the simple, common case
-            self.emitStartTag(name, attrlist, isend)
-            self.todoPush({})
-            if isend:
-                self.emitEndElement(name, isend)
-            return
-
-        self.position = position
-        for key, value in taldict.items():
-            if key not in KNOWN_TAL_ATTRIBUTES:
-                raise TALError("bad TAL attribute: " + `key`, position)
-            if not (value or key == 'omit-tag'):
-                raise TALError("missing value for TAL attribute: " +
-                               `key`, position)
-        for key, value in metaldict.items():
-            if key not in KNOWN_METAL_ATTRIBUTES:
-                raise METALError("bad METAL attribute: " + `key`,
-                position)
-            if not value:
-                raise TALError("missing value for METAL attribute: " +
-                               `key`, position)
-        todo = {}
-        defineMacro = metaldict.get("define-macro")
-        useMacro = metaldict.get("use-macro")
-        defineSlot = metaldict.get("define-slot")
-        fillSlot = metaldict.get("fill-slot")
-        define = taldict.get("define")
-        condition = taldict.get("condition")
-        repeat = taldict.get("repeat")
-        content = taldict.get("content")
-        replace = taldict.get("replace")
-        attrsubst = taldict.get("attributes")
-        onError = taldict.get("on-error")
-        omitTag = taldict.get("omit-tag")
-        TALtag = taldict.get("tal tag")
-        if len(metaldict) > 1 and (defineMacro or useMacro):
-            raise METALError("define-macro and use-macro cannot be used "
-                             "together or with define-slot or fill-slot",
-                             position)
-        if content and replace:
-            raise TALError("content and replace are mutually exclusive",
-                           position)
-
-        repeatWhitespace = None
-        if repeat:
-            # Hack to include preceding whitespace in the loop program
-            repeatWhitespace = self.unEmitNewlineWhitespace()
-        if position != (None, None):
-            # XXX at some point we should insist on a non-trivial position
-            self.emit("setPosition", position)
-        if self.inMacroUse:
-            if fillSlot:
-                self.pushProgram()
-                if self.source_file is not None:
-                    self.emit("setSourceFile", self.source_file)
-                todo["fillSlot"] = fillSlot
-                self.inMacroUse = 0
-        else:
-            if fillSlot:
-                raise METALError, ("fill-slot must be within a use-macro",
-                                   position)
-        if not self.inMacroUse:
-            if defineMacro:
-                self.pushProgram()
-                self.emit("version", TAL_VERSION)
-                self.emit("mode", self.xml and "xml" or "html")
-                if self.source_file is not None:
-                    self.emit("setSourceFile", self.source_file)
-                todo["defineMacro"] = defineMacro
-                self.inMacroDef = self.inMacroDef + 1
-            if useMacro:
-                self.pushSlots()
-                self.pushProgram()
-                todo["useMacro"] = useMacro
-                self.inMacroUse = 1
-            if defineSlot:
-                if not self.inMacroDef:
-                    raise METALError, (
-                        "define-slot must be within a define-macro",
-                        position)
-                self.pushProgram()
-                todo["defineSlot"] = defineSlot
-
-        if taldict:
-            dict = {}
-            for item in attrlist:
-                key, value = item[:2]
-                dict[key] = value
-            self.emit("beginScope", dict)
-            todo["scope"] = 1
-        if onError:
-            self.pushProgram() # handler
-            self.emitStartTag(name, list(attrlist)) # Must copy attrlist!
-            self.pushProgram() # block
-            todo["onError"] = onError
-        if define:
-            self.emitDefines(define)
-            todo["define"] = define
-        if condition:
-            self.pushProgram()
-            todo["condition"] = condition
-        if repeat:
-            todo["repeat"] = repeat
-            self.pushProgram()
-            if repeatWhitespace:
-                self.emitText(repeatWhitespace)
-        if content:
-            todo["content"] = content
-        if replace:
-            todo["replace"] = replace
-            self.pushProgram()
-        optTag = omitTag is not None or TALtag
-        if optTag:
-            todo["optional tag"] = omitTag, TALtag
-            self.pushProgram()
-        if attrsubst:
-            repldict = parseAttributeReplacements(attrsubst)
-            for key, value in repldict.items():
-                repldict[key] = self.compileExpression(value)
-        else:
-            repldict = {}
-        if replace:
-            todo["repldict"] = repldict
-            repldict = {}
-        self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend)
-        if optTag:
-            self.pushProgram()
-        if content:
-            self.pushProgram()
-        if todo and position != (None, None):
-            todo["position"] = position
-        self.todoPush(todo)
-        if isend:
-            self.emitEndElement(name, isend)
-
-    def emitEndElement(self, name, isend=0, implied=0):
-        todo = self.todoPop()
-        if not todo:
-            # Shortcut
-            if not isend:
-                self.emitEndTag(name)
-            return
-
-        self.position = position = todo.get("position", (None, None))
-        defineMacro = todo.get("defineMacro")
-        useMacro = todo.get("useMacro")
-        defineSlot = todo.get("defineSlot")
-        fillSlot = todo.get("fillSlot")
-        repeat = todo.get("repeat")
-        content = todo.get("content")
-        replace = todo.get("replace")
-        condition = todo.get("condition")
-        onError = todo.get("onError")
-        define = todo.get("define")
-        repldict = todo.get("repldict", {})
-        scope = todo.get("scope")
-        optTag = todo.get("optional tag")
-
-        if implied > 0:
-            if defineMacro or useMacro or defineSlot or fillSlot:
-                exc = METALError
-                what = "METAL"
-            else:
-                exc = TALError
-                what = "TAL"
-            raise exc("%s attributes on <%s> require explicit </%s>" %
-                      (what, name, name), position)
-
-        if content:
-            self.emitSubstitution(content, {})
-        if optTag:
-            self.emitOptTag(name, optTag, isend)
-        elif not isend:
-            self.emitEndTag(name)
-        if replace:
-            self.emitSubstitution(replace, repldict)
-        if repeat:
-            self.emitRepeat(repeat)
-        if condition:
-            self.emitCondition(condition)
-        if onError:
-            self.emitOnError(name, onError)
-        if scope:
-            self.emit("endScope")
-        if defineSlot:
-            self.emitDefineSlot(defineSlot)
-        if fillSlot:
-            self.emitFillSlot(fillSlot)
-        if useMacro:
-            self.emitUseMacro(useMacro)
-        if defineMacro:
-            self.emitDefineMacro(defineMacro)
-
-def test():
-    t = TALGenerator()
-    t.pushProgram()
-    t.emit("bar")
-    p = t.popProgram()
-    t.emit("foo", p)
-
-if __name__ == "__main__":
-    test()
diff --git a/TAL/TALInterpreter.py b/TAL/TALInterpreter.py
deleted file mode 100644 (file)
index 0f42284..0000000
+++ /dev/null
@@ -1,626 +0,0 @@
-##############################################################################
-#
-# 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
-# 
-##############################################################################
-"""
-Interpreter for a pre-compiled TAL program.
-"""
-
-import sys
-import getopt
-
-from cgi import escape
-from string import join, lower, rfind
-try:
-    from strop import lower, rfind
-except ImportError:
-    pass
-
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
-
-from TALDefs import quote, TAL_VERSION, TALError, METALError
-from TALDefs import isCurrentVersion, getProgramVersion, getProgramMode
-from TALGenerator import TALGenerator
-
-BOOLEAN_HTML_ATTRS = [
-    # List of Boolean attributes in HTML that should be rendered in
-    # minimized form (e.g. <img ismap> rather than <img ismap="">)
-    # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
-    # XXX The problem with this is that this is not valid XML and
-    # can't be parsed back!
-    "compact", "nowrap", "ismap", "declare", "noshade", "checked",
-    "disabled", "readonly", "multiple", "selected", "noresize",
-    "defer"
-]
-
-EMPTY_HTML_TAGS = [
-    # List of HTML tags with an empty content model; these are
-    # rendered in minimized form, e.g. <img />.
-    # From http://www.w3.org/TR/xhtml1/#dtds
-    "base", "meta", "link", "hr", "br", "param", "img", "area",
-    "input", "col", "basefont", "isindex", "frame",
-]
-
-class AltTALGenerator(TALGenerator):
-
-    def __init__(self, repldict, expressionCompiler=None, xml=0):
-        self.repldict = repldict
-        self.enabled = 1
-        TALGenerator.__init__(self, expressionCompiler, xml)
-
-    def enable(self, enabled):
-        self.enabled = enabled
-
-    def emit(self, *args):
-        if self.enabled:
-            apply(TALGenerator.emit, (self,) + args)
-
-    def emitStartElement(self, name, attrlist, taldict, metaldict,
-                         position=(None, None), isend=0):
-        metaldict = {}
-        taldict = {}
-        if self.enabled and self.repldict:
-            taldict["attributes"] = "x x"
-        TALGenerator.emitStartElement(self, name, attrlist,
-                                      taldict, metaldict, position, isend)
-
-    def replaceAttrs(self, attrlist, repldict):
-        if self.enabled and self.repldict:
-            repldict = self.repldict
-            self.repldict = None
-        return TALGenerator.replaceAttrs(self, attrlist, repldict)
-
-
-class TALInterpreter:
-
-    def __init__(self, program, macros, engine, stream=None,
-                 debug=0, wrap=60, metal=1, tal=1, showtal=-1,
-                 strictinsert=1, stackLimit=100):
-        self.program = program
-        self.macros = macros
-        self.engine = engine
-        self.Default = engine.getDefault()
-        self.stream = stream or sys.stdout
-        self._stream_write = self.stream.write
-        self.debug = debug
-        self.wrap = wrap
-        self.metal = metal
-        self.tal = tal
-        if tal:
-            self.dispatch = self.bytecode_handlers_tal
-        else:
-            self.dispatch = self.bytecode_handlers
-        assert showtal in (-1, 0, 1)
-        if showtal == -1:
-            showtal = (not tal)
-        self.showtal = showtal
-        self.strictinsert = strictinsert
-        self.stackLimit = stackLimit
-        self.html = 0
-        self.endsep = "/>"
-        self.endlen = len(self.endsep)
-        self.macroStack = []
-        self.popMacro = self.macroStack.pop
-        self.position = None, None  # (lineno, offset)
-        self.col = 0
-        self.level = 0
-        self.scopeLevel = 0
-        self.sourceFile = None
-
-    def saveState(self):
-        return (self.position, self.col, self.stream,
-                self.scopeLevel, self.level)
-
-    def restoreState(self, state):
-        (self.position, self.col, self.stream, scopeLevel, level) = state
-        self._stream_write = self.stream.write
-        assert self.level == level
-        while self.scopeLevel > scopeLevel:
-            self.engine.endScope()
-            self.scopeLevel = self.scopeLevel - 1
-        self.engine.setPosition(self.position)
-
-    def restoreOutputState(self, state):
-        (dummy, self.col, self.stream, scopeLevel, level) = state
-        self._stream_write = self.stream.write
-        assert self.level == level
-        assert self.scopeLevel == scopeLevel
-
-    def pushMacro(self, macroName, slots, entering=1):
-        if len(self.macroStack) >= self.stackLimit:
-            raise METALError("macro nesting limit (%d) exceeded "
-                             "by %s" % (self.stackLimit, `macroName`))
-        self.macroStack.append([macroName, slots, entering])
-
-    def macroContext(self, what):
-        macroStack = self.macroStack
-        i = len(macroStack)
-        while i > 0:
-            i = i-1
-            if macroStack[i][0] == what:
-                return i
-        return -1
-
-    def __call__(self):
-        assert self.level == 0
-        assert self.scopeLevel == 0
-        self.interpret(self.program)
-        assert self.level == 0
-        assert self.scopeLevel == 0
-        if self.col > 0:
-            self._stream_write("\n")
-            self.col = 0
-
-    def stream_write(self, s,
-                     len=len, rfind=rfind):
-        self._stream_write(s)
-        i = rfind(s, '\n')
-        if i < 0:
-            self.col = self.col + len(s)
-        else:
-            self.col = len(s) - (i + 1)
-
-    bytecode_handlers = {}
-
-    def interpret(self, program, None=None):
-        oldlevel = self.level
-        self.level = oldlevel + 1
-        handlers = self.dispatch
-        try:
-            if self.debug:
-                for (opcode, args) in program:
-                    s = "%sdo_%s%s\n" % ("    "*self.level, opcode,
-                                      repr(args))
-                    if len(s) > 80:
-                        s = s[:76] + "...\n"
-                    sys.stderr.write(s)
-                    handlers[opcode](self, args)
-            else:
-                for (opcode, args) in program:
-                    handlers[opcode](self, args)
-        finally:
-            self.level = oldlevel
-
-    def do_version(self, version):
-        assert version == TAL_VERSION
-    bytecode_handlers["version"] = do_version
-
-    def do_mode(self, mode):
-        assert mode in ("html", "xml")
-        self.html = (mode == "html")
-        if self.html:
-            self.endsep = " />"
-        else:
-            self.endsep = "/>"
-        self.endlen = len(self.endsep)
-    bytecode_handlers["mode"] = do_mode
-
-    def do_setSourceFile(self, source_file):
-        self.sourceFile = source_file
-        self.engine.setSourceFile(source_file)
-    bytecode_handlers["setSourceFile"] = do_setSourceFile
-
-    def do_setPosition(self, position):
-        self.position = position
-        self.engine.setPosition(position)
-    bytecode_handlers["setPosition"] = do_setPosition
-
-    def do_startEndTag(self, stuff):
-        self.do_startTag(stuff, self.endsep, self.endlen)
-    bytecode_handlers["startEndTag"] = do_startEndTag
-
-    def do_startTag(self, (name, attrList),
-                    end=">", endlen=1, _len=len):
-        # The bytecode generator does not cause calls to this method
-        # for start tags with no attributes; those are optimized down
-        # to rawtext events.  Hence, there is no special "fast path"
-        # for that case.
-        _stream_write = self._stream_write
-        _stream_write("<" + name)
-        namelen = _len(name)
-        col = self.col + namelen + 1
-        wrap = self.wrap
-        align = col + 1
-        if align >= wrap/2:
-            align = 4  # Avoid a narrow column far to the right
-        attrAction = self.dispatch["<attrAction>"]
-        try:
-            for item in attrList:
-                if _len(item) == 2:
-                    name, s = item
-                else:
-                    ok, name, s = attrAction(self, item)
-                    if not ok:
-                        continue
-                slen = _len(s)
-                if (wrap and
-                    col >= align and
-                    col + 1 + slen > wrap):
-                    _stream_write("\n" + " "*align)
-                    col = align + slen
-                else:
-                    s = " " + s
-                    col = col + 1 + slen
-                _stream_write(s)
-            _stream_write(end)
-            col = col + endlen
-        finally:
-            self.col = col
-    bytecode_handlers["startTag"] = do_startTag
-
-    def attrAction(self, item):
-        name, value, action = item[:3]
-        if action == 1 or (action > 1 and not self.showtal):
-            return 0, name, value
-        macs = self.macroStack
-        if action == 2 and self.metal and macs:
-            if len(macs) > 1 or not macs[-1][2]:
-                # Drop all METAL attributes at a use-depth above one.
-                return 0, name, value
-            # Clear 'entering' flag
-            macs[-1][2] = 0
-            # Convert or drop depth-one METAL attributes.
-            i = rfind(name, ":") + 1
-            prefix, suffix = name[:i], name[i:]
-            if suffix == "define-macro":
-                # Convert define-macro as we enter depth one.
-                name = prefix + "use-macro"
-                value = macs[-1][0] # Macro name
-            elif suffix == "define-slot":
-                name = prefix + "slot"
-            elif suffix == "fill-slot":
-                pass
-            else:
-                return 0, name, value
-
-        if value is None:
-            value = name
-        else:
-            value = "%s=%s" % (name, quote(value))
-        return 1, name, value
-
-    def attrAction_tal(self, item):
-        name, value, action = item[:3]
-        if action > 1:
-            return self.attrAction(item)
-        ok = 1
-        if self.html and lower(name) in BOOLEAN_HTML_ATTRS:
-            evalue = self.engine.evaluateBoolean(item[3])
-            if evalue is self.Default:
-                if action == 1: # Cancelled insert
-                    ok = 0
-            elif evalue:
-                value = None
-            else:
-                ok = 0
-        else:
-            evalue = self.engine.evaluateText(item[3])
-            if evalue is self.Default:
-                if action == 1: # Cancelled insert
-                    ok = 0
-            else:
-                if evalue is None:
-                    ok = 0
-                value = evalue
-        if ok:
-            if value is None:
-                value = name
-            value = "%s=%s" % (name, quote(value))
-        return ok, name, value
-
-    bytecode_handlers["<attrAction>"] = attrAction
-
-    def no_tag(self, start, program):
-        state = self.saveState()
-        self.stream = stream = StringIO()
-        self._stream_write = stream.write
-        self.interpret(start)
-        self.restoreOutputState(state)
-        self.interpret(program)
-
-    def do_optTag(self, (name, cexpr, tag_ns, isend, start, program),
-                  omit=0):
-        if tag_ns and not self.showtal:
-            return self.no_tag(start, program)
-            
-        self.interpret(start)
-        if not isend:
-            self.interpret(program)
-            s = '</%s>' % name
-            self._stream_write(s)
-            self.col = self.col + len(s)
-
-    def do_optTag_tal(self, stuff):
-        cexpr = stuff[1]
-        if cexpr is not None and (cexpr == '' or
-                                  self.engine.evaluateBoolean(cexpr)):
-            self.no_tag(stuff[-2], stuff[-1])
-        else:
-            self.do_optTag(stuff)
-    bytecode_handlers["optTag"] = do_optTag
-
-    def dumpMacroStack(self, prefix, suffix, value):
-        sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value))
-        for i in range(len(self.macroStack)):
-            what, macroName, slots = self.macroStack[i]
-            sys.stderr.write("| %2d. %-12s %-12s %s\n" %
-                             (i, what, macroName, slots and slots.keys()))
-        sys.stderr.write("+--------------------------------------\n")
-
-    def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)):
-        self._stream_write(s)
-        self.col = col
-        self.do_setPosition(position)
-        if closeprev:
-            engine = self.engine
-            engine.endScope()
-            engine.beginScope()
-        else:
-            self.engine.beginScope()
-            self.scopeLevel = self.scopeLevel + 1
-
-    def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)):
-        self._stream_write(s)
-        self.col = col
-        self.do_setPosition(position)
-        engine = self.engine
-        if closeprev:
-            engine.endScope()
-            engine.beginScope()
-        else:
-            engine.beginScope()
-            self.scopeLevel = self.scopeLevel + 1
-        engine.setLocal("attrs", dict)
-    bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope
-
-    def do_beginScope(self, dict):
-        self.engine.beginScope()
-        self.scopeLevel = self.scopeLevel + 1
-
-    def do_beginScope_tal(self, dict):
-        engine = self.engine
-        engine.beginScope()
-        engine.setLocal("attrs", dict)
-        self.scopeLevel = self.scopeLevel + 1
-    bytecode_handlers["beginScope"] = do_beginScope
-
-    def do_endScope(self, notused=None):
-        self.engine.endScope()
-        self.scopeLevel = self.scopeLevel - 1
-    bytecode_handlers["endScope"] = do_endScope
-
-    def do_setLocal(self, notused):
-        pass
-
-    def do_setLocal_tal(self, (name, expr)):
-        self.engine.setLocal(name, self.engine.evaluateValue(expr))
-    bytecode_handlers["setLocal"] = do_setLocal
-
-    def do_setGlobal_tal(self, (name, expr)):
-        self.engine.setGlobal(name, self.engine.evaluateValue(expr))
-    bytecode_handlers["setGlobal"] = do_setLocal
-
-    def do_insertText(self, stuff):
-        self.interpret(stuff[1])
-
-    def do_insertText_tal(self, stuff):
-        text = self.engine.evaluateText(stuff[0])
-        if text is None:
-            return
-        if text is self.Default:
-            self.interpret(stuff[1])
-            return
-        s = escape(text)
-        self._stream_write(s)
-        i = rfind(s, '\n')
-        if i < 0:
-            self.col = self.col + len(s)
-        else:
-            self.col = len(s) - (i + 1)
-    bytecode_handlers["insertText"] = do_insertText
-
-    def do_insertStructure(self, stuff):
-        self.interpret(stuff[2])
-
-    def do_insertStructure_tal(self, (expr, repldict, block)):
-        structure = self.engine.evaluateStructure(expr)
-        if structure is None:
-            return
-        if structure is self.Default:
-            self.interpret(block)
-            return
-        text = str(structure)
-        if not (repldict or self.strictinsert):
-            # Take a shortcut, no error checking
-            self.stream_write(text)
-            return
-        if self.html:
-            self.insertHTMLStructure(text, repldict)
-        else:
-            self.insertXMLStructure(text, repldict)
-    bytecode_handlers["insertStructure"] = do_insertStructure
-
-    def insertHTMLStructure(self, text, repldict):
-        from HTMLTALParser import HTMLTALParser
-        gen = AltTALGenerator(repldict, self.engine, 0)
-        p = HTMLTALParser(gen) # Raises an exception if text is invalid
-        p.parseString(text)
-        program, macros = p.getCode()
-        self.interpret(program)
-
-    def insertXMLStructure(self, text, repldict):
-        from TALParser import TALParser
-        gen = AltTALGenerator(repldict, self.engine, 0)
-        p = TALParser(gen)
-        gen.enable(0)
-        p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>')
-        gen.enable(1)
-        p.parseFragment(text) # Raises an exception if text is invalid
-        gen.enable(0)
-        p.parseFragment('</foo>', 1)
-        program, macros = gen.getCode()
-        self.interpret(program)
-
-    def do_loop(self, (name, expr, block)):
-        self.interpret(block)
-
-    def do_loop_tal(self, (name, expr, block)):
-        iterator = self.engine.setRepeat(name, expr)
-        while iterator.next():
-            self.interpret(block)
-    bytecode_handlers["loop"] = do_loop
-
-    def do_rawtextColumn(self, (s, col)):
-        self._stream_write(s)
-        self.col = col
-    bytecode_handlers["rawtextColumn"] = do_rawtextColumn
-
-    def do_rawtextOffset(self, (s, offset)):
-        self._stream_write(s)
-        self.col = self.col + offset
-    bytecode_handlers["rawtextOffset"] = do_rawtextOffset
-
-    def do_condition(self, (condition, block)):
-        if not self.tal or self.engine.evaluateBoolean(condition):
-            self.interpret(block)
-    bytecode_handlers["condition"] = do_condition
-
-    def do_defineMacro(self, (macroName, macro)):
-        macs = self.macroStack
-        if len(macs) == 1:
-            entering = macs[-1][2]
-            if not entering:
-                macs.append(None)
-                self.interpret(macro)
-                macs.pop()
-                return
-        self.interpret(macro)
-    bytecode_handlers["defineMacro"] = do_defineMacro
-
-    def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)):
-        if not self.metal:
-            self.interpret(block)
-            return
-        macro = self.engine.evaluateMacro(macroExpr)
-        if macro is self.Default:
-            macro = block
-        else:
-            if not isCurrentVersion(macro):
-                raise METALError("macro %s has incompatible version %s" %
-                                 (`macroName`, `getProgramVersion(macro)`),
-                                 self.position)
-            mode = getProgramMode(macro)
-            if mode != (self.html and "html" or "xml"):
-                raise METALError("macro %s has incompatible mode %s" %
-                                 (`macroName`, `mode`), self.position)
-        self.pushMacro(macroName, compiledSlots)
-        saved_source = self.sourceFile
-        saved_position = self.position  # Used by Boa Constructor
-        self.interpret(macro)
-        if self.sourceFile != saved_source:
-            self.engine.setSourceFile(saved_source)
-            self.sourceFile = saved_source
-        self.popMacro()
-    bytecode_handlers["useMacro"] = do_useMacro
-
-    def do_fillSlot(self, (slotName, block)):
-        # This is only executed if the enclosing 'use-macro' evaluates
-        # to 'default'.
-        self.interpret(block)
-    bytecode_handlers["fillSlot"] = do_fillSlot
-
-    def do_defineSlot(self, (slotName, block)):
-        if not self.metal:
-            self.interpret(block)
-            return
-        macs = self.macroStack
-        if macs and macs[-1] is not None:
-            saved_source = self.sourceFile
-            saved_position = self.position  # Used by Boa Constructor
-            macroName, slots = self.popMacro()[:2]
-            slot = slots.get(slotName)
-            if slot is not None:
-                self.interpret(slot)
-                if self.sourceFile != saved_source:
-                    self.engine.setSourceFile(saved_source)
-                    self.sourceFile = saved_source
-                self.pushMacro(macroName, slots, entering=0)
-                return
-            self.pushMacro(macroName, slots)
-            if len(macs) == 1:
-                self.interpret(block)
-                return
-        self.interpret(block)
-    bytecode_handlers["defineSlot"] = do_defineSlot
-
-    def do_onError(self, (block, handler)):
-        self.interpret(block)
-
-    def do_onError_tal(self, (block, handler)):
-        state = self.saveState()
-        self.stream = stream = StringIO()
-        self._stream_write = stream.write
-        try:
-            self.interpret(block)
-        except:
-            exc = sys.exc_info()[1]
-            self.restoreState(state)
-            engine = self.engine
-            engine.beginScope()
-            error = engine.createErrorInfo(exc, self.position)
-            engine.setLocal('error', error)
-            try:
-                self.interpret(handler)
-            finally:
-                engine.endScope()
-        else:
-            self.restoreOutputState(state)
-            self.stream_write(stream.getvalue())
-    bytecode_handlers["onError"] = do_onError
-
-    bytecode_handlers_tal = bytecode_handlers.copy()
-    bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal
-    bytecode_handlers_tal["beginScope"] = do_beginScope_tal
-    bytecode_handlers_tal["setLocal"] = do_setLocal_tal
-    bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal
-    bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal
-    bytecode_handlers_tal["insertText"] = do_insertText_tal
-    bytecode_handlers_tal["loop"] = do_loop_tal
-    bytecode_handlers_tal["onError"] = do_onError_tal
-    bytecode_handlers_tal["<attrAction>"] = attrAction_tal
-    bytecode_handlers_tal["optTag"] = do_optTag_tal
-
-
-def test():
-    from driver import FILE, parsefile
-    from DummyEngine import DummyEngine
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], "")
-    except getopt.error, msg:
-        print msg
-        sys.exit(2)
-    if args:
-        file = args[0]
-    else:
-        file = FILE
-    doc = parsefile(file)
-    compiler = TALCompiler(doc)
-    program, macros = compiler()
-    engine = DummyEngine()
-    interpreter = TALInterpreter(program, macros, engine)
-    interpreter()
-
-if __name__ == "__main__":
-    test()
diff --git a/TAL/TALParser.py b/TAL/TALParser.py
deleted file mode 100644 (file)
index f75414e..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-##############################################################################
-#
-# 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
-# 
-##############################################################################
-"""
-Parse XML and compile to TALInterpreter intermediate code.
-"""
-
-import string
-from XMLParser import XMLParser
-from TALDefs import *
-from TALGenerator import TALGenerator
-
-class TALParser(XMLParser):
-
-    ordered_attributes = 1
-
-    def __init__(self, gen=None): # Override
-        XMLParser.__init__(self)
-        if gen is None:
-            gen = TALGenerator()
-        self.gen = gen
-        self.nsStack = []
-        self.nsDict = {XML_NS: 'xml'}
-        self.nsNew = []
-
-    def getCode(self):
-        return self.gen.getCode()
-
-    def getWarnings(self):
-        return ()
-
-    def StartNamespaceDeclHandler(self, prefix, uri):
-        self.nsStack.append(self.nsDict.copy())
-        self.nsDict[uri] = prefix
-        self.nsNew.append((prefix, uri))
-
-    def EndNamespaceDeclHandler(self, prefix):
-        self.nsDict = self.nsStack.pop()
-
-    def StartElementHandler(self, name, attrs):
-        if self.ordered_attributes:
-            # attrs is a list of alternating names and values
-            attrlist = []
-            for i in range(0, len(attrs), 2):
-                key = attrs[i]
-                value = attrs[i+1]
-                attrlist.append((key, value))
-        else:
-            # attrs is a dict of {name: value}
-            attrlist = attrs.items()
-            attrlist.sort() # For definiteness
-        name, attrlist, taldict, metaldict = self.process_ns(name, attrlist)
-        attrlist = self.xmlnsattrs() + attrlist
-        self.gen.emitStartElement(name, attrlist, taldict, metaldict)
-
-    def process_ns(self, name, attrlist):
-        taldict = {}
-        metaldict = {}
-        fixedattrlist = []
-        name, namebase, namens = self.fixname(name)
-        for key, value in attrlist:
-            key, keybase, keyns = self.fixname(key)
-            ns = keyns or namens # default to tag namespace
-            item = key, value
-            if ns == 'metal':
-                metaldict[keybase] = value
-                item = item + ("metal",)
-            elif ns == 'tal':
-                taldict[keybase] = value
-                item = item + ("tal",)
-            fixedattrlist.append(item)
-        if namens in ('metal', 'tal'):
-            taldict['tal tag'] = namens
-        return name, fixedattrlist, taldict, metaldict
-
-    def xmlnsattrs(self):
-        newlist = []
-        for prefix, uri in self.nsNew:
-            if prefix:
-                key = "xmlns:" + prefix
-            else:
-                key = "xmlns"
-            if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS):
-                item = (key, uri, "xmlns")
-            else:
-                item = (key, uri)
-            newlist.append(item)
-        self.nsNew = []
-        return newlist
-
-    def fixname(self, name):
-        if ' ' in name:
-            uri, name = string.split(name, ' ')
-            prefix = self.nsDict[uri]
-            prefixed = name
-            if prefix:
-                prefixed = "%s:%s" % (prefix, name)
-            ns = 'x'
-            if uri == ZOPE_TAL_NS:
-                ns = 'tal'
-            elif uri == ZOPE_METAL_NS:
-                ns = 'metal'
-            return (prefixed, name, ns)
-        return (name, name, None)
-
-    def EndElementHandler(self, name):
-        name = self.fixname(name)[0]
-        self.gen.emitEndElement(name)
-
-    def DefaultHandler(self, text):
-        self.gen.emitRawText(text)
-
-def test():
-    import sys
-    p = TALParser()
-    file = "tests/input/test01.xml"
-    if sys.argv[1:]:
-        file = sys.argv[1]
-    p.parseFile(file)
-    program, macros = p.getCode()
-    from TALInterpreter import TALInterpreter
-    from DummyEngine import DummyEngine
-    engine = DummyEngine(macros)
-    TALInterpreter(program, macros, engine, sys.stdout, wrap=0)()
-
-if __name__ == "__main__":
-    test()
diff --git a/TAL/XMLParser.py b/TAL/XMLParser.py
deleted file mode 100644 (file)
index 71a65ab..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-##############################################################################
-#
-# 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 expat-based XML parser base class.
-"""
-
-import zLOG
-
-class XMLParser:
-
-    ordered_attributes = 0
-
-    handler_names = [
-        "StartElementHandler",
-        "EndElementHandler",
-        "ProcessingInstructionHandler",
-        "CharacterDataHandler",
-        "UnparsedEntityDeclHandler",
-        "NotationDeclHandler",
-        "StartNamespaceDeclHandler",
-        "EndNamespaceDeclHandler",
-        "CommentHandler",
-        "StartCdataSectionHandler",
-        "EndCdataSectionHandler",
-        "DefaultHandler",
-        "DefaultHandlerExpand",
-        "NotStandaloneHandler",
-        "ExternalEntityRefHandler",
-        "XmlDeclHandler",
-        "StartDoctypeDeclHandler",
-        "EndDoctypeDeclHandler",
-        "ElementDeclHandler",
-        "AttlistDeclHandler"
-        ]
-
-    def __init__(self, encoding=None):
-        self.parser = p = self.createParser()
-        if self.ordered_attributes:
-            try:
-                self.parser.ordered_attributes = self.ordered_attributes
-            except AttributeError:
-                zLOG.LOG("TAL.XMLParser", zLOG.INFO, 
-                         "Can't set ordered_attributes")
-                self.ordered_attributes = 0
-        for name in self.handler_names:
-            method = getattr(self, name, None)
-            if method is not None:
-                try:
-                    setattr(p, name, method)
-                except AttributeError:
-                    zLOG.LOG("TAL.XMLParser", zLOG.PROBLEM,
-                             "Can't set expat handler %s" % name)
-
-    def createParser(self, encoding=None):
-        global XMLParseError
-        try:
-            from Products.ParsedXML.Expat import pyexpat
-            XMLParseError = pyexpat.ExpatError
-            return pyexpat.ParserCreate(encoding, ' ')
-        except ImportError:
-            from xml.parsers import expat
-            XMLParseError = expat.ExpatError
-            return expat.ParserCreate(encoding, ' ')
-
-    def parseFile(self, filename):
-        self.parseStream(open(filename))
-
-    def parseString(self, s):
-        self.parser.Parse(s, 1)
-
-    def parseURL(self, url):
-        import urllib
-        self.parseStream(urllib.urlopen(url))
-
-    def parseStream(self, stream):
-        self.parser.ParseFile(stream)
-
-    def parseFragment(self, s, end=0):
-        self.parser.Parse(s, end)
diff --git a/TAL/__init__.py b/TAL/__init__.py
deleted file mode 100644 (file)
index 080ed5d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-##############################################################################
-#
-# 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
-# 
-##############################################################################
-""" Template Attribute Language package """
diff --git a/TAL/markupbase.py b/TAL/markupbase.py
deleted file mode 100644 (file)
index eab134c..0000000
+++ /dev/null
@@ -1,306 +0,0 @@
-"""Shared support for scanning document type declarations in HTML and XHTML."""
-
-import re
-import string
-
-_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*').match
-_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match
-
-del re
-
-
-class ParserBase:
-    """Parser base class which provides some common support methods used
-    by the SGML/HTML and XHTML parsers."""
-
-    def reset(self):
-        self.lineno = 1
-        self.offset = 0
-
-    def getpos(self):
-        """Return current line number and offset."""
-        return self.lineno, self.offset
-
-    # Internal -- update line number and offset.  This should be
-    # called for each piece of data exactly once, in order -- in other
-    # words the concatenation of all the input strings to this
-    # function should be exactly the entire input.
-    def updatepos(self, i, j):
-        if i >= j:
-            return j
-        rawdata = self.rawdata
-        nlines = string.count(rawdata, "\n", i, j)
-        if nlines:
-            self.lineno = self.lineno + nlines
-            pos = string.rindex(rawdata, "\n", i, j) # Should not fail
-            self.offset = j-(pos+1)
-        else:
-            self.offset = self.offset + j-i
-        return j
-
-    _decl_otherchars = ''
-
-    # Internal -- parse declaration (for use by subclasses).
-    def parse_declaration(self, i):
-        # This is some sort of declaration; in "HTML as
-        # deployed," this should only be the document type
-        # declaration ("<!DOCTYPE html...>").
-        rawdata = self.rawdata
-        import sys
-        j = i + 2
-        assert rawdata[i:j] == "<!", "unexpected call to parse_declaration"
-        if rawdata[j:j+1] in ("-", ""):
-            # Start of comment followed by buffer boundary,
-            # or just a buffer boundary.
-            return -1
-        # in practice, this should look like: ((name|stringlit) S*)+ '>'
-        n = len(rawdata)
-        decltype, j = self._scan_name(j, i)
-        if j < 0:
-            return j
-        if decltype == "doctype":
-            self._decl_otherchars = ''
-        while j < n:
-            c = rawdata[j]
-            if c == ">":
-                # end of declaration syntax
-                data = rawdata[i+2:j]
-                if decltype == "doctype":
-                    self.handle_decl(data)
-                else:
-                    self.unknown_decl(data)
-                return j + 1
-            if c in "\"'":
-                m = _declstringlit_match(rawdata, j)
-                if not m:
-                    return -1 # incomplete
-                j = m.end()
-            elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
-                name, j = self._scan_name(j, i)
-            elif c in self._decl_otherchars:
-                j = j + 1
-            elif c == "[":
-                if decltype == "doctype":
-                    j = self._parse_doctype_subset(j + 1, i)
-                else:
-                    self.error("unexpected '[' char in declaration")
-            else:
-                self.error(
-                    "unexpected %s char in declaration" % `rawdata[j]`)
-            if j < 0:
-                return j
-        return -1 # incomplete
-
-    # Internal -- scan past the internal subset in a <!DOCTYPE declaration,
-    # returning the index just past any whitespace following the trailing ']'.
-    def _parse_doctype_subset(self, i, declstartpos):
-        rawdata = self.rawdata
-        n = len(rawdata)
-        j = i
-        while j < n:
-            c = rawdata[j]
-            if c == "<":
-                s = rawdata[j:j+2]
-                if s == "<":
-                    # end of buffer; incomplete
-                    return -1
-                if s != "<!":
-                    self.updatepos(declstartpos, j + 1)
-                    self.error("unexpected char in internal subset (in %s)"
-                               % `s`)
-                if (j + 2) == n:
-                    # end of buffer; incomplete
-                    return -1
-                if (j + 4) > n:
-                    # end of buffer; incomplete
-                    return -1
-                if rawdata[j:j+4] == "<!--":
-                    j = self.parse_comment(j, report=0)
-                    if j < 0:
-                        return j
-                    continue
-                name, j = self._scan_name(j + 2, declstartpos)
-                if j == -1:
-                    return -1
-                if name not in ("attlist", "element", "entity", "notation"):
-                    self.updatepos(declstartpos, j + 2)
-                    self.error(
-                        "unknown declaration %s in internal subset" % `name`)
-                # handle the individual names
-                meth = getattr(self, "_parse_doctype_" + name)
-                j = meth(j, declstartpos)
-                if j < 0:
-                    return j
-            elif c == "%":
-                # parameter entity reference
-                if (j + 1) == n:
-                    # end of buffer; incomplete
-                    return -1
-                s, j = self._scan_name(j + 1, declstartpos)
-                if j < 0:
-                    return j
-                if rawdata[j] == ";":
-                    j = j + 1
-            elif c == "]":
-                j = j + 1
-                while j < n and rawdata[j] in string.whitespace:
-                    j = j + 1
-                if j < n:
-                    if rawdata[j] == ">":
-                        return j
-                    self.updatepos(declstartpos, j)
-                    self.error("unexpected char after internal subset")
-                else:
-                    return -1
-            elif c in string.whitespace:
-                j = j + 1
-            else:
-                self.updatepos(declstartpos, j)
-                self.error("unexpected char %s in internal subset" % `c`)
-        # end of buffer reached
-        return -1
-
-    # Internal -- scan past <!ELEMENT declarations
-    def _parse_doctype_element(self, i, declstartpos):
-        rawdata = self.rawdata
-        n = len(rawdata)
-        name, j = self._scan_name(i, declstartpos)
-        if j == -1:
-            return -1
-        # style content model; just skip until '>'
-        if '>' in rawdata[j:]:
-            return string.find(rawdata, ">", j) + 1
-        return -1
-
-    # Internal -- scan past <!ATTLIST declarations
-    def _parse_doctype_attlist(self, i, declstartpos):
-        rawdata = self.rawdata
-        name, j = self._scan_name(i, declstartpos)
-        c = rawdata[j:j+1]
-        if c == "":
-            return -1
-        if c == ">":
-            return j + 1
-        while 1:
-            # scan a series of attribute descriptions; simplified:
-            #   name type [value] [#constraint]
-            name, j = self._scan_name(j, declstartpos)
-            if j < 0:
-                return j
-            c = rawdata[j:j+1]
-            if c == "":
-                return -1
-            if c == "(":
-                # an enumerated type; look for ')'
-                if ")" in rawdata[j:]:
-                    j = string.find(rawdata, ")", j) + 1
-                else:
-                    return -1
-                while rawdata[j:j+1] in string.whitespace:
-                    j = j + 1
-                if not rawdata[j:]:
-                    # end of buffer, incomplete
-                    return -1
-            else:
-                name, j = self._scan_name(j, declstartpos)
-            c = rawdata[j:j+1]
-            if not c:
-                return -1
-            if c in "'\"":
-                m = _declstringlit_match(rawdata, j)
-                if m:
-                    j = m.end()
-                else:
-                    return -1
-                c = rawdata[j:j+1]
-                if not c:
-                    return -1
-            if c == "#":
-                if rawdata[j:] == "#":
-                    # end of buffer
-                    return -1
-                name, j = self._scan_name(j + 1, declstartpos)
-                if j < 0:
-                    return j
-                c = rawdata[j:j+1]
-                if not c:
-                    return -1
-            if c == '>':
-                # all done
-                return j + 1
-
-    # Internal -- scan past <!NOTATION declarations
-    def _parse_doctype_notation(self, i, declstartpos):
-        name, j = self._scan_name(i, declstartpos)
-        if j < 0:
-            return j
-        rawdata = self.rawdata
-        while 1:
-            c = rawdata[j:j+1]
-            if not c:
-                # end of buffer; incomplete
-                return -1
-            if c == '>':
-                return j + 1
-            if c in "'\"":
-                m = _declstringlit_match(rawdata, j)
-                if not m:
-                    return -1
-                j = m.end()
-            else:
-                name, j = self._scan_name(j, declstartpos)
-                if j < 0:
-                    return j
-
-    # Internal -- scan past <!ENTITY declarations
-    def _parse_doctype_entity(self, i, declstartpos):
-        rawdata = self.rawdata
-        if rawdata[i:i+1] == "%":
-            j = i + 1
-            while 1:
-                c = rawdata[j:j+1]
-                if not c:
-                    return -1
-                if c in string.whitespace:
-                    j = j + 1
-                else:
-                    break
-        else:
-            j = i
-        name, j = self._scan_name(j, declstartpos)
-        if j < 0:
-            return j
-        while 1:
-            c = self.rawdata[j:j+1]
-            if not c:
-                return -1
-            if c in "'\"":
-                m = _declstringlit_match(rawdata, j)
-                if m:
-                    j = m.end()
-                else:
-                    return -1    # incomplete
-            elif c == ">":
-                return j + 1
-            else:
-                name, j = self._scan_name(j, declstartpos)
-                if j < 0:
-                    return j
-
-    # Internal -- scan a name token and the new position and the token, or
-    # return -1 if we've reached the end of the buffer.
-    def _scan_name(self, i, declstartpos):
-        rawdata = self.rawdata
-        n = len(rawdata)
-        if i == n:
-            return None, -1
-        m = _declname_match(rawdata, i)
-        if m:
-            s = m.group()
-            name = string.strip(s)
-            if (i + len(s)) == n:
-                return None, -1  # end of buffer
-            return string.lower(name), m.end()
-        else:
-            self.updatepos(declstartpos, i)
-            self.error("expected name token", self.getpos())
index eb6482c3a7c3bcaffb7018aaadd9218891b70d93..c707f7d7022ba0f28c1bb4c8e73b32d98b0293db 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
@@ -49,11 +49,15 @@ pending web: search "refinement"
 pending web: have roundup.cgi pick up instance config from the environment 
 
 New templating TODO:
+. move PageTempalates, TAL, ZTUtils into roundup.cgi and clean up
 . rewritten documentation (can come after the beta though so stuff is settled)
+. modify cgitb to handle PageTemplate errors better
 . add :required to edit action
 active web: title is stoopid
 active hyperdb: full-text searching doesn't appear to match stuff in titles,
                 even though they're supposed to be indexed...
+active web: daemonify roundup-server (fork, logfile, pidfile)
+active web: UNIX init.d script for roundup-server
 
 ongoing: any bugs
 
diff --git a/ZTUtils/.cvsignore b/ZTUtils/.cvsignore
deleted file mode 100644 (file)
index 0d20b64..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.pyc
diff --git a/ZTUtils/Batch.py b/ZTUtils/Batch.py
deleted file mode 100644 (file)
index 88494f8..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-##############################################################################
-#
-# 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__='''Batch class, for iterating over a sequence in batches
-
-$Id: Batch.py,v 1.1 2002-08-30 08:25:33 richard Exp $'''
-__version__='$Revision: 1.1 $'[11:-2]
-
-from ExtensionClass import Base
-
-class LazyPrevBatch(Base):
-    def __of__(self, parent):
-        return Batch(parent._sequence, parent._size,
-                     parent.first - parent._size + parent.overlap, 0,
-                     parent.orphan, parent.overlap)
-
-class LazyNextBatch(Base):
-    def __of__(self, parent):
-        try: parent._sequence[parent.end]
-        except IndexError: return None
-        return Batch(parent._sequence, parent._size,
-                     parent.end - parent.overlap, 0,
-                     parent.orphan, parent.overlap)
-
-class LazySequenceLength(Base):
-    def __of__(self, parent):
-        parent.sequence_length = l = len(parent._sequence)
-        return l
-
-class Batch(Base):
-    """Create a sequence batch"""
-    __allow_access_to_unprotected_subobjects__ = 1
-
-    previous = LazyPrevBatch()
-    next = LazyNextBatch()
-    sequence_length = LazySequenceLength()
-
-    def __init__(self, sequence, size, start=0, end=0,
-                 orphan=0, overlap=0):
-        '''Encapsulate "sequence" in batches of "size".
-
-        Arguments: "start" and "end" are 0-based indexes into the
-        sequence.  If the next batch would contain no more than
-        "orphan" elements, it is combined with the current batch.
-        "overlap" is the number of elements shared by adjacent
-        batches.  If "size" is not specified, it is computed from
-        "start" and "end".  Failing that, it is 7.
-
-        Attributes: Note that the "start" attribute, unlike the
-        argument, is a 1-based index (I know, lame).  "first" is the
-        0-based index.  "length" is the actual number of elements in
-        the batch.
-
-        "sequence_length" is the length of the original, unbatched, sequence
-        '''
-
-        start = start + 1
-
-        start,end,sz = opt(start,end,size,orphan,sequence)
-
-        self._sequence = sequence
-        self.size = sz
-        self._size = size
-        self.start = start
-        self.end = end
-        self.orphan = orphan
-        self.overlap = overlap
-        self.first = max(start - 1, 0)
-        self.length = self.end - self.first
-        if self.first == 0:
-            self.previous = None
-
-
-    def __getitem__(self, index):
-        if index < 0:
-            if index + self.end < self.first: raise IndexError, index
-            return self._sequence[index + self.end]
-        
-        if index >= self.length: raise IndexError, index
-        return self._sequence[index+self.first]
-
-    def __len__(self):
-        return self.length
-
-def opt(start,end,size,orphan,sequence):
-    if size < 1:
-        if start > 0 and end > 0 and end >= start:
-            size=end+1-start
-        else: size=7
-
-    if start > 0:
-
-        try: sequence[start-1]
-        except IndexError: start=len(sequence)
-
-        if end > 0:
-            if end < start: end=start
-        else:
-            end=start+size-1
-            try: sequence[end+orphan-1]
-            except IndexError: end=len(sequence)
-    elif end > 0:
-        try: sequence[end-1]
-        except IndexError: end=len(sequence)
-        start=end+1-size
-        if start - 1 < orphan: start=1
-    else:
-        start=1
-        end=start+size-1
-        try: sequence[end+orphan-1]
-        except IndexError: end=len(sequence)
-    return start,end,size
diff --git a/ZTUtils/CHANGES.txt b/ZTUtils/CHANGES.txt
deleted file mode 100644 (file)
index e2ad284..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-ZTUtils changes
-
-  This file contains change information for the current release. 
-  Change information for previous versions can be found in the
-  file HISTORY.txt.
-
-    Version 1.5
-
-      Features Added
-
-        - Added 'sequence_length' attribute to batches.      
-
-        - Under Python 2.2, Iterator both accepts and produces Python
-          iterator objects.
-
-        - first() and last() methods allow you to tell whether the
-          current element is different from the next or previous
-          element.  This is most useful when the sequence is sorted.
-
-      Bugs Fixed
-
-        - Handle both string and class Unauthorized exceptions.
-
-        - Batch construction masked sequence errors, such as
-          Unauthorized.
-
-        - Orphan defaulted to different values in different places.
-
diff --git a/ZTUtils/HISTORY.txt b/ZTUtils/HISTORY.txt
deleted file mode 100644 (file)
index ee37155..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-ZTUtils history
-
-  This file contains change information for previous versions of
-  ZTUtils. Change information for the current release can be found
-  in the file CHANGES.txt.
-
-
-    Version 1.1.3
-
-      Brown-bag bugfix release.
-
-    Version 1.1.2
-
-      Bugs Fixed
-
-        - Orphans defaulted to 3, which was confusing and out of sync
-          with DTML-In.
-
-        - Orphan batches were broken.
-
-    Version 1.1.1
-
-      Bugs Fixed
-
-        - Python 1.5.2-incompatible changes crept in.
-
-    Version 1.1.0
-
-      Features Added
-
-        - TreeMakers have a setChildAccess() method that you can use
-          to control tree construction.  Child nodes can be accessed
-          through either an attribute name or callback function.
-          Children fetched by attribute name can be filtered through a
-          callback function.
-
-        - A new LazyFilter class allows you to filter a sequence using
-          Zope security and an optional filter callback function.  The
-          security and filter tests are lazy, meaning they are
-          performed as late as possible.  
-
-          The optional 'skip' argument determines the reaction when
-          access to a sequence element is refused by the Zope security
-          policy.  The default (None) is to raise the 'Unauthorized'
-          exception.  If a string is passed, such elements are
-          skipped.  If the string is non-empty, it is treated as a
-          permission name, and the element is skipped if the user
-          doesn't have that permission on the element.
-
-        - The Zope versions of TreeMaker, SimpleTreeMaker, and Batch
-          now use LazyFilter. The TreeMakers have a setSkip() method
-          that can be used to set the 'skip' value. Batch has an
-          optional 'skip_unauthorized' argument that is passed to
-          LazyFilter as 'skip'.
-
-        - Utility functions make_query(), url_query(), and
-          make_hidden_input() have been added.
diff --git a/ZTUtils/Iterator.py b/ZTUtils/Iterator.py
deleted file mode 100644 (file)
index cf3d51c..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-##############################################################################
-#
-# 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__='''Iterator class
-
-Unlike the builtin iterators of Python 2.2+, these classes are
-designed to maintain information about the state of an iteration.
-The Iterator() function accepts either a sequence or a Python
-iterator.  The next() method fetches the next item, and returns
-true if it succeeds.
-
-$Id: Iterator.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
-__version__='$Revision: 1.1 $'[11:-2]
-
-import string
-
-class Iterator:
-    '''Simple Iterator class'''
-
-    __allow_access_to_unprotected_subobjects__ = 1
-
-    nextIndex = 0
-    def __init__(self, seq):
-        self.seq = seq
-        for inner in seqInner, iterInner:
-            if inner._supports(seq):
-                self._inner = inner
-                self._prep_next = inner.prep_next
-                return
-        raise TypeError, "Iterator does not support %s" % `seq`
-
-    def __getattr__(self, name):
-        try:
-            inner = getattr(self._inner, 'it_' + name)
-        except AttributeError:
-            raise AttributeError, name
-        return inner(self)
-
-    def next(self):
-        if not (hasattr(self, '_next') or self._prep_next(self)):
-            return 0
-        self.index = i = self.nextIndex
-        self.nextIndex = i+1
-        self._advance(self)
-        return 1
-
-    def _advance(self, it):
-        self.item = self._next
-        del self._next
-        del self.end
-        self._advance = self._inner.advance
-        self.start = 1
-            
-    def number(self): return self.nextIndex
-
-    def even(self): return not self.index % 2
-
-    def odd(self): return self.index % 2
-
-    def letter(self, base=ord('a'), radix=26):
-        index = self.index
-        s = ''
-        while 1:
-            index, off = divmod(index, radix)
-            s = chr(base + off) + s
-            if not index: return s
-
-    def Letter(self):
-        return self.letter(base=ord('A'))
-
-    def Roman(self, rnvalues=(
-                    (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'),
-                    (100,'C'),(90,'XC'),(50,'L'),(40,'XL'),
-                    (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ):
-        n = self.index + 1
-        s = ''
-        for v, r in rnvalues:
-            rct, n = divmod(n, v)
-            s = s + r * rct
-        return s
-
-    def roman(self, lower=string.lower):
-        return lower(self.Roman())
-
-    def first(self, name=None):
-        if self.start: return 1
-        return not self.same_part(name, self._last, self.item)
-
-    def last(self, name=None):
-        if self.end: return 1
-        return not self.same_part(name, self.item, self._next)
-
-    def same_part(self, name, ob1, ob2):
-        if name is None:
-            return ob1 == ob2
-        no = []
-        return getattr(ob1, name, no) == getattr(ob2, name, no) is not no
-
-    def __iter__(self):
-        return IterIter(self)
-
-class InnerBase:
-    '''Base Inner class for Iterators'''
-    # Prep sets up ._next and .end
-    def prep_next(self, it):
-        it.next = self.no_next
-        it.end = 1
-        return 0
-
-    # Advance knocks them down
-    def advance(self, it):
-        it._last = it.item
-        it.item = it._next
-        del it._next
-        del it.end
-        it.start = 0
-            
-    def no_next(self, it):
-        return 0
-
-    def it_end(self, it):
-        if hasattr(it, '_next'):
-            return 0
-        return not self.prep_next(it)
-
-class SeqInner(InnerBase):
-    '''Inner class for sequence Iterators'''
-
-    def _supports(self, ob):
-        try: ob[0]
-        except TypeError: return 0
-        except: pass
-        return 1
-
-    def prep_next(self, it):
-        i = it.nextIndex
-        try:
-            it._next = it.seq[i]
-        except IndexError:
-            it._prep_next = self.no_next
-            it.end = 1
-            return 0
-        it.end = 0
-        return 1
-
-    def it_length(self, it):
-        it.length = l = len(it.seq)
-        return l
-
-try:
-    StopIteration=StopIteration
-except NameError:
-    StopIteration="StopIteration"
-
-class IterInner(InnerBase):
-    '''Iterator inner class for Python iterators'''
-
-    def _supports(self, ob):
-        try:
-            if hasattr(ob, 'next') and (ob is iter(ob)):
-                return 1
-        except:
-            return 0
-
-    def prep_next(self, it):
-        try:
-            it._next = it.seq.next()
-        except StopIteration:
-            it._prep_next = self.no_next
-            it.end = 1
-            return 0
-        it.end = 0
-        return 1
-
-class IterIter:
-    def __init__(self, it):
-        self.it = it
-        self.skip = it.nextIndex > 0 and not it.end
-    def next(self):
-        it = self.it
-        if self.skip:
-            self.skip = 0
-            return it.item
-        if it.next():
-            return it.item
-        raise StopIteration
-
-seqInner = SeqInner()
-iterInner = IterInner()
diff --git a/ZTUtils/SimpleTree.py b/ZTUtils/SimpleTree.py
deleted file mode 100644 (file)
index ca32073..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-##############################################################################
-#
-# 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__='''Simple Tree classes
-
-$Id: SimpleTree.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
-__version__='$Revision: 1.1 $'[11:-2]
-
-from Tree import TreeMaker, TreeNode, b2a
-
-class SimpleTreeNode(TreeNode):
-    def branch(self):
-        if self.state == 0:
-            return {'link': None, 'img': '&nbsp;&nbsp;'}
-
-        if self.state < 0:
-            setst = 'expand'
-            exnum = self.aq_parent.expansion_number
-            img = 'pl'
-        else:
-            setst = 'collapse'
-            exnum = self.expansion_number
-            img = 'mi'
-
-        base = self.aq_acquire('baseURL')
-        obid = self.id
-        pre = self.aq_acquire('tree_pre')
-
-        return {'link': '?%s-setstate=%s,%s,%s#%s' % (pre, setst[0],
-                                                      exnum, obid, obid),
-        'img': '<img src="%s/p_/%s" alt="%s" border="0">' % (base, img, setst)}
-        
-
-class SimpleTreeMaker(TreeMaker):
-    '''Generate Simple Trees'''
-
-    def __init__(self, tree_pre="tree"):
-        self.tree_pre = tree_pre
-
-    def node(self, object):
-        node = SimpleTreeNode()
-        node.object = object
-        node.id = b2a(self.getId(object))
-        return node
-
-    def markRoot(self, node):
-        node.tree_pre = self.tree_pre
-        node.baseURL = node.object.REQUEST['BASEPATH1']
diff --git a/ZTUtils/Tree.py b/ZTUtils/Tree.py
deleted file mode 100644 (file)
index ea0b562..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-##############################################################################
-#
-# 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__='''Tree manipulation classes
-
-$Id: Tree.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
-__version__='$Revision: 1.1 $'[11:-2]
-
-from Acquisition import Explicit
-from ComputedAttribute import ComputedAttribute
-
-class TreeNode(Explicit):
-    __allow_access_to_unprotected_subobjects__ = 1
-    state = 0 # leaf
-    height = 1
-    size = 1
-    def __init__(self):
-        self._child_list = []
-    def _add_child(self, child):
-        'Add a child which already has all of its children.'
-        self._child_list.append(child)
-        self.height = max(self.height, child.height + 1)
-        self.size = self.size + child.size
-    def flat(self):
-        'Return a flattened preorder list of tree nodes'
-        items = []
-        self.walk(items.append)
-        return items
-    def walk(self, f, data=None):
-        'Preorder walk this tree, passing each node to a function'
-        if data is None:
-            f(self)
-        else:
-            f(self, data)
-        for child in self._child_list:
-            child.__of__(self).walk(f, data)
-    def _depth(self):
-        return self.aq_parent.depth + 1
-    depth = ComputedAttribute(_depth, 1)
-    def __getitem__(self, index):
-        return self._child_list[index].__of__(self)
-    def __len__(self):
-        return len(self._child_list)
-
-_marker = [] 
-        
-class TreeMaker:
-    '''Class for mapping a hierachy of objects into a tree of nodes.'''
-
-    __allow_access_to_unprotected_subobjects__ = 1
-
-    _id = 'tpId'
-    _values = 'tpValues'
-    _assume_children = 0
-    _values_filter = None
-    _values_function = None
-    _expand_root = 1
-
-    def setChildAccess(self, attrname=_marker, filter=_marker,
-                       function=_marker):
-        '''Set the criteria for fetching child nodes.
-
-        Child nodes can be accessed through either an attribute name
-        or callback function.  Children fetched by attribute name can
-        be filtered through a callback function.
-        '''
-        if function is _marker:
-            self._values_function = None
-            if attrname is not _marker:
-                self._values = str(attrname)
-            if filter is not _marker:
-                self._values_filter = filter
-        else:
-            self._values_function = function
-    
-    def tree(self, root, expanded=None, subtree=0):
-        '''Create a tree from root, with specified nodes expanded.
-
-        "expanded" must be false, true, or a mapping.
-        Each key of the mapping is the id of a top-level expanded
-        node, and each value is the "expanded" value for the
-        children of that node.
-        '''
-        node = self.node(root)
-        child_exp = expanded
-        if not simple_type(expanded):
-            # Assume a mapping
-            expanded = expanded.has_key(node.id)
-            child_exp = child_exp.get(node.id)
-        if expanded or (not subtree and self._expand_root):
-            children = self.getChildren(root)
-            if children:
-                node.state = 1 # expanded
-                for child in children:
-                    node._add_child(self.tree(child, child_exp, 1))
-        elif self.hasChildren(root):
-            node.state = -1 # collapsed
-        if not subtree:
-            node.depth = 0
-            if hasattr(self, 'markRoot'):
-                self.markRoot(node)
-        return node
-
-    def node(self, object):
-        node = TreeNode()
-        node.object = object
-        node.id = b2a(self.getId(object))
-        return node
-    
-    def getId(self, object):
-        id_attr = self._id
-        if hasattr(object, id_attr):
-            obid = getattr(object, id_attr)
-            if not simple_type(obid): obid = obid()
-            return obid
-        if hasattr(object, '_p_oid'): return str(object._p_oid)
-        return id(object)
-
-    def hasChildren(self, object):
-        if self._assume_children:
-            return 1
-        return self.getChildren(object)
-
-    def getChildren(self, object):
-        if self._values_function is not None:
-            return self._values_function(object)
-        if self._values_filter and hasattr(object, 'aq_acquire'):
-            return object.aq_acquire(self._values, aqcallback,
-                                     self._values_filter)()
-        return getattr(object, self._values)()
-
-def simple_type(ob,
-                is_simple={type(''):1, type(0):1, type(0.0):1,
-                           type(0L):1, type(None):1 }.has_key):
-    return is_simple(type(ob))
-
-def aqcallback(self, inst, parent, name, value, filter):
-    return filter(self, inst, parent, name, value)
-
-from binascii import b2a_base64, a2b_base64
-import string
-from string import split, join, translate
-
-a2u_map = string.maketrans('+/=', '-._')
-u2a_map = string.maketrans('-._', '+/=')
-
-def b2a(s):
-    '''Encode a value as a cookie- and url-safe string.
-
-    Encoded string use only alpahnumeric characters, and "._-".
-    '''
-    s = str(s)
-    if len(s) <= 57:
-        return translate(b2a_base64(s)[:-1], a2u_map)
-    frags = []
-    for i in range(0, len(s), 57):
-        frags.append(b2a_base64(s[i:i + 57])[:-1])
-    return translate(join(frags, ''), a2u_map)
-
-def a2b(s):
-    '''Decode a b2a-encoded string.'''
-    s = translate(s, u2a_map)
-    if len(s) <= 76:
-        return a2b_base64(s)
-    frags = []
-    for i in range(0, len(s), 76):
-        frags.append(a2b_base64(s[i:i + 76]))
-    return join(frags, '')
-
-def encodeExpansion(nodes):
-    '''Encode the expanded node ids of a tree into a string.
-
-    Accepts a list of nodes, such as that produced by root.flat().
-    Marks each expanded node with an expansion_number attribute.
-    Since node ids are encoded, the resulting string is safe for
-    use in cookies and URLs.
-    '''
-    steps = []
-    last_depth = -1
-    n = 0
-    for node in nodes:
-        if node.state <=0: continue
-        dd = last_depth - node.depth + 1
-        last_depth = node.depth
-        if dd > 0:
-            steps.append('.' * dd)
-        steps.append(node.id)
-        node.expansion_number = n
-        n = n + 1
-    return join(steps, ':')
-        
-def decodeExpansion(s, nth=None):
-    '''Decode an expanded node map from a string.
-
-    If nth is an integer, also return the (map, key) pair for the nth entry.
-    '''
-    map = m = {}
-    mstack = []
-    pop = 0
-    nth_pair = None
-    if nth is not None:
-        nth_pair = (None, None)
-    for step in split(s, ':'):
-        if step[:1] == '.':
-            pop = len(step) - 1
-            continue
-        if pop < 0:
-            mstack.append(m)
-            m[obid] = {}
-            m = m[obid]
-        elif map:
-            m[obid] = None
-        if len(step) == 0:
-            return map
-        obid = step
-        if pop > 0:
-            m = mstack[-pop]
-            del mstack[-pop:]
-        pop = -1
-        if nth == 0:
-            nth_pair = (m, obid)
-            nth = None
-        elif nth is not None:
-            nth = nth - 1
-    m[obid] = None
-    if nth == 0:
-        return map, (m, obid)
-    if nth_pair is not None:
-        return map, nth_pair
-    return map
-
diff --git a/ZTUtils/Zope.py b/ZTUtils/Zope.py
deleted file mode 100644 (file)
index 1f84078..0000000
+++ /dev/null
@@ -1,302 +0,0 @@
-##############################################################################
-#
-# 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__='''Zope-specific versions of ZTUTils classes
-
-$Id: Zope.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
-__version__='$Revision: 1.1 $'[11:-2]
-
-import sys, cgi, urllib, cgi
-from Tree import encodeExpansion, decodeExpansion, TreeMaker
-from SimpleTree import SimpleTreeMaker
-from Batch import Batch
-from Products.ZCatalog.Lazy import Lazy
-from AccessControl import getSecurityManager
-from string import split, join
-from types import StringType, ListType, IntType, FloatType
-from DateTime import DateTime
-
-try:
-    from AccessControl.ZopeGuards import guarded_getitem
-except ImportError:
-    Unauthorized = 'Unauthorized'
-    def guarded_getitem(object, index):
-        v = object[index]
-        if getSecurityManager().validate(object, object, index, v):
-            return v
-        raise Unauthorized, 'unauthorized access to element %s' % `i`
-else:
-    from AccessControl import Unauthorized
-
-class LazyFilter(Lazy):
-    # A LazyFilter that checks with the security policy
-
-    def __init__(self, seq, test=None, skip=None):
-        self._seq=seq
-        self._data=[]
-        self._eindex=-1
-        self._test=test
-        if not (skip is None or str(skip) == skip): 
-            raise TypeError, 'Skip must be None or a string'
-        self._skip = skip 
-
-    def __getitem__(self,index):
-        data=self._data
-        try: s=self._seq
-        except AttributeError: return data[index]
-
-        i=index
-        if i < 0: i=len(self)+i
-        if i < 0: raise IndexError, index
-
-        ind=len(data)
-        if i < ind: return data[i]
-        ind=ind-1
-
-        test=self._test
-        e=self._eindex
-        skip = self._skip
-        while i > ind:
-            e = e + 1
-            try:
-                try: v = guarded_getitem(s, e)
-                except Unauthorized, vv:
-                    if skip is None:
-                        self._eindex = e
-                        msg = '(item %s): %s' % (index, vv)
-                        raise Unauthorized, msg, sys.exc_info()[2]
-                    skip_this = 1
-                else:
-                    skip_this = 0
-            except IndexError:
-                del self._test
-                del self._seq
-                del self._eindex
-                raise IndexError, index
-            if skip_this: continue
-            if skip and not getSecurityManager().checkPermission(skip, v):
-                continue
-            if test is None or test(v):
-                data.append(v)
-                ind=ind+1
-        self._eindex=e
-        return data[i]
-
-class TreeSkipMixin:
-    '''Mixin class to make trees test security, and allow
-    skipping of unauthorized objects. '''
-    skip = None
-    def setSkip(self, skip):
-        self.skip = skip
-        return self
-    def getChildren(self, object):
-        return LazyFilter(self._getChildren(object), skip=self.skip)
-
-class TreeMaker(TreeSkipMixin, TreeMaker):
-    _getChildren = TreeMaker.getChildren
-
-class SimpleTreeMaker(TreeSkipMixin, SimpleTreeMaker):
-    _getChildren = SimpleTreeMaker.getChildren
-    def cookieTree(self, root_object, default_state=None):
-        '''Make a tree with state stored in a cookie.'''
-        tree_pre = self.tree_pre
-        state_name = '%s-state' % tree_pre
-        set_name = '%s-setstate' % tree_pre
-
-        req = root_object.REQUEST
-        state = req.get(state_name)
-        if state:
-            setst = req.form.get(set_name)
-            if setst:
-                st, pn, expid = split(setst, ',')
-                state, (m, obid) = decodeExpansion(state, int(pn))
-                if m is None:
-                    pass
-                elif st == 'e':
-                    if m[obid] is None:
-                        m[obid] = {expid: None}
-                    else:
-                        m[obid][expid] = None
-                elif st == 'c' and m is not state and obid==expid:
-                    del m[obid]
-            else:
-                state = decodeExpansion(state)
-        else:
-            state = default_state
-        tree = self.tree(root_object, state)
-        rows = tree.flat()
-        req.RESPONSE.setCookie(state_name, encodeExpansion(rows))
-        return tree, rows
-
-# Make the Batch class test security, and let it skip unauthorized.
-_Batch = Batch
-class Batch(Batch):
-    def __init__(self, sequence, size, start=0, end=0,
-                 orphan=0, overlap=0, skip_unauthorized=None):
-        sequence = LazyFilter(sequence, skip=skip_unauthorized)
-        _Batch.__init__(self, sequence, size, start, end,
-                        orphan, overlap)
-
-# These functions are meant to be used together in templates that use
-# trees or batches.  For example, given a batch with a 'bstart' query
-# argument, you would use "url_query(request, omit='bstart')" to get
-# the base for the batching links, then append 
-# "make_query(bstart=batch.previous.first)" to one and
-# "make_query(bstart=batch.end)" to the other.
-
-def make_query(*args, **kwargs):
-    '''Construct a URL query string, with marshalling markup.
-
-    If there are positional arguments, they must be dictionaries.
-    They are combined with the dictionary of keyword arguments to form
-    a dictionary of query names and values.
-
-    Query names (the keys) must be strings.  Values may be strings,
-    integers, floats, or DateTimes, and they may also be lists or
-    namespaces containing these types.  Names and string values
-    should not be URL-quoted.  All arguments are marshalled with
-    complex_marshal().
-    '''
-
-    d = {}
-    for arg in args:
-        d.update(arg)
-    d.update(kwargs)
-
-    uq = urllib.quote
-    qlist = complex_marshal(d.items())
-    for i in range(len(qlist)):
-        k, m, v = qlist[i]
-        qlist[i] = '%s%s=%s' % (uq(k), m, uq(str(v)))
-
-    return join(qlist, '&')
-                
-def make_hidden_input(*args, **kwargs):
-    '''Construct a set of hidden input elements, with marshalling markup.
-
-    If there are positional arguments, they must be dictionaries.
-    They are combined with the dictionary of keyword arguments to form
-    a dictionary of query names and values.
-
-    Query names (the keys) must be strings.  Values may be strings,
-    integers, floats, or DateTimes, and they may also be lists or
-    namespaces containing these types.  All arguments are marshalled with
-    complex_marshal().
-    '''
-
-    d = {}
-    for arg in args:
-        d.update(arg)
-    d.update(kwargs)
-
-    hq = cgi.escape
-    qlist = complex_marshal(d.items())
-    for i in range(len(qlist)):
-        k, m, v = qlist[i]
-        qlist[i] = ('<input type="hidden" name="%s%s" value="%s">'
-                    % (hq(k), m, hq(str(v))))
-
-    return join(qlist, '\n')
-                
-def complex_marshal(pairs):
-    '''Add request marshalling information to a list of name-value pairs.
-
-    Names must be strings.  Values may be strings,
-    integers, floats, or DateTimes, and they may also be lists or
-    namespaces containing these types.
-
-    The list is edited in place so that each (name, value) pair
-    becomes a (name, marshal, value) triple.  The middle value is the
-    request marshalling string.  Integer, float, and DateTime values
-    will have ":int", ":float", or ":date" as their marshal string.
-    Lists will be flattened, and the elements given ":list" in
-    addition to their simple marshal string.  Dictionaries will be
-    flattened and marshalled using ":record".
-    '''
-    i = len(pairs)
-    while i > 0:
-        i = i - 1
-        k, v = pairs[i]
-        m = ''
-        sublist = None
-        if isinstance(v, StringType):
-            pass
-        elif hasattr(v, 'items'):
-            sublist = []
-            for sk, sv in v.items():
-                sm = simple_marshal(sv)
-                sublist.append(('%s.%s' % (k, sk), '%s:record' % sm,  sv))
-        elif isinstance(v, ListType):
-            sublist = []
-            for sv in v:
-                sm = simple_marshal(sv)
-                sublist.append((k, '%s:list' % sm, sv))
-        else:
-            m = simple_marshal(v)
-        if sublist is None:
-            pairs[i] = (k, m, v)
-        else:
-            pairs[i:i + 1] = sublist
-
-    return pairs
-
-def simple_marshal(v):
-    if isinstance(v, StringType):
-        return ''
-    if isinstance(v, IntType):
-        return ':int'
-    if isinstance(v, FloatType):
-        return ':float'
-    if isinstance(v, DateTime):
-        return ':date'
-    return ''
-
-def url_query(request, req_name="URL", omit=None):
-    '''Construct a URL with a query string, using the current request.
-
-    request: the request object
-    req_name: the name, such as "URL1" or "BASEPATH1", to get from request
-    omit: sequence of name of query arguments to omit.  If a name
-    contains a colon, it is treated literally.  Otherwise, it will
-    match each argument name that starts with the name and a period or colon. 
-    '''
-
-    base = request[req_name]
-    qs = request.get('QUERY_STRING', '')
-    
-    if qs and omit:
-        qsparts = split(qs, '&')
-
-        if isinstance(omit, StringType):
-            omits = {omit: None}
-        else:
-            omits = {}
-            for name in omit:
-                omits[name] = None
-        omitted = omits.has_key
-
-        unq = urllib.unquote
-        for i in range(len(qsparts)):
-            name = unq(split(qsparts[i], '=', 1)[0])
-            if omitted(name):
-                qsparts[i] = ''
-            name = split(name, ':', 1)[0]
-            if omitted(name):
-                qsparts[i] = ''
-            name = split(name, '.', 1)[0]
-            if omitted(name):
-                qsparts[i] = ''
-            
-        qs = join(filter(None, qsparts), '&')
-
-    # We alway append '?' since arguments will be appended to the URL
-    return '%s?%s' % (base, qs)
diff --git a/ZTUtils/__init__.py b/ZTUtils/__init__.py
deleted file mode 100644 (file)
index 133f817..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-##############################################################################
-#
-# 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 of template utility classes and functions.
-
-$Id: __init__.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
-__version__='$Revision: 1.1 $'[11:-2]
-
-from Batch import Batch
-from Iterator import Iterator
-from Tree import TreeMaker, encodeExpansion, decodeExpansion, a2b, b2a
-from SimpleTree import SimpleTreeMaker
-
-import sys
-if sys.modules.has_key('Zope'):
-    del sys
-    __allow_access_to_unprotected_subobjects__ = 1
-    __roles__ = None
-
-    from Zope import Batch, TreeMaker, SimpleTreeMaker, LazyFilter
-    from Zope import url_query, make_query, make_hidden_input
-
index f98e7766d5d1c8bc2843291518d35e28bdd0cc49..5ab4af2eb123c4d287aae3e74f03664e5ffac02e 100644 (file)
@@ -2,7 +2,7 @@
 Installing Roundup
 ==================
 
-:Version: $Revision: 1.19 $
+:Version: $Revision: 1.20 $
 
 .. contents::
 
@@ -85,7 +85,21 @@ Installation
 ============
 
 Set aside 15-30 minutes. Please make sure you're using a supported version of
-Python -- see `testing your python`_.
+Python -- see `testing your python`_. There's three sections to this
+installation guide:
+
+1. `basic installation steps`_ that all installers must follow
+2. `shared environment steps`_ to take if you're installing on a shared
+    UNIX machine and want to restrict local access to roundup
+3. `internet setup`_ steps to take if your tracker is to be used by the wider
+    internet community
+
+Most users will only need to follow the first step, since the environment will
+be a trusted one.
+
+
+Basic Installation Steps
+~~~~~~~~~~~~~~~~~~~~~~~~
 
 1. To install the Roundup support code into your Python tree and
    Roundup scripts into /usr/local/bin::
@@ -139,32 +153,47 @@ Python -- see `testing your python`_.
 
       Once this is done, the instance has been created.
 
-3. Each instance ideally should have its own UNIX group, so create
-   a UNIX group (edit ``/etc/group`` or your appropriate NIS map if
-   you're using NIS).  To continue with my examples so far, I would
-   create the UNIX group 'support', although the name of the UNIX
-   group does not have to be the same as the instance name.  To this
-   'support' group I then add all of the UNIX usernames who will be
-   working with this Roundup instance.  In addition to 'real' users,
-   the Roundup email gateway will need to have permissions to this
-   area as well, so add the user your mail service runs as to the
-   group.  The UNIX group might then look like::
+3. XXX Set up the CGI interface
+
+4. XXX Set up the mail gateway
+
 
-        support:*:1002:jblaine,samh,geezer,mail
+Shared Environment Steps
+~~~~~~~~~~~~~~~~~~~~~~~~
 
-   If you intend to use the web interface (as most people do), you
-   should also add the username your web server runs as to the group.
-   My group now looks like this::
+Each instance ideally should have its own UNIX group, so create
+a UNIX group (edit ``/etc/group`` or your appropriate NIS map if
+you're using NIS).  To continue with my examples so far, I would
+create the UNIX group 'support', although the name of the UNIX
+group does not have to be the same as the instance name.  To this
+'support' group I then add all of the UNIX usernames who will be
+working with this Roundup instance.  In addition to 'real' users,
+the Roundup email gateway will need to have permissions to this
+area as well, so add the user your mail service runs as to the
+group.  The UNIX group might then look like::
 
-        support:*:1002:jblaine,samh,geezer,mail,apache
+     support:*:1002:jblaine,samh,geezer,mail
 
-4. Configure your new instance by editing the file ``instance_config.py``
-   located in the instance home you specified in step 2c above.  This
-   file is Python code and must adhere to Python syntax rules, but
-   don't be daunted if you do not know Python - it should look pretty
-   straightfoward if you carefully read the comments in the file.
+If you intend to use the web interface (as most people do), you
+should also add the username your web server runs as to the group.
+My group now looks like this::
 
-5. There are two supported ways to get emailed issues into the
+     support:*:1002:jblaine,samh,geezer,mail,apache
+
+An alternative to the above is to create a new user who has the sole
+responsibility of running roundup. This user:
+
+1. runs the CGI interface daemon
+2. runs regular polls for email
+3. runs regular checks (using cron) to ensure the daemon is up
+4. optionally has no login password so that nobody but the "root" user
+   may actually login and play with the roundup setup.
+
+
+Internet Setup
+~~~~~~~~~~~~~~
+
+1. There are two supported ways to get emailed issues into the
    Roundup instance.  You should pick ONE of the following, both
    of which will continue my example setup from above:
 
@@ -185,11 +214,14 @@ Python -- see `testing your python`_.
    If you don't want to use the email component of Roundup, then remove the
    "``nosyreator.py``" module from your instance "``detectors``" directory.
 
-6. Test the email gateway.  Under most flavors of UNIX, this
+2. Test the email gateway.  Under most flavors of UNIX, this
    can be done by::
 
      echo test | mail -s '[issue] test' support@YOUR_DOMAIN_HERE
 
+XXX mention HTTPS
+XXX mention Basic vs. cookie auth
+
 
 Upgrading
 =========
index 4cdc41dfc3f8be2741fb7ddcf346c501125e899e..b9e33a1004954adad3da569f413157c9fc729bda 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_anydbm.py,v 1.71 2002-09-04 07:12:19 richard Exp $
+#$Id: back_anydbm.py,v 1.72 2002-09-05 00:33:22 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
@@ -803,7 +803,7 @@ class Class(hyperdb.Class):
                 l = []
                 for entry in value:
                     if type(entry) != type(''):
-                        raise ValueError, '"%s" multilink value (%r) '
+                        raise ValueError, '"%s" multilink value (%r) '\
                             'must contain Strings'%(key, value)
                     # if it isn't a number, it's a key
                     if not num_re.match(entry):
@@ -1909,6 +1909,9 @@ class IssueClass(Class, roundupdb.IssueClass):
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.71  2002/09/04 07:12:19  richard
+#better error message
+#
 #Revision 1.70  2002/09/04 04:29:36  richard
 #bugfix
 #
index c5d51333b0a214a166c2132fd42eb0222cb0f113..ad156d23af769b9d8aa0c062e066aa256f760cde 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: back_gadfly.py,v 1.13 2002-09-04 07:12:19 richard Exp $
+# $Id: back_gadfly.py,v 1.14 2002-09-05 00:33:22 richard Exp $
 __doc__ = '''
 About Gadfly
 ============
@@ -923,7 +923,7 @@ class Class(hyperdb.Class):
                 l = []
                 for entry in value:
                     if type(entry) != type(''):
-                        raise ValueError, '"%s" multilink value (%r) '
+                        raise ValueError, '"%s" multilink value (%r) '\
                             'must contain Strings'%(key, value)
                     # if it isn't a number, it's a key
                     if not num_re.match(entry):
@@ -1758,6 +1758,9 @@ class IssueClass(Class, roundupdb.IssueClass):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.13  2002/09/04 07:12:19  richard
+# better error message
+#
 # Revision 1.12  2002/09/04 04:30:18  richard
 # bugfix
 #
diff --git a/roundup/cgi/PageTemplates/.cvsignore b/roundup/cgi/PageTemplates/.cvsignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/roundup/cgi/PageTemplates/ComputedAttribute.py b/roundup/cgi/PageTemplates/ComputedAttribute.py
new file mode 100644 (file)
index 0000000..7117fb4
--- /dev/null
@@ -0,0 +1,11 @@
+class ComputedAttribute:
+    def __init__(self, callable, level):
+        self.callable = callable
+        self.level = level
+    def __of__(self, *args):
+        if self.level > 0:
+            return self.callable
+        if isinstance(self.callable, type('')):
+            return getattr(args[0], self.callable)
+        return self.callable(*args)
+
diff --git a/roundup/cgi/PageTemplates/Expressions.py b/roundup/cgi/PageTemplates/Expressions.py
new file mode 100644 (file)
index 0000000..105da31
--- /dev/null
@@ -0,0 +1,315 @@
+##############################################################################
+#
+# 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
+
+_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)
+
+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]
+
+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 = 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()
+        path.pop(0)
+
+    path.reverse()
+    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
+
+        # Try an attribute.
+        o = get(object, name, M)
+#       print '...', (object, name, M, o)
+        if o is M:
+            # Try an item.
+#           print '... 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.
+                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
+        #print '... object is now', `o`
+        object = o
+
+    return object
+
diff --git a/roundup/cgi/PageTemplates/MultiMapping.py b/roundup/cgi/PageTemplates/MultiMapping.py
new file mode 100644 (file)
index 0000000..b528288
--- /dev/null
@@ -0,0 +1,25 @@
+import operator
+
+class MultiMapping:
+    def __init__(self, *stores):
+        self.stores = list(stores)
+    def __getitem__(self, key):
+        for store in self.stores:
+            if store.has_key(key):
+                return store[key]
+        raise KeyError, key
+    _marker = []
+    def get(self, key, default=_marker):
+        for store in self.stores:
+            if store.has_key(key):
+                return store[key]
+        if default is self._marker:
+            raise KeyError, key
+        return default
+    def __len__(self):
+        return reduce(operator.add, [len(x) for x in stores], 0)
+    def push(self, store):
+        self.stores.append(store)
+    def pop(self):
+        return self.stores.pop()
+
diff --git a/roundup/cgi/PageTemplates/PageTemplate.py b/roundup/cgi/PageTemplates/PageTemplate.py
new file mode 100755 (executable)
index 0000000..d30aa64
--- /dev/null
@@ -0,0 +1,205 @@
+##############################################################################
+#
+# 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 roundup.cgi.TAL.TALParser import TALParser
+from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser
+from roundup.cgi.TAL.TALGenerator import TALGenerator
+from roundup.cgi.TAL.TALInterpreter import TALInterpreter
+from Expressions import getEngine
+from string import join, strip, rstrip, split, replace, lower, find
+from cStringIO import StringIO
+from ComputedAttribute import ComputedAttribute
+
+class PageTemplate:
+    "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/roundup/cgi/PageTemplates/PathIterator.py b/roundup/cgi/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/roundup/cgi/PageTemplates/PythonExpr.py b/roundup/cgi/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/roundup/cgi/PageTemplates/README.txt b/roundup/cgi/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/roundup/cgi/PageTemplates/TALES.py b/roundup/cgi/PageTemplates/TALES.py
new file mode 100644 (file)
index 0000000..33b6e9f
--- /dev/null
@@ -0,0 +1,286 @@
+##############################################################################
+#
+# 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
+from roundup.cgi import 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/roundup/cgi/PageTemplates/__init__.py b/roundup/cgi/PageTemplates/__init__.py
new file mode 100644 (file)
index 0000000..3d58de0
--- /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-09-05 00:37:09 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)
diff --git a/roundup/cgi/TAL/.cvsignore b/roundup/cgi/TAL/.cvsignore
new file mode 100644 (file)
index 0000000..0cd1d94
--- /dev/null
@@ -0,0 +1,2 @@
+.path
+*.pyc
diff --git a/roundup/cgi/TAL/HTMLParser.py b/roundup/cgi/TAL/HTMLParser.py
new file mode 100644 (file)
index 0000000..5ab076b
--- /dev/null
@@ -0,0 +1,403 @@
+"""A parser for HTML and XHTML."""
+
+# This file is based on sgmllib.py, but the API is slightly different.
+
+# XXX There should be a way to distinguish between PCDATA (parsed
+# character data -- the normal case), RCDATA (replaceable character
+# data -- only char and entity references and end tags are special)
+# and CDATA (character data -- only end tags are special).
+
+
+import markupbase
+import re
+import string
+
+# Regular expressions used for parsing
+
+interesting_normal = re.compile('[&<]')
+interesting_cdata = re.compile(r'<(/|\Z)')
+incomplete = re.compile('&[a-zA-Z#]')
+
+entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
+charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
+
+starttagopen = re.compile('<[a-zA-Z]')
+piclose = re.compile('>')
+endtagopen = re.compile('</')
+commentclose = re.compile(r'--\s*>')
+tagfind = re.compile('[a-zA-Z][-.a-zA-Z0-9:_]*')
+attrfind = re.compile(
+    r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*'
+    r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./:;+*%?!&$\(\)_#=~]*))?')
+
+locatestarttagend = re.compile(r"""
+  <[a-zA-Z][-.a-zA-Z0-9:_]*          # tag name
+  (?:\s+                             # whitespace before attribute name
+    (?:[a-zA-Z_][-.:a-zA-Z0-9_]*     # attribute name
+      (?:\s*=\s*                     # value indicator
+        (?:'[^']*'                   # LITA-enclosed value
+          |\"[^\"]*\"                # LIT-enclosed value
+          |[^'\">\s]+                # bare value
+         )
+       )?
+     )
+   )*
+  \s*                                # trailing whitespace
+""", re.VERBOSE)
+endendtag = re.compile('>')
+endtagfind = re.compile('</\s*([a-zA-Z][-.a-zA-Z0-9:_]*)\s*>')
+
+
+class HTMLParseError(Exception):
+    """Exception raised for all parse errors."""
+
+    def __init__(self, msg, position=(None, None)):
+        assert msg
+        self.msg = msg
+        self.lineno = position[0]
+        self.offset = position[1]
+
+    def __str__(self):
+        result = self.msg
+        if self.lineno is not None:
+            result = result + ", at line %d" % self.lineno
+        if self.offset is not None:
+            result = result + ", column %d" % (self.offset + 1)
+        return result
+
+
+def _contains_at(s, sub, pos):
+    return s[pos:pos+len(sub)] == sub
+
+
+class HTMLParser(markupbase.ParserBase):
+    """Find tags and other markup and call handler functions.
+
+    Usage:
+        p = HTMLParser()
+        p.feed(data)
+        ...
+        p.close()
+
+    Start tags are handled by calling self.handle_starttag() or
+    self.handle_startendtag(); end tags by self.handle_endtag().  The
+    data between tags is passed from the parser to the derived class
+    by calling self.handle_data() with the data as argument (the data
+    may be split up in arbitrary chunks).  Entity references are
+    passed by calling self.handle_entityref() with the entity
+    reference as the argument.  Numeric character references are
+    passed to self.handle_charref() with the string containing the
+    reference as the argument.
+    """
+
+    CDATA_CONTENT_ELEMENTS = ("script", "style")
+
+
+    def __init__(self):
+        """Initialize and reset this instance."""
+        self.reset()
+
+    def reset(self):
+        """Reset this instance.  Loses all unprocessed data."""
+        self.rawdata = ''
+        self.stack = []
+        self.lasttag = '???'
+        self.interesting = interesting_normal
+        markupbase.ParserBase.reset(self)
+
+    def feed(self, data):
+        """Feed data to the parser.
+
+        Call this as often as you want, with as little or as much text
+        as you want (may include '\n').
+        """
+        self.rawdata = self.rawdata + data
+        self.goahead(0)
+
+    def close(self):
+        """Handle any buffered data."""
+        self.goahead(1)
+
+    def error(self, message):
+        raise HTMLParseError(message, self.getpos())
+
+    __starttag_text = None
+
+    def get_starttag_text(self):
+        """Return full source of start tag: '<...>'."""
+        return self.__starttag_text
+
+    cdata_endtag = None
+
+    def set_cdata_mode(self, endtag=None):
+        self.cdata_endtag = endtag
+        self.interesting = interesting_cdata
+
+    def clear_cdata_mode(self):
+        self.cdata_endtag = None
+        self.interesting = interesting_normal
+
+    # Internal -- handle data as far as reasonable.  May leave state
+    # and data to be processed by a subsequent call.  If 'end' is
+    # true, force handling all data as if followed by EOF marker.
+    def goahead(self, end):
+        rawdata = self.rawdata
+        i = 0
+        n = len(rawdata)
+        while i < n:
+            match = self.interesting.search(rawdata, i) # < or &
+            if match:
+                j = match.start()
+            else:
+                j = n
+            if i < j: self.handle_data(rawdata[i:j])
+            i = self.updatepos(i, j)
+            if i == n: break
+            if rawdata[i] == '<':
+                if starttagopen.match(rawdata, i): # < + letter
+                    k = self.parse_starttag(i)
+                elif endtagopen.match(rawdata, i): # </
+                    k = self.parse_endtag(i)
+                elif _contains_at(rawdata, "<!--", i): # <!--
+                    k = self.parse_comment(i)
+                elif _contains_at(rawdata, "<!", i): # <!
+                    k = self.parse_declaration(i)
+                elif _contains_at(rawdata, "<?", i): # <?
+                    k = self.parse_pi(i)
+                elif _contains_at(rawdata, "<?", i): # <!
+                    k = self.parse_declaration(i)
+                elif (i + 1) < n:
+                    self.handle_data("<")
+                    k = i + 1
+                else:
+                    break
+                if k < 0:
+                    if end:
+                        self.error("EOF in middle of construct")
+                    break
+                i = self.updatepos(i, k)
+            elif rawdata[i:i+2] == "&#":
+                match = charref.match(rawdata, i)
+                if match:
+                    name = match.group()[2:-1]
+                    self.handle_charref(name)
+                    k = match.end()
+                    if rawdata[k-1] != ';':
+                        k = k - 1
+                    i = self.updatepos(i, k)
+                    continue
+                else:
+                    break
+            elif rawdata[i] == '&':
+                match = entityref.match(rawdata, i)
+                if match:
+                    name = match.group(1)
+                    self.handle_entityref(name)
+                    k = match.end()
+                    if rawdata[k-1] != ';':
+                        k = k - 1
+                    i = self.updatepos(i, k)
+                    continue
+                match = incomplete.match(rawdata, i)
+                if match:
+                    # match.group() will contain at least 2 chars
+                    rest = rawdata[i:]
+                    if end and match.group() == rest:
+                        self.error("EOF in middle of entity or char ref")
+                    # incomplete
+                    break
+                elif (i + 1) < n:
+                    # not the end of the buffer, and can't be confused
+                    # with some other construct
+                    self.handle_data("&")
+                    i = self.updatepos(i, i + 1)
+                else:
+                    break
+            else:
+                assert 0, "interesting.search() lied"
+        # end while
+        if end and i < n:
+            self.handle_data(rawdata[i:n])
+            i = self.updatepos(i, n)
+        self.rawdata = rawdata[i:]
+
+    # Internal -- parse comment, return end or -1 if not terminated
+    def parse_comment(self, i, report=1):
+        rawdata = self.rawdata
+        assert rawdata[i:i+4] == '<!--', 'unexpected call to parse_comment()'
+        match = commentclose.search(rawdata, i+4)
+        if not match:
+            return -1
+        if report:
+            j = match.start()
+            self.handle_comment(rawdata[i+4: j])
+        j = match.end()
+        return j
+
+    # Internal -- parse processing instr, return end or -1 if not terminated
+    def parse_pi(self, i):
+        rawdata = self.rawdata
+        assert rawdata[i:i+2] == '<?', 'unexpected call to parse_pi()'
+        match = piclose.search(rawdata, i+2) # >
+        if not match:
+            return -1
+        j = match.start()
+        self.handle_pi(rawdata[i+2: j])
+        j = match.end()
+        return j
+
+    # Internal -- handle starttag, return end or -1 if not terminated
+    def parse_starttag(self, i):
+        self.__starttag_text = None
+        endpos = self.check_for_whole_start_tag(i)
+        if endpos < 0:
+            return endpos
+        rawdata = self.rawdata
+        self.__starttag_text = rawdata[i:endpos]
+
+        # Now parse the data between i+1 and j into a tag and attrs
+        attrs = []
+        match = tagfind.match(rawdata, i+1)
+        assert match, 'unexpected call to parse_starttag()'
+        k = match.end()
+        self.lasttag = tag = string.lower(rawdata[i+1:k])
+
+        while k < endpos:
+            m = attrfind.match(rawdata, k)
+            if not m:
+                break
+            attrname, rest, attrvalue = m.group(1, 2, 3)
+            if not rest:
+                attrvalue = None
+            elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
+                 attrvalue[:1] == '"' == attrvalue[-1:]:
+                attrvalue = attrvalue[1:-1]
+                attrvalue = self.unescape(attrvalue)
+            attrs.append((string.lower(attrname), attrvalue))
+            k = m.end()
+
+        end = string.strip(rawdata[k:endpos])
+        if end not in (">", "/>"):
+            lineno, offset = self.getpos()
+            if "\n" in self.__starttag_text:
+                lineno = lineno + string.count(self.__starttag_text, "\n")
+                offset = len(self.__starttag_text) \
+                         - string.rfind(self.__starttag_text, "\n")
+            else:
+                offset = offset + len(self.__starttag_text)
+            self.error("junk characters in start tag: %s"
+                       % `rawdata[k:endpos][:20]`)
+        if end[-2:] == '/>':
+            # XHTML-style empty tag: <span attr="value" />
+            self.handle_startendtag(tag, attrs)
+        else:
+            self.handle_starttag(tag, attrs)
+            if tag in self.CDATA_CONTENT_ELEMENTS:
+                self.set_cdata_mode(tag)
+        return endpos
+
+    # Internal -- check to see if we have a complete starttag; return end
+    # or -1 if incomplete.
+    def check_for_whole_start_tag(self, i):
+        rawdata = self.rawdata
+        m = locatestarttagend.match(rawdata, i)
+        if m:
+            j = m.end()
+            next = rawdata[j:j+1]
+            if next == ">":
+                return j + 1
+            if next == "/":
+                s = rawdata[j:j+2]
+                if s == "/>":
+                    return j + 2
+                if s == "/":
+                    # buffer boundary
+                    return -1
+                # else bogus input
+                self.updatepos(i, j + 1)
+                self.error("malformed empty start tag")
+            if next == "":
+                # end of input
+                return -1
+            if next in ("abcdefghijklmnopqrstuvwxyz=/"
+                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+                # end of input in or before attribute value, or we have the
+                # '/' from a '/>' ending
+                return -1
+            self.updatepos(i, j)
+            self.error("malformed start tag")
+        raise AssertionError("we should not get here!")
+
+    # Internal -- parse endtag, return end or -1 if incomplete
+    def parse_endtag(self, i):
+        rawdata = self.rawdata
+        assert rawdata[i:i+2] == "</", "unexpected call to parse_endtag"
+        match = endendtag.search(rawdata, i+1) # >
+        if not match:
+            return -1
+        j = match.end()
+        match = endtagfind.match(rawdata, i) # </ + tag + >
+        if not match:
+            self.error("bad end tag: %s" % `rawdata[i:j]`)
+        tag = string.lower(match.group(1))
+        if (  self.cdata_endtag is not None
+              and tag != self.cdata_endtag):
+            # Should be a mismatched end tag, but we'll treat it
+            # as text anyway, since most HTML authors aren't
+            # interested in the finer points of syntax.
+            self.handle_data(match.group(0))
+        else:
+            self.handle_endtag(tag)
+            self.clear_cdata_mode()
+        return j
+
+    # Overridable -- finish processing of start+end tag: <tag.../>
+    def handle_startendtag(self, tag, attrs):
+        self.handle_starttag(tag, attrs)
+        self.handle_endtag(tag)
+
+    # Overridable -- handle start tag
+    def handle_starttag(self, tag, attrs):
+        pass
+
+    # Overridable -- handle end tag
+    def handle_endtag(self, tag):
+        pass
+
+    # Overridable -- handle character reference
+    def handle_charref(self, name):
+        pass
+
+    # Overridable -- handle entity reference
+    def handle_entityref(self, name):
+        pass
+
+    # Overridable -- handle data
+    def handle_data(self, data):
+        pass
+
+    # Overridable -- handle comment
+    def handle_comment(self, data):
+        pass
+
+    # Overridable -- handle declaration
+    def handle_decl(self, decl):
+        pass
+
+    # Overridable -- handle processing instruction
+    def handle_pi(self, data):
+        pass
+
+    def unknown_decl(self, data):
+        self.error("unknown declaration: " + `data`)
+
+    # Internal -- helper to remove special character quoting
+    def unescape(self, s):
+        if '&' not in s:
+            return s
+        s = string.replace(s, "&lt;", "<")
+        s = string.replace(s, "&gt;", ">")
+        s = string.replace(s, "&apos;", "'")
+        s = string.replace(s, "&quot;", '"')
+        s = string.replace(s, "&amp;", "&") # Must be last
+        return s
diff --git a/roundup/cgi/TAL/HTMLTALParser.py b/roundup/cgi/TAL/HTMLTALParser.py
new file mode 100644 (file)
index 0000000..8d7f0db
--- /dev/null
@@ -0,0 +1,290 @@
+##############################################################################
+#
+# 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
+# 
+##############################################################################
+"""
+Parse HTML and compile to TALInterpreter intermediate code.
+"""
+
+import sys
+import string
+
+from TALGenerator import TALGenerator
+from TALDefs import ZOPE_METAL_NS, ZOPE_TAL_NS, METALError, TALError
+from HTMLParser import HTMLParser, HTMLParseError
+
+BOOLEAN_HTML_ATTRS = [
+    # List of Boolean attributes in HTML that may be given in
+    # minimized form (e.g. <img ismap> rather than <img ismap="">)
+    # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
+    "compact", "nowrap", "ismap", "declare", "noshade", "checked",
+    "disabled", "readonly", "multiple", "selected", "noresize",
+    "defer"
+    ]
+
+EMPTY_HTML_TAGS = [
+    # List of HTML tags with an empty content model; these are
+    # rendered in minimized form, e.g. <img />.
+    # From http://www.w3.org/TR/xhtml1/#dtds
+    "base", "meta", "link", "hr", "br", "param", "img", "area",
+    "input", "col", "basefont", "isindex", "frame",
+    ]
+
+PARA_LEVEL_HTML_TAGS = [
+    # List of HTML elements that close open paragraph-level elements
+    # and are themselves paragraph-level.
+    "h1", "h2", "h3", "h4", "h5", "h6", "p",
+    ]
+
+BLOCK_CLOSING_TAG_MAP = {
+    "tr": ("tr", "td", "th"),
+    "td": ("td", "th"),
+    "th": ("td", "th"),
+    "li": ("li",),
+    "dd": ("dd", "dt"),
+    "dt": ("dd", "dt"),
+    }
+
+BLOCK_LEVEL_HTML_TAGS = [
+    # List of HTML tags that denote larger sections than paragraphs.
+    "blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody",
+    "noframe", "ul", "ol", "li", "dl", "dt", "dd", "div",
+    ]
+
+TIGHTEN_IMPLICIT_CLOSE_TAGS = (PARA_LEVEL_HTML_TAGS
+                               + BLOCK_CLOSING_TAG_MAP.keys())
+
+
+class NestingError(HTMLParseError):
+    """Exception raised when elements aren't properly nested."""
+
+    def __init__(self, tagstack, endtag, position=(None, None)):
+        self.endtag = endtag
+        if tagstack:
+            if len(tagstack) == 1:
+                msg = ('Open tag <%s> does not match close tag </%s>'
+                       % (tagstack[0], endtag))
+            else:
+                msg = ('Open tags <%s> do not match close tag </%s>'
+                       % (string.join(tagstack, '>, <'), endtag))
+        else:
+            msg = 'No tags are open to match </%s>' % endtag
+        HTMLParseError.__init__(self, msg, position)
+
+class EmptyTagError(NestingError):
+    """Exception raised when empty elements have an end tag."""
+
+    def __init__(self, tag, position=(None, None)):
+        self.tag = tag
+        msg = 'Close tag </%s> should be removed' % tag
+        HTMLParseError.__init__(self, msg, position)
+
+class OpenTagError(NestingError):
+    """Exception raised when a tag is not allowed in another tag."""
+
+    def __init__(self, tagstack, tag, position=(None, None)):
+        self.tag = tag
+        msg = 'Tag <%s> is not allowed in <%s>' % (tag, tagstack[-1])
+        HTMLParseError.__init__(self, msg, position)
+
+class HTMLTALParser(HTMLParser):
+
+    # External API
+
+    def __init__(self, gen=None):
+        HTMLParser.__init__(self)
+        if gen is None:
+            gen = TALGenerator(xml=0)
+        self.gen = gen
+        self.tagstack = []
+        self.nsstack = []
+        self.nsdict = {'tal': ZOPE_TAL_NS, 'metal': ZOPE_METAL_NS}
+
+    def parseFile(self, file):
+        f = open(file)
+        data = f.read()
+        f.close()
+        self.parseString(data)
+
+    def parseString(self, data):
+        self.feed(data)
+        self.close()
+        while self.tagstack:
+            self.implied_endtag(self.tagstack[-1], 2)
+        assert self.nsstack == [], self.nsstack
+
+    def getCode(self):
+        return self.gen.getCode()
+
+    def getWarnings(self):
+        return ()
+
+    # Overriding HTMLParser methods
+
+    def handle_starttag(self, tag, attrs):
+        self.close_para_tags(tag)
+        self.scan_xmlns(attrs)
+        tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
+        self.tagstack.append(tag)
+        self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+                                  self.getpos())
+        if tag in EMPTY_HTML_TAGS:
+            self.implied_endtag(tag, -1)
+
+    def handle_startendtag(self, tag, attrs):
+        self.close_para_tags(tag)
+        self.scan_xmlns(attrs)
+        tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
+        if taldict.get("content"):
+            self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+                                      self.getpos())
+            self.gen.emitEndElement(tag, implied=-1)
+        else:
+            self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+                                      self.getpos(), isend=1)
+        self.pop_xmlns()
+
+    def handle_endtag(self, tag):
+        if tag in EMPTY_HTML_TAGS:
+            # </img> etc. in the source is an error
+            raise EmptyTagError(tag, self.getpos())
+        self.close_enclosed_tags(tag)
+        self.gen.emitEndElement(tag)
+        self.pop_xmlns()
+        self.tagstack.pop()
+
+    def close_para_tags(self, tag):
+        if tag in EMPTY_HTML_TAGS:
+            return
+        close_to = -1
+        if BLOCK_CLOSING_TAG_MAP.has_key(tag):
+            blocks_to_close = BLOCK_CLOSING_TAG_MAP[tag]
+            for i in range(len(self.tagstack)):
+                t = self.tagstack[i]
+                if t in blocks_to_close:
+                    if close_to == -1:
+                        close_to = i
+                elif t in BLOCK_LEVEL_HTML_TAGS:
+                    close_to = -1
+        elif tag in PARA_LEVEL_HTML_TAGS + BLOCK_LEVEL_HTML_TAGS:
+            i = len(self.tagstack) - 1
+            while i >= 0:
+                closetag = self.tagstack[i]
+                if closetag in BLOCK_LEVEL_HTML_TAGS:
+                    break
+                if closetag in PARA_LEVEL_HTML_TAGS:
+                    if closetag != "p":
+                        raise OpenTagError(self.tagstack, tag, self.getpos())
+                    close_to = i
+                i = i - 1
+        if close_to >= 0:
+            while len(self.tagstack) > close_to:
+                self.implied_endtag(self.tagstack[-1], 1)
+
+    def close_enclosed_tags(self, tag):
+        if tag not in self.tagstack:
+            raise NestingError(self.tagstack, tag, self.getpos())
+        while tag != self.tagstack[-1]:
+            self.implied_endtag(self.tagstack[-1], 1)
+        assert self.tagstack[-1] == tag
+
+    def implied_endtag(self, tag, implied):
+        assert tag == self.tagstack[-1]
+        assert implied in (-1, 1, 2)
+        isend = (implied < 0)
+        if tag in TIGHTEN_IMPLICIT_CLOSE_TAGS:
+            # Pick out trailing whitespace from the program, and
+            # insert the close tag before the whitespace.
+            white = self.gen.unEmitWhitespace()
+        else:
+            white = None
+        self.gen.emitEndElement(tag, isend=isend, implied=implied)
+        if white:
+            self.gen.emitRawText(white)
+        self.tagstack.pop()
+        self.pop_xmlns()
+
+    def handle_charref(self, name):
+        self.gen.emitRawText("&#%s;" % name)
+
+    def handle_entityref(self, name):
+        self.gen.emitRawText("&%s;" % name)
+
+    def handle_data(self, data):
+        self.gen.emitRawText(data)
+
+    def handle_comment(self, data):
+        self.gen.emitRawText("<!--%s-->" % data)
+
+    def handle_decl(self, data):
+        self.gen.emitRawText("<!%s>" % data)
+
+    def handle_pi(self, data):
+        self.gen.emitRawText("<?%s>" % data)
+
+    # Internal thingies
+
+    def scan_xmlns(self, attrs):
+        nsnew = {}
+        for key, value in attrs:
+            if key[:6] == "xmlns:":
+                nsnew[key[6:]] = value
+        if nsnew:
+            self.nsstack.append(self.nsdict)
+            self.nsdict = self.nsdict.copy()
+            self.nsdict.update(nsnew)
+        else:
+            self.nsstack.append(self.nsdict)
+
+    def pop_xmlns(self):
+        self.nsdict = self.nsstack.pop()
+
+    def fixname(self, name):
+        if ':' in name:
+            prefix, suffix = string.split(name, ':', 1)
+            if prefix == 'xmlns':
+                nsuri = self.nsdict.get(suffix)
+                if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS):
+                    return name, name, prefix
+            else:
+                nsuri = self.nsdict.get(prefix)
+                if nsuri == ZOPE_TAL_NS:
+                    return name, suffix, 'tal'
+                elif nsuri == ZOPE_METAL_NS:
+                    return name, suffix,  'metal'
+        return name, name, 0
+
+    def process_ns(self, name, attrs):
+        attrlist = []
+        taldict = {}
+        metaldict = {}
+        name, namebase, namens = self.fixname(name)
+        for item in attrs:
+            key, value = item
+            key, keybase, keyns = self.fixname(key)
+            ns = keyns or namens # default to tag namespace
+            if ns and ns != 'unknown':
+                item = (key, value, ns)
+            if ns == 'tal':
+                if taldict.has_key(keybase):
+                    raise TALError("duplicate TAL attribute " +
+                                   `keybase`, self.getpos())
+                taldict[keybase] = value
+            elif ns == 'metal':
+                if metaldict.has_key(keybase):
+                    raise METALError("duplicate METAL attribute " +
+                                     `keybase`, self.getpos())
+                metaldict[keybase] = value
+            attrlist.append(item)
+        if namens in ('metal', 'tal'):
+            taldict['tal tag'] = namens
+        return name, attrlist, taldict, metaldict
diff --git a/roundup/cgi/TAL/README.txt b/roundup/cgi/TAL/README.txt
new file mode 100644 (file)
index 0000000..4a28816
--- /dev/null
@@ -0,0 +1,97 @@
+TAL - Template Attribute Language
+---------------------------------
+
+This is an implementation of TAL, the Zope Template Attribute
+Language.  For TAL, see the Zope Presentation Templates ZWiki:
+
+    http://dev.zope.org/Wikis/DevSite/Projects/ZPT/FrontPage
+
+It is not a Zope product nor is it designed exclusively to run inside
+of Zope, but if you have a Zope checkout that includes
+Products/ParsedXML, its Expat parser will be used.
+
+Prerequisites
+-------------
+
+You need:
+
+- A recent checkout of Zope2; don't forget to run the wo_pcgi.py
+  script to compile everything.  (See above -- this is now optional.)
+
+- A recent checkout of the Zope2 product ParsedXML, accessible
+  throught <Zope2>/lib/python/Products/ParsedXML; don't forget to run
+  the setup.py script to compiles Expat.  (Again, optional.)
+
+- Python 1.5.2; the driver script refuses to work with other versions
+  unless you specify the -n option; this is done so that I don't
+  accidentally use Python 2.x features.
+
+- Create a .path file containing proper module search path; it should
+  point the <Zope2>/lib/python directory that you want to use.
+
+How To Play
+-----------
+
+(Don't forget to edit .path, see above!)
+
+The script driver.py takes an XML file with TAL markup as argument and
+writes the expanded version to standard output.  The filename argument
+defaults to tests/input/test01.xml.
+
+Regression test
+---------------
+
+There are unit test suites in the 'tests' subdirectory; these can be
+run with tests/run.py.  This should print the testcase names plus
+progress info, followed by a final line saying "OK".  It requires that
+../unittest.py exists.
+
+There are a number of test files in the 'tests' subdirectory, named
+tests/input/test<number>.xml and tests/input/test<number>.html.  The
+Python script ./runtest.py calls driver.main() for each test file, and
+should print "<file> OK" for each one.  These tests are also run as
+part of the unit test suites, so tests/run.py is all you need.
+
+What's Here
+-----------
+
+DummyEngine.py         simple-minded TALES execution engine
+TALInterpreter.py      class to interpret intermediate code
+TALGenerator.py                class to generate intermediate code
+XMLParser.py           base class to parse XML, avoiding DOM
+TALParser.py           class to parse XML with TAL into intermediate code
+HTMLTALParser.py       class to parse HTML with TAL into intermediate code
+HTMLParser.py          HTML-parsing base class
+driver.py              script to demonstrate TAL expansion
+timer.py               script to time various processing phases
+setpath.py             hack to set sys.path and import ZODB
+__init__.py            empty file that makes this directory a package
+runtest.py             Python script to run file-comparison tests
+ndiff.py               helper for runtest.py to produce diffs
+tests/                 drectory with test files and output
+tests/run.py           Python script to run all tests
+
+Author and License
+------------------
+
+This code is written by Guido van Rossum (project lead), Fred Drake,
+and Tim Peters.  It is owned by Digital Creations and can be
+redistributed under the Zope Public License.
+
+TO DO
+-----
+
+(See also http://www.zope.org/Members/jim/ZPTIssueTracker .)
+
+- Need to remove leading whitespace and newline when omitting an
+  element (either through tal:replace with a value of nothing or
+  tal:condition with a false condition).
+
+- Empty TAL/METAL attributes are ignored: tal:replace="" is ignored
+  rather than causing an error.
+
+- HTMLTALParser.py and TALParser.py are silly names.  Should be
+  HTMLTALCompiler.py and XMLTALCompiler.py (or maybe shortened,
+  without "TAL"?)
+
+- Should we preserve case of tags and attribute names in HTML?
diff --git a/roundup/cgi/TAL/TALDefs.py b/roundup/cgi/TAL/TALDefs.py
new file mode 100644 (file)
index 0000000..dbc0443
--- /dev/null
@@ -0,0 +1,145 @@
+##############################################################################
+#
+# 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
+# 
+##############################################################################
+"""
+Common definitions used by TAL and METAL compilation an transformation.
+"""
+
+from types import ListType, TupleType
+
+TAL_VERSION = "1.3.2"
+
+XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace
+XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations
+
+ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal"
+ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal"
+
+NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*"
+
+KNOWN_METAL_ATTRIBUTES = [
+    "define-macro",
+    "use-macro",
+    "define-slot",
+    "fill-slot",
+    "slot"
+    ]
+
+KNOWN_TAL_ATTRIBUTES = [
+    "define",
+    "condition",
+    "content",
+    "replace",
+    "repeat",
+    "attributes",
+    "on-error",
+    "omit-tag",
+    "tal tag",
+    ]
+
+class TALError(Exception):
+
+    def __init__(self, msg, position=(None, None)):
+        assert msg != ""
+        self.msg = msg
+        self.lineno = position[0]
+        self.offset = position[1]
+
+    def __str__(self):
+        result = self.msg
+        if self.lineno is not None:
+            result = result + ", at line %d" % self.lineno
+        if self.offset is not None:
+            result = result + ", column %d" % (self.offset + 1)
+        return result
+
+class METALError(TALError):
+    pass
+
+class TALESError(TALError):
+    pass
+
+class ErrorInfo:
+
+    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]
+
+import re
+_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
+_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)
+del re
+
+def parseAttributeReplacements(arg):
+    dict = {}
+    for part in splitParts(arg):
+        m = _attr_re.match(part)
+        if not m:
+            raise TALError("Bad syntax in attributes:" + `part`)
+        name, expr = m.group(1, 2)
+        if dict.has_key(name):
+            raise TALError("Duplicate attribute name in attributes:" + `part`)
+        dict[name] = expr
+    return dict
+
+def parseSubstitution(arg, position=(None, None)):
+    m = _subst_re.match(arg)
+    if not m:
+        raise TALError("Bad syntax in substitution text: " + `arg`, position)
+    key, expr = m.group(1, 2)
+    if not key:
+        key = "text"
+    return key, expr
+
+def splitParts(arg):
+    # Break in pieces at undoubled semicolons and
+    # change double semicolons to singles:
+    import string
+    arg = string.replace(arg, ";;", "\0")
+    parts = string.split(arg, ';')
+    parts = map(lambda s, repl=string.replace: repl(s, "\0", ";"), parts)
+    if len(parts) > 1 and not string.strip(parts[-1]):
+        del parts[-1] # It ended in a semicolon
+    return parts
+
+def isCurrentVersion(program):
+    version = getProgramVersion(program)
+    return version == TAL_VERSION
+
+def getProgramMode(program):
+    version = getProgramVersion(program)
+    if (version == TAL_VERSION and isinstance(program[1], TupleType) and
+        len(program[1]) == 2):
+        opcode, mode = program[1]
+        if opcode == "mode":
+            return mode
+    return None
+
+def getProgramVersion(program):
+    if (len(program) >= 2 and
+        isinstance(program[0], TupleType) and len(program[0]) == 2):
+        opcode, version = program[0]
+        if opcode == "version":
+            return version
+    return None
+
+import cgi
+def quote(s, escape=cgi.escape):
+    return '"%s"' % escape(s, 1)
+del cgi
diff --git a/roundup/cgi/TAL/TALGenerator.py b/roundup/cgi/TAL/TALGenerator.py
new file mode 100644 (file)
index 0000000..0bfadea
--- /dev/null
@@ -0,0 +1,583 @@
+##############################################################################
+#
+# 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
+# 
+##############################################################################
+"""
+Code generator for TALInterpreter intermediate code.
+"""
+
+import string
+import re
+import cgi
+
+from TALDefs import *
+
+class TALGenerator:
+
+    inMacroUse = 0
+    inMacroDef = 0
+    source_file = None
+    
+    def __init__(self, expressionCompiler=None, xml=1, source_file=None):
+        if not expressionCompiler:
+            from DummyEngine import DummyEngine
+            expressionCompiler = DummyEngine()
+        self.expressionCompiler = expressionCompiler
+        self.CompilerError = expressionCompiler.getCompilerError()
+        self.program = []
+        self.stack = []
+        self.todoStack = []
+        self.macros = {}
+        self.slots = {}
+        self.slotStack = []
+        self.xml = xml
+        self.emit("version", TAL_VERSION)
+        self.emit("mode", xml and "xml" or "html")
+        if source_file is not None:
+            self.source_file = source_file
+            self.emit("setSourceFile", source_file)
+
+    def getCode(self):
+        assert not self.stack
+        assert not self.todoStack
+        return self.optimize(self.program), self.macros
+
+    def optimize(self, program):
+        output = []
+        collect = []
+        rawseen = cursor = 0
+        if self.xml:
+            endsep = "/>"
+        else:
+            endsep = " />"
+        for cursor in xrange(len(program)+1):
+            try:
+                item = program[cursor]
+            except IndexError:
+                item = (None, None)
+            opcode = item[0]
+            if opcode == "rawtext":
+                collect.append(item[1])
+                continue
+            if opcode == "endTag":
+                collect.append("</%s>" % item[1])
+                continue
+            if opcode == "startTag":
+                if self.optimizeStartTag(collect, item[1], item[2], ">"):
+                    continue
+            if opcode == "startEndTag":
+                if self.optimizeStartTag(collect, item[1], item[2], endsep):
+                    continue
+            if opcode in ("beginScope", "endScope"):
+                # Push *Scope instructions in front of any text instructions;
+                # this allows text instructions separated only by *Scope
+                # instructions to be joined together.
+                output.append(self.optimizeArgsList(item))
+                continue
+            text = string.join(collect, "")
+            if text:
+                i = string.rfind(text, "\n")
+                if i >= 0:
+                    i = len(text) - (i + 1)
+                    output.append(("rawtextColumn", (text, i)))
+                else:
+                    output.append(("rawtextOffset", (text, len(text))))
+            if opcode != None:
+                output.append(self.optimizeArgsList(item))
+            rawseen = cursor+1
+            collect = []
+        return self.optimizeCommonTriple(output)
+
+    def optimizeArgsList(self, item):
+        if len(item) == 2:
+            return item
+        else:
+            return item[0], tuple(item[1:])
+
+    actionIndex = {"replace":0, "insert":1, "metal":2, "tal":3, "xmlns":4,
+                   0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
+    def optimizeStartTag(self, collect, name, attrlist, end):
+        if not attrlist:
+            collect.append("<%s%s" % (name, end))
+            return 1
+        opt = 1
+        new = ["<" + name]
+        for i in range(len(attrlist)):
+            item = attrlist[i]
+            if len(item) > 2:
+                opt = 0
+                name, value, action = item[:3]
+                action = self.actionIndex[action]
+                attrlist[i] = (name, value, action) + item[3:]
+            else:
+                if item[1] is None:
+                    s = item[0]
+                else:
+                    s = "%s=%s" % (item[0], quote(item[1]))
+                attrlist[i] = item[0], s
+            if item[1] is None:
+                new.append(" " + item[0])
+            else:
+                new.append(" %s=%s" % (item[0], quote(item[1])))
+        if opt:
+            new.append(end)
+            collect.extend(new)
+        return opt
+
+    def optimizeCommonTriple(self, program):
+        if len(program) < 3:
+            return program
+        output = program[:2]
+        prev2, prev1 = output
+        for item in program[2:]:
+            if (  item[0] == "beginScope"
+                  and prev1[0] == "setPosition"
+                  and prev2[0] == "rawtextColumn"):
+                position = output.pop()[1]
+                text, column = output.pop()[1]
+                prev1 = None, None
+                closeprev = 0
+                if output and output[-1][0] == "endScope":
+                    closeprev = 1
+                    output.pop()
+                item = ("rawtextBeginScope",
+                        (text, column, position, closeprev, item[1]))
+            output.append(item)
+            prev2 = prev1
+            prev1 = item
+        return output
+
+    def todoPush(self, todo):
+        self.todoStack.append(todo)
+
+    def todoPop(self):
+        return self.todoStack.pop()
+
+    def compileExpression(self, expr):
+        try:
+            return self.expressionCompiler.compile(expr)
+        except self.CompilerError, err:
+            raise TALError('%s in expression %s' % (err.args[0], `expr`),
+                           self.position)
+
+    def pushProgram(self):
+        self.stack.append(self.program)
+        self.program = []
+
+    def popProgram(self):
+        program = self.program
+        self.program = self.stack.pop()
+        return self.optimize(program)
+
+    def pushSlots(self):
+        self.slotStack.append(self.slots)
+        self.slots = {}
+
+    def popSlots(self):
+        slots = self.slots
+        self.slots = self.slotStack.pop()
+        return slots
+
+    def emit(self, *instruction):
+        self.program.append(instruction)
+
+    def emitStartTag(self, name, attrlist, isend=0):
+        if isend:
+            opcode = "startEndTag"
+        else:
+            opcode = "startTag"
+        self.emit(opcode, name, attrlist)
+
+    def emitEndTag(self, name):
+        if self.xml and self.program and self.program[-1][0] == "startTag":
+            # Minimize empty element
+            self.program[-1] = ("startEndTag",) + self.program[-1][1:]
+        else:
+            self.emit("endTag", name)
+
+    def emitOptTag(self, name, optTag, isend):
+        program = self.popProgram() #block
+        start = self.popProgram() #start tag
+        if (isend or not program) and self.xml:
+            # Minimize empty element
+            start[-1] = ("startEndTag",) + start[-1][1:]
+            isend = 1
+        cexpr = optTag[0]
+        if cexpr:
+            cexpr = self.compileExpression(optTag[0])
+        self.emit("optTag", name, cexpr, optTag[1], isend, start, program)
+        
+    def emitRawText(self, text):
+        self.emit("rawtext", text)
+
+    def emitText(self, text):
+        self.emitRawText(cgi.escape(text))
+
+    def emitDefines(self, defines):
+        for part in splitParts(defines):
+            m = re.match(
+                r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part)
+            if not m:
+                raise TALError("invalid define syntax: " + `part`,
+                               self.position)
+            scope, name, expr = m.group(1, 2, 3)
+            scope = scope or "local"
+            cexpr = self.compileExpression(expr)
+            if scope == "local":
+                self.emit("setLocal", name, cexpr)
+            else:
+                self.emit("setGlobal", name, cexpr)
+
+    def emitOnError(self, name, onError):
+        block = self.popProgram()
+        key, expr = parseSubstitution(onError)
+        cexpr = self.compileExpression(expr)
+        if key == "text":
+            self.emit("insertText", cexpr, [])
+        else:
+            assert key == "structure"
+            self.emit("insertStructure", cexpr, {}, [])
+        self.emitEndTag(name)
+        handler = self.popProgram()
+        self.emit("onError", block, handler)
+
+    def emitCondition(self, expr):
+        cexpr = self.compileExpression(expr)
+        program = self.popProgram()
+        self.emit("condition", cexpr, program)
+
+    def emitRepeat(self, arg):
+        m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg)
+        if not m:
+            raise TALError("invalid repeat syntax: " + `arg`,
+                           self.position)
+        name, expr = m.group(1, 2)
+        cexpr = self.compileExpression(expr)
+        program = self.popProgram()
+        self.emit("loop", name, cexpr, program)
+
+    def emitSubstitution(self, arg, attrDict={}):
+        key, expr = parseSubstitution(arg)
+        cexpr = self.compileExpression(expr)
+        program = self.popProgram()
+        if key == "text":
+            self.emit("insertText", cexpr, program)
+        else:
+            assert key == "structure"
+            self.emit("insertStructure", cexpr, attrDict, program)
+
+    def emitDefineMacro(self, macroName):
+        program = self.popProgram()
+        macroName = string.strip(macroName)
+        if self.macros.has_key(macroName):
+            raise METALError("duplicate macro definition: %s" % `macroName`,
+                             self.position)
+        if not re.match('%s$' % NAME_RE, macroName):
+            raise METALError("invalid macro name: %s" % `macroName`,
+                             self.position)
+        self.macros[macroName] = program
+        self.inMacroDef = self.inMacroDef - 1
+        self.emit("defineMacro", macroName, program)
+
+    def emitUseMacro(self, expr):
+        cexpr = self.compileExpression(expr)
+        program = self.popProgram()
+        self.inMacroUse = 0
+        self.emit("useMacro", expr, cexpr, self.popSlots(), program)
+
+    def emitDefineSlot(self, slotName):
+        program = self.popProgram()
+        slotName = string.strip(slotName)
+        if not re.match('%s$' % NAME_RE, slotName):
+            raise METALError("invalid slot name: %s" % `slotName`,
+                             self.position)
+        self.emit("defineSlot", slotName, program)
+
+    def emitFillSlot(self, slotName):
+        program = self.popProgram()
+        slotName = string.strip(slotName)
+        if self.slots.has_key(slotName):
+            raise METALError("duplicate fill-slot name: %s" % `slotName`,
+                             self.position)
+        if not re.match('%s$' % NAME_RE, slotName):
+            raise METALError("invalid slot name: %s" % `slotName`,
+                             self.position)
+        self.slots[slotName] = program
+        self.inMacroUse = 1
+        self.emit("fillSlot", slotName, program)
+
+    def unEmitWhitespace(self):
+        collect = []
+        i = len(self.program) - 1
+        while i >= 0:
+            item = self.program[i]
+            if item[0] != "rawtext":
+                break
+            text = item[1]
+            if not re.match(r"\A\s*\Z", text):
+                break
+            collect.append(text)
+            i = i-1
+        del self.program[i+1:]
+        if i >= 0 and self.program[i][0] == "rawtext":
+            text = self.program[i][1]
+            m = re.search(r"\s+\Z", text)
+            if m:
+                self.program[i] = ("rawtext", text[:m.start()])
+                collect.append(m.group())
+        collect.reverse()
+        return string.join(collect, "")
+
+    def unEmitNewlineWhitespace(self):
+        collect = []
+        i = len(self.program)
+        while i > 0:
+            i = i-1
+            item = self.program[i]
+            if item[0] != "rawtext":
+                break
+            text = item[1]
+            if re.match(r"\A[ \t]*\Z", text):
+                collect.append(text)
+                continue
+            m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text)
+            if not m:
+                break
+            text, rest = m.group(1, 2)
+            collect.reverse()
+            rest = rest + string.join(collect, "")
+            del self.program[i:]
+            if text:
+                self.emit("rawtext", text)
+            return rest
+        return None
+
+    def replaceAttrs(self, attrlist, repldict):
+        if not repldict:
+            return attrlist
+        newlist = []
+        for item in attrlist:
+            key = item[0]
+            if repldict.has_key(key):
+                item = item[:2] + ("replace", repldict[key])
+                del repldict[key]
+            newlist.append(item)
+        for key, value in repldict.items(): # Add dynamic-only attributes
+            item = (key, None, "insert", value)
+            newlist.append(item)
+        return newlist
+
+    def emitStartElement(self, name, attrlist, taldict, metaldict,
+                         position=(None, None), isend=0):
+        if not taldict and not metaldict:
+            # Handle the simple, common case
+            self.emitStartTag(name, attrlist, isend)
+            self.todoPush({})
+            if isend:
+                self.emitEndElement(name, isend)
+            return
+
+        self.position = position
+        for key, value in taldict.items():
+            if key not in KNOWN_TAL_ATTRIBUTES:
+                raise TALError("bad TAL attribute: " + `key`, position)
+            if not (value or key == 'omit-tag'):
+                raise TALError("missing value for TAL attribute: " +
+                               `key`, position)
+        for key, value in metaldict.items():
+            if key not in KNOWN_METAL_ATTRIBUTES:
+                raise METALError("bad METAL attribute: " + `key`,
+                position)
+            if not value:
+                raise TALError("missing value for METAL attribute: " +
+                               `key`, position)
+        todo = {}
+        defineMacro = metaldict.get("define-macro")
+        useMacro = metaldict.get("use-macro")
+        defineSlot = metaldict.get("define-slot")
+        fillSlot = metaldict.get("fill-slot")
+        define = taldict.get("define")
+        condition = taldict.get("condition")
+        repeat = taldict.get("repeat")
+        content = taldict.get("content")
+        replace = taldict.get("replace")
+        attrsubst = taldict.get("attributes")
+        onError = taldict.get("on-error")
+        omitTag = taldict.get("omit-tag")
+        TALtag = taldict.get("tal tag")
+        if len(metaldict) > 1 and (defineMacro or useMacro):
+            raise METALError("define-macro and use-macro cannot be used "
+                             "together or with define-slot or fill-slot",
+                             position)
+        if content and replace:
+            raise TALError("content and replace are mutually exclusive",
+                           position)
+
+        repeatWhitespace = None
+        if repeat:
+            # Hack to include preceding whitespace in the loop program
+            repeatWhitespace = self.unEmitNewlineWhitespace()
+        if position != (None, None):
+            # XXX at some point we should insist on a non-trivial position
+            self.emit("setPosition", position)
+        if self.inMacroUse:
+            if fillSlot:
+                self.pushProgram()
+                if self.source_file is not None:
+                    self.emit("setSourceFile", self.source_file)
+                todo["fillSlot"] = fillSlot
+                self.inMacroUse = 0
+        else:
+            if fillSlot:
+                raise METALError, ("fill-slot must be within a use-macro",
+                                   position)
+        if not self.inMacroUse:
+            if defineMacro:
+                self.pushProgram()
+                self.emit("version", TAL_VERSION)
+                self.emit("mode", self.xml and "xml" or "html")
+                if self.source_file is not None:
+                    self.emit("setSourceFile", self.source_file)
+                todo["defineMacro"] = defineMacro
+                self.inMacroDef = self.inMacroDef + 1
+            if useMacro:
+                self.pushSlots()
+                self.pushProgram()
+                todo["useMacro"] = useMacro
+                self.inMacroUse = 1
+            if defineSlot:
+                if not self.inMacroDef:
+                    raise METALError, (
+                        "define-slot must be within a define-macro",
+                        position)
+                self.pushProgram()
+                todo["defineSlot"] = defineSlot
+
+        if taldict:
+            dict = {}
+            for item in attrlist:
+                key, value = item[:2]
+                dict[key] = value
+            self.emit("beginScope", dict)
+            todo["scope"] = 1
+        if onError:
+            self.pushProgram() # handler
+            self.emitStartTag(name, list(attrlist)) # Must copy attrlist!
+            self.pushProgram() # block
+            todo["onError"] = onError
+        if define:
+            self.emitDefines(define)
+            todo["define"] = define
+        if condition:
+            self.pushProgram()
+            todo["condition"] = condition
+        if repeat:
+            todo["repeat"] = repeat
+            self.pushProgram()
+            if repeatWhitespace:
+                self.emitText(repeatWhitespace)
+        if content:
+            todo["content"] = content
+        if replace:
+            todo["replace"] = replace
+            self.pushProgram()
+        optTag = omitTag is not None or TALtag
+        if optTag:
+            todo["optional tag"] = omitTag, TALtag
+            self.pushProgram()
+        if attrsubst:
+            repldict = parseAttributeReplacements(attrsubst)
+            for key, value in repldict.items():
+                repldict[key] = self.compileExpression(value)
+        else:
+            repldict = {}
+        if replace:
+            todo["repldict"] = repldict
+            repldict = {}
+        self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend)
+        if optTag:
+            self.pushProgram()
+        if content:
+            self.pushProgram()
+        if todo and position != (None, None):
+            todo["position"] = position
+        self.todoPush(todo)
+        if isend:
+            self.emitEndElement(name, isend)
+
+    def emitEndElement(self, name, isend=0, implied=0):
+        todo = self.todoPop()
+        if not todo:
+            # Shortcut
+            if not isend:
+                self.emitEndTag(name)
+            return
+
+        self.position = position = todo.get("position", (None, None))
+        defineMacro = todo.get("defineMacro")
+        useMacro = todo.get("useMacro")
+        defineSlot = todo.get("defineSlot")
+        fillSlot = todo.get("fillSlot")
+        repeat = todo.get("repeat")
+        content = todo.get("content")
+        replace = todo.get("replace")
+        condition = todo.get("condition")
+        onError = todo.get("onError")
+        define = todo.get("define")
+        repldict = todo.get("repldict", {})
+        scope = todo.get("scope")
+        optTag = todo.get("optional tag")
+
+        if implied > 0:
+            if defineMacro or useMacro or defineSlot or fillSlot:
+                exc = METALError
+                what = "METAL"
+            else:
+                exc = TALError
+                what = "TAL"
+            raise exc("%s attributes on <%s> require explicit </%s>" %
+                      (what, name, name), position)
+
+        if content:
+            self.emitSubstitution(content, {})
+        if optTag:
+            self.emitOptTag(name, optTag, isend)
+        elif not isend:
+            self.emitEndTag(name)
+        if replace:
+            self.emitSubstitution(replace, repldict)
+        if repeat:
+            self.emitRepeat(repeat)
+        if condition:
+            self.emitCondition(condition)
+        if onError:
+            self.emitOnError(name, onError)
+        if scope:
+            self.emit("endScope")
+        if defineSlot:
+            self.emitDefineSlot(defineSlot)
+        if fillSlot:
+            self.emitFillSlot(fillSlot)
+        if useMacro:
+            self.emitUseMacro(useMacro)
+        if defineMacro:
+            self.emitDefineMacro(defineMacro)
+
+def test():
+    t = TALGenerator()
+    t.pushProgram()
+    t.emit("bar")
+    p = t.popProgram()
+    t.emit("foo", p)
+
+if __name__ == "__main__":
+    test()
diff --git a/roundup/cgi/TAL/TALInterpreter.py b/roundup/cgi/TAL/TALInterpreter.py
new file mode 100644 (file)
index 0000000..0f42284
--- /dev/null
@@ -0,0 +1,626 @@
+##############################################################################
+#
+# 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
+# 
+##############################################################################
+"""
+Interpreter for a pre-compiled TAL program.
+"""
+
+import sys
+import getopt
+
+from cgi import escape
+from string import join, lower, rfind
+try:
+    from strop import lower, rfind
+except ImportError:
+    pass
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+from TALDefs import quote, TAL_VERSION, TALError, METALError
+from TALDefs import isCurrentVersion, getProgramVersion, getProgramMode
+from TALGenerator import TALGenerator
+
+BOOLEAN_HTML_ATTRS = [
+    # List of Boolean attributes in HTML that should be rendered in
+    # minimized form (e.g. <img ismap> rather than <img ismap="">)
+    # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
+    # XXX The problem with this is that this is not valid XML and
+    # can't be parsed back!
+    "compact", "nowrap", "ismap", "declare", "noshade", "checked",
+    "disabled", "readonly", "multiple", "selected", "noresize",
+    "defer"
+]
+
+EMPTY_HTML_TAGS = [
+    # List of HTML tags with an empty content model; these are
+    # rendered in minimized form, e.g. <img />.
+    # From http://www.w3.org/TR/xhtml1/#dtds
+    "base", "meta", "link", "hr", "br", "param", "img", "area",
+    "input", "col", "basefont", "isindex", "frame",
+]
+
+class AltTALGenerator(TALGenerator):
+
+    def __init__(self, repldict, expressionCompiler=None, xml=0):
+        self.repldict = repldict
+        self.enabled = 1
+        TALGenerator.__init__(self, expressionCompiler, xml)
+
+    def enable(self, enabled):
+        self.enabled = enabled
+
+    def emit(self, *args):
+        if self.enabled:
+            apply(TALGenerator.emit, (self,) + args)
+
+    def emitStartElement(self, name, attrlist, taldict, metaldict,
+                         position=(None, None), isend=0):
+        metaldict = {}
+        taldict = {}
+        if self.enabled and self.repldict:
+            taldict["attributes"] = "x x"
+        TALGenerator.emitStartElement(self, name, attrlist,
+                                      taldict, metaldict, position, isend)
+
+    def replaceAttrs(self, attrlist, repldict):
+        if self.enabled and self.repldict:
+            repldict = self.repldict
+            self.repldict = None
+        return TALGenerator.replaceAttrs(self, attrlist, repldict)
+
+
+class TALInterpreter:
+
+    def __init__(self, program, macros, engine, stream=None,
+                 debug=0, wrap=60, metal=1, tal=1, showtal=-1,
+                 strictinsert=1, stackLimit=100):
+        self.program = program
+        self.macros = macros
+        self.engine = engine
+        self.Default = engine.getDefault()
+        self.stream = stream or sys.stdout
+        self._stream_write = self.stream.write
+        self.debug = debug
+        self.wrap = wrap
+        self.metal = metal
+        self.tal = tal
+        if tal:
+            self.dispatch = self.bytecode_handlers_tal
+        else:
+            self.dispatch = self.bytecode_handlers
+        assert showtal in (-1, 0, 1)
+        if showtal == -1:
+            showtal = (not tal)
+        self.showtal = showtal
+        self.strictinsert = strictinsert
+        self.stackLimit = stackLimit
+        self.html = 0
+        self.endsep = "/>"
+        self.endlen = len(self.endsep)
+        self.macroStack = []
+        self.popMacro = self.macroStack.pop
+        self.position = None, None  # (lineno, offset)
+        self.col = 0
+        self.level = 0
+        self.scopeLevel = 0
+        self.sourceFile = None
+
+    def saveState(self):
+        return (self.position, self.col, self.stream,
+                self.scopeLevel, self.level)
+
+    def restoreState(self, state):
+        (self.position, self.col, self.stream, scopeLevel, level) = state
+        self._stream_write = self.stream.write
+        assert self.level == level
+        while self.scopeLevel > scopeLevel:
+            self.engine.endScope()
+            self.scopeLevel = self.scopeLevel - 1
+        self.engine.setPosition(self.position)
+
+    def restoreOutputState(self, state):
+        (dummy, self.col, self.stream, scopeLevel, level) = state
+        self._stream_write = self.stream.write
+        assert self.level == level
+        assert self.scopeLevel == scopeLevel
+
+    def pushMacro(self, macroName, slots, entering=1):
+        if len(self.macroStack) >= self.stackLimit:
+            raise METALError("macro nesting limit (%d) exceeded "
+                             "by %s" % (self.stackLimit, `macroName`))
+        self.macroStack.append([macroName, slots, entering])
+
+    def macroContext(self, what):
+        macroStack = self.macroStack
+        i = len(macroStack)
+        while i > 0:
+            i = i-1
+            if macroStack[i][0] == what:
+                return i
+        return -1
+
+    def __call__(self):
+        assert self.level == 0
+        assert self.scopeLevel == 0
+        self.interpret(self.program)
+        assert self.level == 0
+        assert self.scopeLevel == 0
+        if self.col > 0:
+            self._stream_write("\n")
+            self.col = 0
+
+    def stream_write(self, s,
+                     len=len, rfind=rfind):
+        self._stream_write(s)
+        i = rfind(s, '\n')
+        if i < 0:
+            self.col = self.col + len(s)
+        else:
+            self.col = len(s) - (i + 1)
+
+    bytecode_handlers = {}
+
+    def interpret(self, program, None=None):
+        oldlevel = self.level
+        self.level = oldlevel + 1
+        handlers = self.dispatch
+        try:
+            if self.debug:
+                for (opcode, args) in program:
+                    s = "%sdo_%s%s\n" % ("    "*self.level, opcode,
+                                      repr(args))
+                    if len(s) > 80:
+                        s = s[:76] + "...\n"
+                    sys.stderr.write(s)
+                    handlers[opcode](self, args)
+            else:
+                for (opcode, args) in program:
+                    handlers[opcode](self, args)
+        finally:
+            self.level = oldlevel
+
+    def do_version(self, version):
+        assert version == TAL_VERSION
+    bytecode_handlers["version"] = do_version
+
+    def do_mode(self, mode):
+        assert mode in ("html", "xml")
+        self.html = (mode == "html")
+        if self.html:
+            self.endsep = " />"
+        else:
+            self.endsep = "/>"
+        self.endlen = len(self.endsep)
+    bytecode_handlers["mode"] = do_mode
+
+    def do_setSourceFile(self, source_file):
+        self.sourceFile = source_file
+        self.engine.setSourceFile(source_file)
+    bytecode_handlers["setSourceFile"] = do_setSourceFile
+
+    def do_setPosition(self, position):
+        self.position = position
+        self.engine.setPosition(position)
+    bytecode_handlers["setPosition"] = do_setPosition
+
+    def do_startEndTag(self, stuff):
+        self.do_startTag(stuff, self.endsep, self.endlen)
+    bytecode_handlers["startEndTag"] = do_startEndTag
+
+    def do_startTag(self, (name, attrList),
+                    end=">", endlen=1, _len=len):
+        # The bytecode generator does not cause calls to this method
+        # for start tags with no attributes; those are optimized down
+        # to rawtext events.  Hence, there is no special "fast path"
+        # for that case.
+        _stream_write = self._stream_write
+        _stream_write("<" + name)
+        namelen = _len(name)
+        col = self.col + namelen + 1
+        wrap = self.wrap
+        align = col + 1
+        if align >= wrap/2:
+            align = 4  # Avoid a narrow column far to the right
+        attrAction = self.dispatch["<attrAction>"]
+        try:
+            for item in attrList:
+                if _len(item) == 2:
+                    name, s = item
+                else:
+                    ok, name, s = attrAction(self, item)
+                    if not ok:
+                        continue
+                slen = _len(s)
+                if (wrap and
+                    col >= align and
+                    col + 1 + slen > wrap):
+                    _stream_write("\n" + " "*align)
+                    col = align + slen
+                else:
+                    s = " " + s
+                    col = col + 1 + slen
+                _stream_write(s)
+            _stream_write(end)
+            col = col + endlen
+        finally:
+            self.col = col
+    bytecode_handlers["startTag"] = do_startTag
+
+    def attrAction(self, item):
+        name, value, action = item[:3]
+        if action == 1 or (action > 1 and not self.showtal):
+            return 0, name, value
+        macs = self.macroStack
+        if action == 2 and self.metal and macs:
+            if len(macs) > 1 or not macs[-1][2]:
+                # Drop all METAL attributes at a use-depth above one.
+                return 0, name, value
+            # Clear 'entering' flag
+            macs[-1][2] = 0
+            # Convert or drop depth-one METAL attributes.
+            i = rfind(name, ":") + 1
+            prefix, suffix = name[:i], name[i:]
+            if suffix == "define-macro":
+                # Convert define-macro as we enter depth one.
+                name = prefix + "use-macro"
+                value = macs[-1][0] # Macro name
+            elif suffix == "define-slot":
+                name = prefix + "slot"
+            elif suffix == "fill-slot":
+                pass
+            else:
+                return 0, name, value
+
+        if value is None:
+            value = name
+        else:
+            value = "%s=%s" % (name, quote(value))
+        return 1, name, value
+
+    def attrAction_tal(self, item):
+        name, value, action = item[:3]
+        if action > 1:
+            return self.attrAction(item)
+        ok = 1
+        if self.html and lower(name) in BOOLEAN_HTML_ATTRS:
+            evalue = self.engine.evaluateBoolean(item[3])
+            if evalue is self.Default:
+                if action == 1: # Cancelled insert
+                    ok = 0
+            elif evalue:
+                value = None
+            else:
+                ok = 0
+        else:
+            evalue = self.engine.evaluateText(item[3])
+            if evalue is self.Default:
+                if action == 1: # Cancelled insert
+                    ok = 0
+            else:
+                if evalue is None:
+                    ok = 0
+                value = evalue
+        if ok:
+            if value is None:
+                value = name
+            value = "%s=%s" % (name, quote(value))
+        return ok, name, value
+
+    bytecode_handlers["<attrAction>"] = attrAction
+
+    def no_tag(self, start, program):
+        state = self.saveState()
+        self.stream = stream = StringIO()
+        self._stream_write = stream.write
+        self.interpret(start)
+        self.restoreOutputState(state)
+        self.interpret(program)
+
+    def do_optTag(self, (name, cexpr, tag_ns, isend, start, program),
+                  omit=0):
+        if tag_ns and not self.showtal:
+            return self.no_tag(start, program)
+            
+        self.interpret(start)
+        if not isend:
+            self.interpret(program)
+            s = '</%s>' % name
+            self._stream_write(s)
+            self.col = self.col + len(s)
+
+    def do_optTag_tal(self, stuff):
+        cexpr = stuff[1]
+        if cexpr is not None and (cexpr == '' or
+                                  self.engine.evaluateBoolean(cexpr)):
+            self.no_tag(stuff[-2], stuff[-1])
+        else:
+            self.do_optTag(stuff)
+    bytecode_handlers["optTag"] = do_optTag
+
+    def dumpMacroStack(self, prefix, suffix, value):
+        sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value))
+        for i in range(len(self.macroStack)):
+            what, macroName, slots = self.macroStack[i]
+            sys.stderr.write("| %2d. %-12s %-12s %s\n" %
+                             (i, what, macroName, slots and slots.keys()))
+        sys.stderr.write("+--------------------------------------\n")
+
+    def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)):
+        self._stream_write(s)
+        self.col = col
+        self.do_setPosition(position)
+        if closeprev:
+            engine = self.engine
+            engine.endScope()
+            engine.beginScope()
+        else:
+            self.engine.beginScope()
+            self.scopeLevel = self.scopeLevel + 1
+
+    def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)):
+        self._stream_write(s)
+        self.col = col
+        self.do_setPosition(position)
+        engine = self.engine
+        if closeprev:
+            engine.endScope()
+            engine.beginScope()
+        else:
+            engine.beginScope()
+            self.scopeLevel = self.scopeLevel + 1
+        engine.setLocal("attrs", dict)
+    bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope
+
+    def do_beginScope(self, dict):
+        self.engine.beginScope()
+        self.scopeLevel = self.scopeLevel + 1
+
+    def do_beginScope_tal(self, dict):
+        engine = self.engine
+        engine.beginScope()
+        engine.setLocal("attrs", dict)
+        self.scopeLevel = self.scopeLevel + 1
+    bytecode_handlers["beginScope"] = do_beginScope
+
+    def do_endScope(self, notused=None):
+        self.engine.endScope()
+        self.scopeLevel = self.scopeLevel - 1
+    bytecode_handlers["endScope"] = do_endScope
+
+    def do_setLocal(self, notused):
+        pass
+
+    def do_setLocal_tal(self, (name, expr)):
+        self.engine.setLocal(name, self.engine.evaluateValue(expr))
+    bytecode_handlers["setLocal"] = do_setLocal
+
+    def do_setGlobal_tal(self, (name, expr)):
+        self.engine.setGlobal(name, self.engine.evaluateValue(expr))
+    bytecode_handlers["setGlobal"] = do_setLocal
+
+    def do_insertText(self, stuff):
+        self.interpret(stuff[1])
+
+    def do_insertText_tal(self, stuff):
+        text = self.engine.evaluateText(stuff[0])
+        if text is None:
+            return
+        if text is self.Default:
+            self.interpret(stuff[1])
+            return
+        s = escape(text)
+        self._stream_write(s)
+        i = rfind(s, '\n')
+        if i < 0:
+            self.col = self.col + len(s)
+        else:
+            self.col = len(s) - (i + 1)
+    bytecode_handlers["insertText"] = do_insertText
+
+    def do_insertStructure(self, stuff):
+        self.interpret(stuff[2])
+
+    def do_insertStructure_tal(self, (expr, repldict, block)):
+        structure = self.engine.evaluateStructure(expr)
+        if structure is None:
+            return
+        if structure is self.Default:
+            self.interpret(block)
+            return
+        text = str(structure)
+        if not (repldict or self.strictinsert):
+            # Take a shortcut, no error checking
+            self.stream_write(text)
+            return
+        if self.html:
+            self.insertHTMLStructure(text, repldict)
+        else:
+            self.insertXMLStructure(text, repldict)
+    bytecode_handlers["insertStructure"] = do_insertStructure
+
+    def insertHTMLStructure(self, text, repldict):
+        from HTMLTALParser import HTMLTALParser
+        gen = AltTALGenerator(repldict, self.engine, 0)
+        p = HTMLTALParser(gen) # Raises an exception if text is invalid
+        p.parseString(text)
+        program, macros = p.getCode()
+        self.interpret(program)
+
+    def insertXMLStructure(self, text, repldict):
+        from TALParser import TALParser
+        gen = AltTALGenerator(repldict, self.engine, 0)
+        p = TALParser(gen)
+        gen.enable(0)
+        p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>')
+        gen.enable(1)
+        p.parseFragment(text) # Raises an exception if text is invalid
+        gen.enable(0)
+        p.parseFragment('</foo>', 1)
+        program, macros = gen.getCode()
+        self.interpret(program)
+
+    def do_loop(self, (name, expr, block)):
+        self.interpret(block)
+
+    def do_loop_tal(self, (name, expr, block)):
+        iterator = self.engine.setRepeat(name, expr)
+        while iterator.next():
+            self.interpret(block)
+    bytecode_handlers["loop"] = do_loop
+
+    def do_rawtextColumn(self, (s, col)):
+        self._stream_write(s)
+        self.col = col
+    bytecode_handlers["rawtextColumn"] = do_rawtextColumn
+
+    def do_rawtextOffset(self, (s, offset)):
+        self._stream_write(s)
+        self.col = self.col + offset
+    bytecode_handlers["rawtextOffset"] = do_rawtextOffset
+
+    def do_condition(self, (condition, block)):
+        if not self.tal or self.engine.evaluateBoolean(condition):
+            self.interpret(block)
+    bytecode_handlers["condition"] = do_condition
+
+    def do_defineMacro(self, (macroName, macro)):
+        macs = self.macroStack
+        if len(macs) == 1:
+            entering = macs[-1][2]
+            if not entering:
+                macs.append(None)
+                self.interpret(macro)
+                macs.pop()
+                return
+        self.interpret(macro)
+    bytecode_handlers["defineMacro"] = do_defineMacro
+
+    def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)):
+        if not self.metal:
+            self.interpret(block)
+            return
+        macro = self.engine.evaluateMacro(macroExpr)
+        if macro is self.Default:
+            macro = block
+        else:
+            if not isCurrentVersion(macro):
+                raise METALError("macro %s has incompatible version %s" %
+                                 (`macroName`, `getProgramVersion(macro)`),
+                                 self.position)
+            mode = getProgramMode(macro)
+            if mode != (self.html and "html" or "xml"):
+                raise METALError("macro %s has incompatible mode %s" %
+                                 (`macroName`, `mode`), self.position)
+        self.pushMacro(macroName, compiledSlots)
+        saved_source = self.sourceFile
+        saved_position = self.position  # Used by Boa Constructor
+        self.interpret(macro)
+        if self.sourceFile != saved_source:
+            self.engine.setSourceFile(saved_source)
+            self.sourceFile = saved_source
+        self.popMacro()
+    bytecode_handlers["useMacro"] = do_useMacro
+
+    def do_fillSlot(self, (slotName, block)):
+        # This is only executed if the enclosing 'use-macro' evaluates
+        # to 'default'.
+        self.interpret(block)
+    bytecode_handlers["fillSlot"] = do_fillSlot
+
+    def do_defineSlot(self, (slotName, block)):
+        if not self.metal:
+            self.interpret(block)
+            return
+        macs = self.macroStack
+        if macs and macs[-1] is not None:
+            saved_source = self.sourceFile
+            saved_position = self.position  # Used by Boa Constructor
+            macroName, slots = self.popMacro()[:2]
+            slot = slots.get(slotName)
+            if slot is not None:
+                self.interpret(slot)
+                if self.sourceFile != saved_source:
+                    self.engine.setSourceFile(saved_source)
+                    self.sourceFile = saved_source
+                self.pushMacro(macroName, slots, entering=0)
+                return
+            self.pushMacro(macroName, slots)
+            if len(macs) == 1:
+                self.interpret(block)
+                return
+        self.interpret(block)
+    bytecode_handlers["defineSlot"] = do_defineSlot
+
+    def do_onError(self, (block, handler)):
+        self.interpret(block)
+
+    def do_onError_tal(self, (block, handler)):
+        state = self.saveState()
+        self.stream = stream = StringIO()
+        self._stream_write = stream.write
+        try:
+            self.interpret(block)
+        except:
+            exc = sys.exc_info()[1]
+            self.restoreState(state)
+            engine = self.engine
+            engine.beginScope()
+            error = engine.createErrorInfo(exc, self.position)
+            engine.setLocal('error', error)
+            try:
+                self.interpret(handler)
+            finally:
+                engine.endScope()
+        else:
+            self.restoreOutputState(state)
+            self.stream_write(stream.getvalue())
+    bytecode_handlers["onError"] = do_onError
+
+    bytecode_handlers_tal = bytecode_handlers.copy()
+    bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal
+    bytecode_handlers_tal["beginScope"] = do_beginScope_tal
+    bytecode_handlers_tal["setLocal"] = do_setLocal_tal
+    bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal
+    bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal
+    bytecode_handlers_tal["insertText"] = do_insertText_tal
+    bytecode_handlers_tal["loop"] = do_loop_tal
+    bytecode_handlers_tal["onError"] = do_onError_tal
+    bytecode_handlers_tal["<attrAction>"] = attrAction_tal
+    bytecode_handlers_tal["optTag"] = do_optTag_tal
+
+
+def test():
+    from driver import FILE, parsefile
+    from DummyEngine import DummyEngine
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "")
+    except getopt.error, msg:
+        print msg
+        sys.exit(2)
+    if args:
+        file = args[0]
+    else:
+        file = FILE
+    doc = parsefile(file)
+    compiler = TALCompiler(doc)
+    program, macros = compiler()
+    engine = DummyEngine()
+    interpreter = TALInterpreter(program, macros, engine)
+    interpreter()
+
+if __name__ == "__main__":
+    test()
diff --git a/roundup/cgi/TAL/TALParser.py b/roundup/cgi/TAL/TALParser.py
new file mode 100644 (file)
index 0000000..f75414e
--- /dev/null
@@ -0,0 +1,137 @@
+##############################################################################
+#
+# 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
+# 
+##############################################################################
+"""
+Parse XML and compile to TALInterpreter intermediate code.
+"""
+
+import string
+from XMLParser import XMLParser
+from TALDefs import *
+from TALGenerator import TALGenerator
+
+class TALParser(XMLParser):
+
+    ordered_attributes = 1
+
+    def __init__(self, gen=None): # Override
+        XMLParser.__init__(self)
+        if gen is None:
+            gen = TALGenerator()
+        self.gen = gen
+        self.nsStack = []
+        self.nsDict = {XML_NS: 'xml'}
+        self.nsNew = []
+
+    def getCode(self):
+        return self.gen.getCode()
+
+    def getWarnings(self):
+        return ()
+
+    def StartNamespaceDeclHandler(self, prefix, uri):
+        self.nsStack.append(self.nsDict.copy())
+        self.nsDict[uri] = prefix
+        self.nsNew.append((prefix, uri))
+
+    def EndNamespaceDeclHandler(self, prefix):
+        self.nsDict = self.nsStack.pop()
+
+    def StartElementHandler(self, name, attrs):
+        if self.ordered_attributes:
+            # attrs is a list of alternating names and values
+            attrlist = []
+            for i in range(0, len(attrs), 2):
+                key = attrs[i]
+                value = attrs[i+1]
+                attrlist.append((key, value))
+        else:
+            # attrs is a dict of {name: value}
+            attrlist = attrs.items()
+            attrlist.sort() # For definiteness
+        name, attrlist, taldict, metaldict = self.process_ns(name, attrlist)
+        attrlist = self.xmlnsattrs() + attrlist
+        self.gen.emitStartElement(name, attrlist, taldict, metaldict)
+
+    def process_ns(self, name, attrlist):
+        taldict = {}
+        metaldict = {}
+        fixedattrlist = []
+        name, namebase, namens = self.fixname(name)
+        for key, value in attrlist:
+            key, keybase, keyns = self.fixname(key)
+            ns = keyns or namens # default to tag namespace
+            item = key, value
+            if ns == 'metal':
+                metaldict[keybase] = value
+                item = item + ("metal",)
+            elif ns == 'tal':
+                taldict[keybase] = value
+                item = item + ("tal",)
+            fixedattrlist.append(item)
+        if namens in ('metal', 'tal'):
+            taldict['tal tag'] = namens
+        return name, fixedattrlist, taldict, metaldict
+
+    def xmlnsattrs(self):
+        newlist = []
+        for prefix, uri in self.nsNew:
+            if prefix:
+                key = "xmlns:" + prefix
+            else:
+                key = "xmlns"
+            if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS):
+                item = (key, uri, "xmlns")
+            else:
+                item = (key, uri)
+            newlist.append(item)
+        self.nsNew = []
+        return newlist
+
+    def fixname(self, name):
+        if ' ' in name:
+            uri, name = string.split(name, ' ')
+            prefix = self.nsDict[uri]
+            prefixed = name
+            if prefix:
+                prefixed = "%s:%s" % (prefix, name)
+            ns = 'x'
+            if uri == ZOPE_TAL_NS:
+                ns = 'tal'
+            elif uri == ZOPE_METAL_NS:
+                ns = 'metal'
+            return (prefixed, name, ns)
+        return (name, name, None)
+
+    def EndElementHandler(self, name):
+        name = self.fixname(name)[0]
+        self.gen.emitEndElement(name)
+
+    def DefaultHandler(self, text):
+        self.gen.emitRawText(text)
+
+def test():
+    import sys
+    p = TALParser()
+    file = "tests/input/test01.xml"
+    if sys.argv[1:]:
+        file = sys.argv[1]
+    p.parseFile(file)
+    program, macros = p.getCode()
+    from TALInterpreter import TALInterpreter
+    from DummyEngine import DummyEngine
+    engine = DummyEngine(macros)
+    TALInterpreter(program, macros, engine, sys.stdout, wrap=0)()
+
+if __name__ == "__main__":
+    test()
diff --git a/roundup/cgi/TAL/XMLParser.py b/roundup/cgi/TAL/XMLParser.py
new file mode 100644 (file)
index 0000000..98cc4d3
--- /dev/null
@@ -0,0 +1,89 @@
+##############################################################################
+#
+# 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 expat-based XML parser base class.
+"""
+
+class XMLParser:
+
+    ordered_attributes = 0
+
+    handler_names = [
+        "StartElementHandler",
+        "EndElementHandler",
+        "ProcessingInstructionHandler",
+        "CharacterDataHandler",
+        "UnparsedEntityDeclHandler",
+        "NotationDeclHandler",
+        "StartNamespaceDeclHandler",
+        "EndNamespaceDeclHandler",
+        "CommentHandler",
+        "StartCdataSectionHandler",
+        "EndCdataSectionHandler",
+        "DefaultHandler",
+        "DefaultHandlerExpand",
+        "NotStandaloneHandler",
+        "ExternalEntityRefHandler",
+        "XmlDeclHandler",
+        "StartDoctypeDeclHandler",
+        "EndDoctypeDeclHandler",
+        "ElementDeclHandler",
+        "AttlistDeclHandler"
+        ]
+
+    def __init__(self, encoding=None):
+        self.parser = p = self.createParser()
+        if self.ordered_attributes:
+            try:
+                self.parser.ordered_attributes = self.ordered_attributes
+            except AttributeError:
+                #zLOG.LOG("TAL.XMLParser", zLOG.INFO, 
+                #         "Can't set ordered_attributes")
+                self.ordered_attributes = 0
+        for name in self.handler_names:
+            method = getattr(self, name, None)
+            if method is not None:
+                try:
+                    setattr(p, name, method)
+                except AttributeError:
+                    #zLOG.LOG("TAL.XMLParser", zLOG.PROBLEM,
+                    #         "Can't set expat handler %s" % name)
+                    pass
+
+    def createParser(self, encoding=None):
+        global XMLParseError
+        try:
+            from Products.ParsedXML.Expat import pyexpat
+            XMLParseError = pyexpat.ExpatError
+            return pyexpat.ParserCreate(encoding, ' ')
+        except ImportError:
+            from xml.parsers import expat
+            XMLParseError = expat.ExpatError
+            return expat.ParserCreate(encoding, ' ')
+
+    def parseFile(self, filename):
+        self.parseStream(open(filename))
+
+    def parseString(self, s):
+        self.parser.Parse(s, 1)
+
+    def parseURL(self, url):
+        import urllib
+        self.parseStream(urllib.urlopen(url))
+
+    def parseStream(self, stream):
+        self.parser.ParseFile(stream)
+
+    def parseFragment(self, s, end=0):
+        self.parser.Parse(s, end)
diff --git a/roundup/cgi/TAL/__init__.py b/roundup/cgi/TAL/__init__.py
new file mode 100644 (file)
index 0000000..080ed5d
--- /dev/null
@@ -0,0 +1,14 @@
+##############################################################################
+#
+# 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
+# 
+##############################################################################
+""" Template Attribute Language package """
diff --git a/roundup/cgi/TAL/markupbase.py b/roundup/cgi/TAL/markupbase.py
new file mode 100644 (file)
index 0000000..eab134c
--- /dev/null
@@ -0,0 +1,306 @@
+"""Shared support for scanning document type declarations in HTML and XHTML."""
+
+import re
+import string
+
+_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*').match
+_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match
+
+del re
+
+
+class ParserBase:
+    """Parser base class which provides some common support methods used
+    by the SGML/HTML and XHTML parsers."""
+
+    def reset(self):
+        self.lineno = 1
+        self.offset = 0
+
+    def getpos(self):
+        """Return current line number and offset."""
+        return self.lineno, self.offset
+
+    # Internal -- update line number and offset.  This should be
+    # called for each piece of data exactly once, in order -- in other
+    # words the concatenation of all the input strings to this
+    # function should be exactly the entire input.
+    def updatepos(self, i, j):
+        if i >= j:
+            return j
+        rawdata = self.rawdata
+        nlines = string.count(rawdata, "\n", i, j)
+        if nlines:
+            self.lineno = self.lineno + nlines
+            pos = string.rindex(rawdata, "\n", i, j) # Should not fail
+            self.offset = j-(pos+1)
+        else:
+            self.offset = self.offset + j-i
+        return j
+
+    _decl_otherchars = ''
+
+    # Internal -- parse declaration (for use by subclasses).
+    def parse_declaration(self, i):
+        # This is some sort of declaration; in "HTML as
+        # deployed," this should only be the document type
+        # declaration ("<!DOCTYPE html...>").
+        rawdata = self.rawdata
+        import sys
+        j = i + 2
+        assert rawdata[i:j] == "<!", "unexpected call to parse_declaration"
+        if rawdata[j:j+1] in ("-", ""):
+            # Start of comment followed by buffer boundary,
+            # or just a buffer boundary.
+            return -1
+        # in practice, this should look like: ((name|stringlit) S*)+ '>'
+        n = len(rawdata)
+        decltype, j = self._scan_name(j, i)
+        if j < 0:
+            return j
+        if decltype == "doctype":
+            self._decl_otherchars = ''
+        while j < n:
+            c = rawdata[j]
+            if c == ">":
+                # end of declaration syntax
+                data = rawdata[i+2:j]
+                if decltype == "doctype":
+                    self.handle_decl(data)
+                else:
+                    self.unknown_decl(data)
+                return j + 1
+            if c in "\"'":
+                m = _declstringlit_match(rawdata, j)
+                if not m:
+                    return -1 # incomplete
+                j = m.end()
+            elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
+                name, j = self._scan_name(j, i)
+            elif c in self._decl_otherchars:
+                j = j + 1
+            elif c == "[":
+                if decltype == "doctype":
+                    j = self._parse_doctype_subset(j + 1, i)
+                else:
+                    self.error("unexpected '[' char in declaration")
+            else:
+                self.error(
+                    "unexpected %s char in declaration" % `rawdata[j]`)
+            if j < 0:
+                return j
+        return -1 # incomplete
+
+    # Internal -- scan past the internal subset in a <!DOCTYPE declaration,
+    # returning the index just past any whitespace following the trailing ']'.
+    def _parse_doctype_subset(self, i, declstartpos):
+        rawdata = self.rawdata
+        n = len(rawdata)
+        j = i
+        while j < n:
+            c = rawdata[j]
+            if c == "<":
+                s = rawdata[j:j+2]
+                if s == "<":
+                    # end of buffer; incomplete
+                    return -1
+                if s != "<!":
+                    self.updatepos(declstartpos, j + 1)
+                    self.error("unexpected char in internal subset (in %s)"
+                               % `s`)
+                if (j + 2) == n:
+                    # end of buffer; incomplete
+                    return -1
+                if (j + 4) > n:
+                    # end of buffer; incomplete
+                    return -1
+                if rawdata[j:j+4] == "<!--":
+                    j = self.parse_comment(j, report=0)
+                    if j < 0:
+                        return j
+                    continue
+                name, j = self._scan_name(j + 2, declstartpos)
+                if j == -1:
+                    return -1
+                if name not in ("attlist", "element", "entity", "notation"):
+                    self.updatepos(declstartpos, j + 2)
+                    self.error(
+                        "unknown declaration %s in internal subset" % `name`)
+                # handle the individual names
+                meth = getattr(self, "_parse_doctype_" + name)
+                j = meth(j, declstartpos)
+                if j < 0:
+                    return j
+            elif c == "%":
+                # parameter entity reference
+                if (j + 1) == n:
+                    # end of buffer; incomplete
+                    return -1
+                s, j = self._scan_name(j + 1, declstartpos)
+                if j < 0:
+                    return j
+                if rawdata[j] == ";":
+                    j = j + 1
+            elif c == "]":
+                j = j + 1
+                while j < n and rawdata[j] in string.whitespace:
+                    j = j + 1
+                if j < n:
+                    if rawdata[j] == ">":
+                        return j
+                    self.updatepos(declstartpos, j)
+                    self.error("unexpected char after internal subset")
+                else:
+                    return -1
+            elif c in string.whitespace:
+                j = j + 1
+            else:
+                self.updatepos(declstartpos, j)
+                self.error("unexpected char %s in internal subset" % `c`)
+        # end of buffer reached
+        return -1
+
+    # Internal -- scan past <!ELEMENT declarations
+    def _parse_doctype_element(self, i, declstartpos):
+        rawdata = self.rawdata
+        n = len(rawdata)
+        name, j = self._scan_name(i, declstartpos)
+        if j == -1:
+            return -1
+        # style content model; just skip until '>'
+        if '>' in rawdata[j:]:
+            return string.find(rawdata, ">", j) + 1
+        return -1
+
+    # Internal -- scan past <!ATTLIST declarations
+    def _parse_doctype_attlist(self, i, declstartpos):
+        rawdata = self.rawdata
+        name, j = self._scan_name(i, declstartpos)
+        c = rawdata[j:j+1]
+        if c == "":
+            return -1
+        if c == ">":
+            return j + 1
+        while 1:
+            # scan a series of attribute descriptions; simplified:
+            #   name type [value] [#constraint]
+            name, j = self._scan_name(j, declstartpos)
+            if j < 0:
+                return j
+            c = rawdata[j:j+1]
+            if c == "":
+                return -1
+            if c == "(":
+                # an enumerated type; look for ')'
+                if ")" in rawdata[j:]:
+                    j = string.find(rawdata, ")", j) + 1
+                else:
+                    return -1
+                while rawdata[j:j+1] in string.whitespace:
+                    j = j + 1
+                if not rawdata[j:]:
+                    # end of buffer, incomplete
+                    return -1
+            else:
+                name, j = self._scan_name(j, declstartpos)
+            c = rawdata[j:j+1]
+            if not c:
+                return -1
+            if c in "'\"":
+                m = _declstringlit_match(rawdata, j)
+                if m:
+                    j = m.end()
+                else:
+                    return -1
+                c = rawdata[j:j+1]
+                if not c:
+                    return -1
+            if c == "#":
+                if rawdata[j:] == "#":
+                    # end of buffer
+                    return -1
+                name, j = self._scan_name(j + 1, declstartpos)
+                if j < 0:
+                    return j
+                c = rawdata[j:j+1]
+                if not c:
+                    return -1
+            if c == '>':
+                # all done
+                return j + 1
+
+    # Internal -- scan past <!NOTATION declarations
+    def _parse_doctype_notation(self, i, declstartpos):
+        name, j = self._scan_name(i, declstartpos)
+        if j < 0:
+            return j
+        rawdata = self.rawdata
+        while 1:
+            c = rawdata[j:j+1]
+            if not c:
+                # end of buffer; incomplete
+                return -1
+            if c == '>':
+                return j + 1
+            if c in "'\"":
+                m = _declstringlit_match(rawdata, j)
+                if not m:
+                    return -1
+                j = m.end()
+            else:
+                name, j = self._scan_name(j, declstartpos)
+                if j < 0:
+                    return j
+
+    # Internal -- scan past <!ENTITY declarations
+    def _parse_doctype_entity(self, i, declstartpos):
+        rawdata = self.rawdata
+        if rawdata[i:i+1] == "%":
+            j = i + 1
+            while 1:
+                c = rawdata[j:j+1]
+                if not c:
+                    return -1
+                if c in string.whitespace:
+                    j = j + 1
+                else:
+                    break
+        else:
+            j = i
+        name, j = self._scan_name(j, declstartpos)
+        if j < 0:
+            return j
+        while 1:
+            c = self.rawdata[j:j+1]
+            if not c:
+                return -1
+            if c in "'\"":
+                m = _declstringlit_match(rawdata, j)
+                if m:
+                    j = m.end()
+                else:
+                    return -1    # incomplete
+            elif c == ">":
+                return j + 1
+            else:
+                name, j = self._scan_name(j, declstartpos)
+                if j < 0:
+                    return j
+
+    # Internal -- scan a name token and the new position and the token, or
+    # return -1 if we've reached the end of the buffer.
+    def _scan_name(self, i, declstartpos):
+        rawdata = self.rawdata
+        n = len(rawdata)
+        if i == n:
+            return None, -1
+        m = _declname_match(rawdata, i)
+        if m:
+            s = m.group()
+            name = string.strip(s)
+            if (i + len(s)) == n:
+                return None, -1  # end of buffer
+            return string.lower(name), m.end()
+        else:
+            self.updatepos(declstartpos, i)
+            self.error("expected name token", self.getpos())
diff --git a/roundup/cgi/ZTUtils/.cvsignore b/roundup/cgi/ZTUtils/.cvsignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/roundup/cgi/ZTUtils/Batch.py b/roundup/cgi/ZTUtils/Batch.py
new file mode 100644 (file)
index 0000000..713d771
--- /dev/null
@@ -0,0 +1,119 @@
+##############################################################################
+#
+# 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__='''Batch class, for iterating over a sequence in batches
+
+$Id: Batch.py,v 1.1 2002-09-05 00:37:09 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+class LazyPrevBatch:
+    def __of__(self, parent):
+        return Batch(parent._sequence, parent._size,
+                     parent.first - parent._size + parent.overlap, 0,
+                     parent.orphan, parent.overlap)
+
+class LazyNextBatch:
+    def __of__(self, parent):
+        try: parent._sequence[parent.end]
+        except IndexError: return None
+        return Batch(parent._sequence, parent._size,
+                     parent.end - parent.overlap, 0,
+                     parent.orphan, parent.overlap)
+
+class LazySequenceLength:
+    def __of__(self, parent):
+        parent.sequence_length = l = len(parent._sequence)
+        return l
+
+class Batch:
+    """Create a sequence batch"""
+    __allow_access_to_unprotected_subobjects__ = 1
+
+    previous = LazyPrevBatch()
+    next = LazyNextBatch()
+    sequence_length = LazySequenceLength()
+
+    def __init__(self, sequence, size, start=0, end=0,
+                 orphan=0, overlap=0):
+        '''Encapsulate "sequence" in batches of "size".
+
+        Arguments: "start" and "end" are 0-based indexes into the
+        sequence.  If the next batch would contain no more than
+        "orphan" elements, it is combined with the current batch.
+        "overlap" is the number of elements shared by adjacent
+        batches.  If "size" is not specified, it is computed from
+        "start" and "end".  Failing that, it is 7.
+
+        Attributes: Note that the "start" attribute, unlike the
+        argument, is a 1-based index (I know, lame).  "first" is the
+        0-based index.  "length" is the actual number of elements in
+        the batch.
+
+        "sequence_length" is the length of the original, unbatched, sequence
+        '''
+
+        start = start + 1
+
+        start,end,sz = opt(start,end,size,orphan,sequence)
+
+        self._sequence = sequence
+        self.size = sz
+        self._size = size
+        self.start = start
+        self.end = end
+        self.orphan = orphan
+        self.overlap = overlap
+        self.first = max(start - 1, 0)
+        self.length = self.end - self.first
+        if self.first == 0:
+            self.previous = None
+
+
+    def __getitem__(self, index):
+        if index < 0:
+            if index + self.end < self.first: raise IndexError, index
+            return self._sequence[index + self.end]
+        
+        if index >= self.length: raise IndexError, index
+        return self._sequence[index+self.first]
+
+    def __len__(self):
+        return self.length
+
+def opt(start,end,size,orphan,sequence):
+    if size < 1:
+        if start > 0 and end > 0 and end >= start:
+            size=end+1-start
+        else: size=7
+
+    if start > 0:
+
+        try: sequence[start-1]
+        except IndexError: start=len(sequence)
+
+        if end > 0:
+            if end < start: end=start
+        else:
+            end=start+size-1
+            try: sequence[end+orphan-1]
+            except IndexError: end=len(sequence)
+    elif end > 0:
+        try: sequence[end-1]
+        except IndexError: end=len(sequence)
+        start=end+1-size
+        if start - 1 < orphan: start=1
+    else:
+        start=1
+        end=start+size-1
+        try: sequence[end+orphan-1]
+        except IndexError: end=len(sequence)
+    return start,end,size
diff --git a/roundup/cgi/ZTUtils/Iterator.py b/roundup/cgi/ZTUtils/Iterator.py
new file mode 100644 (file)
index 0000000..d13b5c2
--- /dev/null
@@ -0,0 +1,198 @@
+##############################################################################
+#
+# 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__='''Iterator class
+
+Unlike the builtin iterators of Python 2.2+, these classes are
+designed to maintain information about the state of an iteration.
+The Iterator() function accepts either a sequence or a Python
+iterator.  The next() method fetches the next item, and returns
+true if it succeeds.
+
+$Id: Iterator.py,v 1.1 2002-09-05 00:37:09 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+import string
+
+class Iterator:
+    '''Simple Iterator class'''
+
+    __allow_access_to_unprotected_subobjects__ = 1
+
+    nextIndex = 0
+    def __init__(self, seq):
+        self.seq = seq
+        for inner in seqInner, iterInner:
+            if inner._supports(seq):
+                self._inner = inner
+                self._prep_next = inner.prep_next
+                return
+        raise TypeError, "Iterator does not support %s" % `seq`
+
+    def __getattr__(self, name):
+        try:
+            inner = getattr(self._inner, 'it_' + name)
+        except AttributeError:
+            raise AttributeError, name
+        return inner(self)
+
+    def next(self):
+        if not (hasattr(self, '_next') or self._prep_next(self)):
+            return 0
+        self.index = i = self.nextIndex
+        self.nextIndex = i+1
+        self._advance(self)
+        return 1
+
+    def _advance(self, it):
+        self.item = self._next
+        del self._next
+        del self.end
+        self._advance = self._inner.advance
+        self.start = 1
+            
+    def number(self): return self.nextIndex
+
+    def even(self): return not self.index % 2
+
+    def odd(self): return self.index % 2
+
+    def letter(self, base=ord('a'), radix=26):
+        index = self.index
+        s = ''
+        while 1:
+            index, off = divmod(index, radix)
+            s = chr(base + off) + s
+            if not index: return s
+
+    def Letter(self):
+        return self.letter(base=ord('A'))
+
+    def Roman(self, rnvalues=(
+                    (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'),
+                    (100,'C'),(90,'XC'),(50,'L'),(40,'XL'),
+                    (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ):
+        n = self.index + 1
+        s = ''
+        for v, r in rnvalues:
+            rct, n = divmod(n, v)
+            s = s + r * rct
+        return s
+
+    def roman(self, lower=string.lower):
+        return lower(self.Roman())
+
+    def first(self, name=None):
+        if self.start: return 1
+        return not self.same_part(name, self._last, self.item)
+
+    def last(self, name=None):
+        if self.end: return 1
+        return not self.same_part(name, self.item, self._next)
+
+    def same_part(self, name, ob1, ob2):
+        if name is None:
+            return ob1 == ob2
+        no = []
+        return getattr(ob1, name, no) == getattr(ob2, name, no) is not no
+
+    def __iter__(self):
+        return IterIter(self)
+
+class InnerBase:
+    '''Base Inner class for Iterators'''
+    # Prep sets up ._next and .end
+    def prep_next(self, it):
+        it.next = self.no_next
+        it.end = 1
+        return 0
+
+    # Advance knocks them down
+    def advance(self, it):
+        it._last = it.item
+        it.item = it._next
+        del it._next
+        del it.end
+        it.start = 0
+            
+    def no_next(self, it):
+        return 0
+
+    def it_end(self, it):
+        if hasattr(it, '_next'):
+            return 0
+        return not self.prep_next(it)
+
+class SeqInner(InnerBase):
+    '''Inner class for sequence Iterators'''
+
+    def _supports(self, ob):
+        try: ob[0]
+        except TypeError: return 0
+        except: pass
+        return 1
+
+    def prep_next(self, it):
+        i = it.nextIndex
+        try:
+            it._next = it.seq[i]
+        except IndexError:
+            it._prep_next = self.no_next
+            it.end = 1
+            return 0
+        it.end = 0
+        return 1
+
+    def it_length(self, it):
+        it.length = l = len(it.seq)
+        return l
+
+try:
+    StopIteration=StopIteration
+except NameError:
+    StopIteration="StopIteration"
+
+class IterInner(InnerBase):
+    '''Iterator inner class for Python iterators'''
+
+    def _supports(self, ob):
+        try:
+            if hasattr(ob, 'next') and (ob is iter(ob)):
+                return 1
+        except:
+            return 0
+
+    def prep_next(self, it):
+        try:
+            it._next = it.seq.next()
+        except StopIteration:
+            it._prep_next = self.no_next
+            it.end = 1
+            return 0
+        it.end = 0
+        return 1
+
+class IterIter:
+    def __init__(self, it):
+        self.it = it
+        self.skip = it.nextIndex > 0 and not it.end
+    def next(self):
+        it = self.it
+        if self.skip:
+            self.skip = 0
+            return it.item
+        if it.next():
+            return it.item
+        raise StopIteration
+
+seqInner = SeqInner()
+iterInner = IterInner()
diff --git a/roundup/cgi/ZTUtils/__init__.py b/roundup/cgi/ZTUtils/__init__.py
new file mode 100644 (file)
index 0000000..f9eb32c
--- /dev/null
@@ -0,0 +1,20 @@
+##############################################################################
+#
+# 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 of template utility classes and functions.
+
+$Id: __init__.py,v 1.1 2002-09-05 00:37:09 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+from Batch import Batch
+from Iterator import Iterator
+
index aea865120c74e74ecc7e398f55453e46c2ceac06..d7afb83a559c1e3a929fc898827aa552e07fd538 100644 (file)
@@ -16,31 +16,11 @@ try:
 except ImportError:
     StructuredText = None
 
-# Make sure these modules are loaded
-# I need these to run PageTemplates outside of Zope :(
-# If we're running in a Zope environment, these modules will be loaded
-# already...
-if not sys.modules.has_key('zLOG'):
-    import zLOG
-    sys.modules['zLOG'] = zLOG
-if not sys.modules.has_key('MultiMapping'):
-    import MultiMapping
-    sys.modules['MultiMapping'] = MultiMapping
-if not sys.modules.has_key('ComputedAttribute'):
-    import ComputedAttribute
-    sys.modules['ComputedAttribute'] = ComputedAttribute
-if not sys.modules.has_key('ExtensionClass'):
-    import ExtensionClass
-    sys.modules['ExtensionClass'] = ExtensionClass
-if not sys.modules.has_key('Acquisition'):
-    import Acquisition
-    sys.modules['Acquisition'] = Acquisition
-
-# now it's safe to import PageTemplates, TAL and ZTUtils
-from PageTemplates import PageTemplate
-from PageTemplates.Expressions import getEngine
-from TAL.TALInterpreter import TALInterpreter
-import ZTUtils
+# bring in the templating support
+from roundup.cgi.PageTemplates import PageTemplate
+from roundup.cgi.PageTemplates.Expressions import getEngine
+from roundup.cgi.TAL.TALInterpreter import TALInterpreter
+from roundup.cgi import ZTUtils
 
 # XXX WAH pagetemplates aren't pickleable :(
 #def getTemplate(dir, name, classname=None, request=None):