summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 6039b1d)
raw | patch | inline | side by side (parent: 6039b1d)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Thu, 5 Sep 2002 00:37:09 +0000 (00:37 +0000) | ||
committer | richard <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:
diff --git a/PageTemplates/.cvsignore b/PageTemplates/.cvsignore
--- a/PageTemplates/.cvsignore
+++ /dev/null
@@ -1 +0,0 @@
-*.pyc
diff --git a/PageTemplates/Expressions.py b/PageTemplates/Expressions.py
+++ /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
+++ /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
+++ /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
+++ /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
--- a/PageTemplates/README.txt
+++ /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
--- a/PageTemplates/TALES.py
+++ /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
+++ /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
--- a/TAL/.cvsignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.path
-*.pyc
diff --git a/TAL/HTMLParser.py b/TAL/HTMLParser.py
--- a/TAL/HTMLParser.py
+++ /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, "<", "<")
- s = string.replace(s, ">", ">")
- s = string.replace(s, "'", "'")
- s = string.replace(s, """, '"')
- s = string.replace(s, "&", "&") # Must be last
- return s
diff --git a/TAL/HTMLTALParser.py b/TAL/HTMLTALParser.py
--- a/TAL/HTMLTALParser.py
+++ /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
--- a/TAL/README.txt
+++ /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
--- a/TAL/TALDefs.py
+++ /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
--- a/TAL/TALGenerator.py
+++ /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
--- a/TAL/TALInterpreter.py
+++ /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
--- a/TAL/TALParser.py
+++ /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
--- a/TAL/XMLParser.py
+++ /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
--- a/TAL/__init__.py
+++ /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
--- a/TAL/markupbase.py
+++ /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())
diff --git a/TODO.txt b/TODO.txt
index eb6482c3a7c3bcaffb7018aaadd9218891b70d93..c707f7d7022ba0f28c1bb4c8e73b32d98b0293db 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
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
--- a/ZTUtils/.cvsignore
+++ /dev/null
@@ -1 +0,0 @@
-*.pyc
diff --git a/ZTUtils/Batch.py b/ZTUtils/Batch.py
--- a/ZTUtils/Batch.py
+++ /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
--- a/ZTUtils/CHANGES.txt
+++ /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
--- a/ZTUtils/HISTORY.txt
+++ /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
--- a/ZTUtils/Iterator.py
+++ /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
--- a/ZTUtils/SimpleTree.py
+++ /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': ' '}
-
- 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
--- a/ZTUtils/Tree.py
+++ /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
--- a/ZTUtils/Zope.py
+++ /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
--- a/ZTUtils/__init__.py
+++ /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
-
diff --git a/doc/installation.txt b/doc/installation.txt
index f98e7766d5d1c8bc2843291518d35e28bdd0cc49..5ab4af2eb123c4d287aae3e74f03664e5ffac02e 100644 (file)
--- a/doc/installation.txt
+++ b/doc/installation.txt
Installing Roundup
==================
-:Version: $Revision: 1.19 $
+:Version: $Revision: 1.20 $
.. contents::
============
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::
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:
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)
# 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
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):
#
#$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)
-# $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
============
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):
#
# $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
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/roundup/cgi/PageTemplates/ComputedAttribute.py b/roundup/cgi/PageTemplates/ComputedAttribute.py
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /dev/null
@@ -0,0 +1,2 @@
+.path
+*.pyc
diff --git a/roundup/cgi/TAL/HTMLParser.py b/roundup/cgi/TAL/HTMLParser.py
--- /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, "<", "<")
+ s = string.replace(s, ">", ">")
+ s = string.replace(s, "'", "'")
+ s = string.replace(s, """, '"')
+ s = string.replace(s, "&", "&") # Must be last
+ return s
diff --git a/roundup/cgi/TAL/HTMLTALParser.py b/roundup/cgi/TAL/HTMLTALParser.py
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/roundup/cgi/ZTUtils/Batch.py b/roundup/cgi/ZTUtils/Batch.py
--- /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
--- /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
--- /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)
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):