[Zope3-checkins] CVS: Zope3/src/zope/tal - .cvsignore:1.2 __init__.py:1.2 changes.txt:1.2 driver.py:1.2 dummyengine.py:1.2 history.txt:1.2 htmltalparser.py:1.2 interfaces.py:1.2 ndiff.py:1.2 readme.txt:1.2 runtest.py:1.2 setpath.py:1.2 taldefs.py:1.2 talgenerator.py:1.2 talgettext.py:1.2 talinterpreter.py:1.2 talparser.py:1.2 timer.py:1.2 translationcontext.py:1.2 xmlparser.py:1.2

Jim Fulton jim@zope.com
Wed, 25 Dec 2002 09:16:00 -0500


Update of /cvs-repository/Zope3/src/zope/tal
In directory cvs.zope.org:/tmp/cvs-serv20790/src/zope/tal

Added Files:
	.cvsignore __init__.py changes.txt driver.py dummyengine.py 
	history.txt htmltalparser.py interfaces.py ndiff.py readme.txt 
	runtest.py setpath.py taldefs.py talgenerator.py talgettext.py 
	talinterpreter.py talparser.py timer.py translationcontext.py 
	xmlparser.py 
Log Message:
Grand renaming:

- Renamed most files (especially python modules) to lower case.

- Moved views and interfaces into separate hierarchies within each
  project, where each top-level directory under the zope package
  is a separate project.

- Moved everything to src from lib/python.

  lib/python will eventually go away. I need access to the cvs
  repository to make this happen, however.

There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.



=== Zope3/src/zope/tal/.cvsignore 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/.cvsignore	Wed Dec 25 09:15:29 2002
@@ -0,0 +1 @@
+.path


=== Zope3/src/zope/tal/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/__init__.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/tal/changes.txt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/changes.txt	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,11 @@
+TAL 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.0
+
+      Features Added
+
+        - Line and column numbers are added to more exceptions.


=== Zope3/src/zope/tal/driver.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/driver.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,197 @@
+#!/usr/bin/env python
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+Driver program to test METAL and TAL implementation.
+
+Usage: driver.py [options] [file]
+Options:
+    -h / --help
+        Print this message and exit.
+    -H / --html
+    -x / --xml
+        Explicitly choose HTML or XML input.  The default is to automatically
+        select based on the file extension.  These options are mutually
+        exclusive.
+    -l
+        Lenient structure insertion.
+    -m
+        Macro expansion only
+    -s
+        Print intermediate opcodes only
+    -t
+        Leave TAL/METAL attributes in output
+    -i
+        Leave I18N substitution strings un-interpolated.
+"""
+
+import os
+import sys
+
+import getopt
+
+if __name__ == "__main__":
+    import setpath                      # Local hack to tweak sys.path etc.
+
+# Import local classes
+import zope.tal.taldefs
+from zope.tal.dummyengine import DummyEngine
+from zope.tal.dummyengine import DummyTranslationService
+
+FILE = "tests/input/test01.xml"
+
+class TestTranslations(DummyTranslationService):
+    def translate(self, domain, msgid, mapping=None, context=None,
+                  target_language=None):
+        if msgid == 'timefmt':
+            return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping
+        elif msgid == 'jobnum':
+            return '%(jobnum)s is the JOB NUMBER' % mapping
+        elif msgid == 'verify':
+            s = 'Your contact email address is recorded as %(email)s'
+            return s % mapping
+        elif msgid == 'mailto:${request/submitter}':
+            return 'mailto:bperson@dom.ain'
+        elif msgid == 'origin':
+            return '%(name)s was born in %(country)s' % mapping
+        return DummyTranslationService.translate(self, domain, msgid,
+                                                 mapping, context,
+                                                 target_language)
+
+class TestEngine(DummyEngine):
+    def __init__(self, macros=None):
+        DummyEngine.__init__(self, macros)
+        self.translationService = TestTranslations()
+
+    def evaluatePathOrVar(self, expr):
+        if expr == 'here/currentTime':
+            return {'hours'  : 6,
+                    'minutes': 59,
+                    'ampm'   : 'PM',
+                    }
+        elif expr == 'context/@@object_name':
+            return '7'
+        elif expr == 'request/submitter':
+            return 'aperson@dom.ain'
+        return DummyEngine.evaluatePathOrVar(self, expr)
+
+
+# This is a disgusting hack so that we can use engines that actually know
+# something about certain object paths.  TimeEngine knows about
+# here/currentTime.
+ENGINES = {'test23.html': TestEngine,
+           'test24.html': TestEngine,
+           'test26.html': TestEngine,
+           'test27.html': TestEngine,
+           'test28.html': TestEngine,
+           'test29.html': TestEngine,
+           'test30.html': TestEngine,
+           'test31.html': TestEngine,
+           'test32.html': TestEngine,
+           }
+
+def usage(code, msg=''):
+    # Python 2.1 required
+    print >> sys.stderr, __doc__
+    if msg:
+        print >> sys.stderr, msg
+    sys.exit(code)
+
+def main():
+    macros = 0
+    mode = None
+    showcode = 0
+    showtal = -1
+    strictinsert = 1
+    i18nInterpolate = 1
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hHxlmsti",
+                                   ['help', 'html', 'xml'])
+    except getopt.error, msg:
+        usage(2, msg)
+    for opt, arg in opts:
+        if opt in ('-h', '--help'):
+            usage(0)
+        if opt in ('-H', '--html'):
+            if mode == 'xml':
+                usage(1, '--html and --xml are mutually exclusive')
+            mode = "html"
+        if opt == '-l':
+            strictinsert = 0
+        if opt == '-m':
+            macros = 1
+        if opt == '-n':
+            versionTest = 0
+        if opt in ('-x', '--xml'):
+            if mode == 'html':
+                usage(1, '--html and --xml are mutually exclusive')
+            mode = "xml"
+        if opt == '-s':
+            showcode = 1
+        if opt == '-t':
+            showtal = 1
+        if opt == '-i':
+            i18nInterpolate = 0
+    if args:
+        file = args[0]
+    else:
+        file = FILE
+    it = compilefile(file, mode)
+    if showcode:
+        showit(it)
+    else:
+        # See if we need a special engine for this test
+        engine = None
+        engineClass = ENGINES.get(os.path.basename(file))
+        if engineClass is not None:
+            engine = engineClass(macros)
+        interpretit(it, engine=engine,
+                    tal=(not macros), showtal=showtal,
+                    strictinsert=strictinsert,
+                    i18nInterpolate=i18nInterpolate)
+
+def interpretit(it, engine=None, stream=None, tal=1, showtal=-1,
+                strictinsert=1, i18nInterpolate=1):
+    from zope.tal.talinterpreter import TALInterpreter
+    program, macros = it
+    assert zope.tal.taldefs.isCurrentVersion(program)
+    if engine is None:
+        engine = DummyEngine(macros)
+    TALInterpreter(program, macros, engine, stream, wrap=0,
+                   tal=tal, showtal=showtal, strictinsert=strictinsert,
+                   i18nInterpolate=i18nInterpolate)()
+
+def compilefile(file, mode=None):
+    assert mode in ("html", "xml", None)
+    if mode is None:
+        ext = os.path.splitext(file)[1]
+        if ext.lower() in (".html", ".htm"):
+            mode = "html"
+        else:
+            mode = "xml"
+    if mode == "html":
+        from zope.tal.htmltalparser import HTMLTALParser
+        p = HTMLTALParser()
+    else:
+        from zope.tal.talparser import TALParser
+        p = TALParser()
+    p.parseFile(file)
+    return p.getCode()
+
+def showit(it):
+    from pprint import pprint
+    pprint(it)
+
+if __name__ == "__main__":
+    main()


=== Zope3/src/zope/tal/dummyengine.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/dummyengine.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,241 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+Dummy TALES engine so that I can test out the TAL implementation.
+"""
+
+import re
+import sys
+
+from zope.tal.taldefs import NAME_RE, TALESError, ErrorInfo
+from zope.tal.interfaces import ITALESCompiler, ITALESEngine
+from zope.interfaces.i18n import ITranslationService
+from zope.interfaces.i18n import IDomain
+
+Default = object()
+
+name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match
+
+class CompilerError(Exception):
+    pass
+
+class DummyEngine:
+
+    position = None
+    source_file = None
+
+    __implements__ = ITALESCompiler, ITALESEngine
+
+    def __init__(self, macros=None):
+        if macros is None:
+            macros = {}
+        self.macros = macros
+        dict = {'nothing': None, 'default': Default}
+        self.locals = self.globals = dict
+        self.stack = [dict]
+        self.translationService = DummyTranslationService()
+
+    def getCompilerError(self):
+        return CompilerError
+
+    def setSourceFile(self, source_file):
+        self.source_file = source_file
+
+    def setPosition(self, position):
+        self.position = position
+
+    def compile(self, expr):
+        return "$%s$" % expr
+
+    def uncompile(self, expression):
+        assert (expression.startswith("$") and expression.endswith("$"),
+            expression)
+        return expression[1:-1]
+
+    def beginScope(self):
+        self.stack.append(self.locals)
+
+    def endScope(self):
+        assert len(self.stack) > 1, "more endScope() than beginScope() calls"
+        self.locals = self.stack.pop()
+
+    def setLocal(self, name, value):
+        if self.locals is self.stack[-1]:
+            # Unmerge this scope's locals from previous scope of first set
+            self.locals = self.locals.copy()
+        self.locals[name] = value
+
+    def setGlobal(self, name, value):
+        self.globals[name] = value
+
+    def evaluate(self, expression):
+        assert (expression.startswith("$") and expression.endswith("$"),
+            expression)
+        expression = expression[1:-1]
+        m = name_match(expression)
+        if m:
+            type, expr = m.group(1, 2)
+        else:
+            type = "path"
+            expr = expression
+        if type in ("string", "str"):
+            return expr
+        if type in ("path", "var", "global", "local"):
+            return self.evaluatePathOrVar(expr)
+        if type == "not":
+            return not self.evaluate(expr)
+        if type == "exists":
+            return self.locals.has_key(expr) or self.globals.has_key(expr)
+        if type == "python":
+            try:
+                return eval(expr, self.globals, self.locals)
+            except:
+                raise TALESError("evaluation error in %s" % `expr`)
+        if type == "position":
+            # Insert the current source file name, line number,
+            # and column offset.
+            if self.position:
+                lineno, offset = self.position
+            else:
+                lineno, offset = None, None
+            return '%s (%s,%s)' % (self.source_file, lineno, offset)
+        raise TALESError("unrecognized expression: " + `expression`)
+
+    def evaluatePathOrVar(self, expr):
+        expr = expr.strip()
+        if self.locals.has_key(expr):
+            return self.locals[expr]
+        elif self.globals.has_key(expr):
+            return self.globals[expr]
+        else:
+            raise TALESError("unknown variable: %s" % `expr`)
+
+    def evaluateValue(self, expr):
+        return self.evaluate(expr)
+
+    def evaluateBoolean(self, expr):
+        return self.evaluate(expr)
+
+    def evaluateText(self, expr):
+        text = self.evaluate(expr)
+        if text is not None and text is not Default:
+            text = str(text)
+        return text
+
+    def evaluateStructure(self, expr):
+        # XXX Should return None or a DOM tree
+        return self.evaluate(expr)
+
+    def evaluateSequence(self, expr):
+        # XXX Should return a sequence
+        return self.evaluate(expr)
+
+    def evaluateMacro(self, macroName):
+        assert (macroName.startswith("$") and macroName.endswith("$"),
+            macroName)
+        macroName = macroName[1:-1]
+        file, localName = self.findMacroFile(macroName)
+        if not file:
+            # Local macro
+            macro = self.macros[localName]
+        else:
+            # External macro
+            import driver
+            program, macros = driver.compilefile(file)
+            macro = macros.get(localName)
+            if not macro:
+                raise TALESError("macro %s not found in file %s" %
+                                 (localName, file))
+        return macro
+
+    def findMacroDocument(self, macroName):
+        file, localName = self.findMacroFile(macroName)
+        if not file:
+            return file, localName
+        import driver
+        doc = driver.parsefile(file)
+        return doc, localName
+
+    def findMacroFile(self, macroName):
+        if not macroName:
+            raise TALESError("empty macro name")
+        i = macroName.rfind('/')
+        if i < 0:
+            # No slash -- must be a locally defined macro
+            return None, macroName
+        else:
+            # Up to last slash is the filename
+            fileName = macroName[:i]
+            localName = macroName[i+1:]
+            return fileName, localName
+
+    def setRepeat(self, name, expr):
+        seq = self.evaluateSequence(expr)
+        return Iterator(name, seq, self)
+
+    def createErrorInfo(self, err, position):
+        return ErrorInfo(err, position)
+
+    def getDefault(self):
+        return Default
+
+    def translate(self, domain, msgid, mapping):
+        return self.translationService.translate(domain, msgid, mapping)
+
+
+class Iterator:
+
+    def __init__(self, name, seq, engine):
+        self.name = name
+        self.seq = seq
+        self.engine = engine
+        self.nextIndex = 0
+
+    def next(self):
+        i = self.nextIndex
+        try:
+            item = self.seq[i]
+        except IndexError:
+            return 0
+        self.nextIndex = i+1
+        self.engine.setLocal(self.name, item)
+        return 1
+
+class DummyDomain:
+    __implements__ = IDomain
+
+    def translate(self, msgid, mapping=None, context=None,
+                  target_language=None):
+        # This is a fake translation service which simply uppercases non
+        # ${name} placeholder text in the message id.
+        #
+        # First, transform a string with ${name} placeholders into a list of
+        # substrings.  Then upcase everything but the placeholders, then glue
+        # things back together.
+        def repl(m):
+            return mapping[m.group(m.lastindex).lower()]
+        cre = re.compile(r'\$(?:([_A-Z]\w*)|\{([_A-Z]\w*)\})')
+        return cre.sub(repl, msgid.upper())
+
+class DummyTranslationService:
+    __implements__ = ITranslationService
+
+    def translate(self, domain, msgid, mapping=None, context=None,
+                  target_language=None):
+        # Ignore domain
+        return self.getDomain(domain).translate(msgid, mapping, context,
+                                                target_language)
+
+    def getDomain(self, domain):
+        return DummyDomain()


=== Zope3/src/zope/tal/history.txt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/history.txt	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,72 @@
+TAL history
+
+  This file contains change information for previous versions.
+  Change information for the current release can be found
+  in the file CHANGES.txt.
+
+    Version 1.4.0
+
+      Features Added
+
+        - Added TAL statement: omit_tag="[<boolean expr>]" replaces
+          the statement tag with its contents if the boolean
+          expression is true or omitted.
+
+        - The TAL and METAL namespaces can be applied to tag names,
+          tags in these namespaces are removed from rendered output
+          (leaving the contents in place, as with omit_tag)
+          whenever attributes in these namespaces would be, and
+          tag attributes without explicit namespaces default to the
+          tag's namespace (per XML spec).
+
+    Version 1.3.3
+
+      Bugs Fixed
+
+        - tal:atributes was creating stray attributes in METAL
+          expansion, and there was no unit test for this behavior.
+
+        - tal:attributes parsing was not catching badly malformed
+          values, and used "print" instead of raising exceptions.
+
+    Version 1.3.2
+
+      Features Added
+
+        - Adopted Zope-style CHANGES.txt and HISTORY.txt
+        - Improved execution performance
+        - Added simple ZPT vs. TAL vs. DTML benchmarks, run by markbench.py
+
+    Version 1.3.0
+
+      Features Added
+
+        - New builtin variable 'attrs'.
+
+      Bug Fixed
+
+        - Nested macros were not working correctly.
+
+    Version 1.2.0
+
+      Features Added
+
+        - The 'if' path modifier can cancel any TAL action.
+
+      Bug Fixed
+
+        - tal:attributes inserted empty attributes into source.
+
+    Version 1.1.0
+
+      Features Added
+        - TAL does not try to parse replacement structural text.
+        - Changed tests to match TAL's omitted attributes.
+
+    Version 1.0.0
+
+        - Various minor bugs fixed
+
+    Version 1.0.0b1
+
+	- All functionality described in the Project Wiki is implemented


=== Zope3/src/zope/tal/htmltalparser.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/htmltalparser.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,309 @@
+##############################################################################
+#
+# 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
+
+from HTMLParser import HTMLParser, HTMLParseError
+
+from zope.tal.taldefs import ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, \
+                             METALError, TALError, I18NError
+from zope.tal.talgenerator import TALGenerator
+
+
+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>'
+                       % ('>, <'.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,
+                       'i18n': ZOPE_I18N_NS,
+                       }
+
+    def parseFile(self, file):
+        f = open(file)
+        data = f.read()
+        f.close()
+        try:
+            self.parseString(data)
+        except TALError, e:
+            e.setFile(file)
+            raise
+
+    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, i18ndict \
+             = self.process_ns(tag, attrs)
+        self.tagstack.append(tag)
+        self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict,
+                                  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, i18ndict \
+             = self.process_ns(tag, attrs)
+        if taldict.get("content"):
+            self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+                                      i18ndict, self.getpos())
+            self.gen.emitEndElement(tag, implied=-1)
+        else:
+            self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+                                      i18ndict, 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.startswith("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 = name.split(':', 1)
+            if prefix == 'xmlns':
+                nsuri = self.nsdict.get(suffix)
+                if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS, ZOPE_I18N_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'
+                elif nsuri == ZOPE_I18N_NS:
+                    return name, suffix, 'i18n'
+        return name, name, 0
+
+    def process_ns(self, name, attrs):
+        attrlist = []
+        taldict = {}
+        metaldict = {}
+        i18ndict = {}
+        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
+            elif ns == 'i18n':
+                if i18ndict.has_key(keybase):
+                    raise I18NError("duplicate i18n attribute " +
+                                    `keybase`, self.getpos())
+                i18ndict[keybase] = value
+            attrlist.append(item)
+        if namens in ('metal', 'tal'):
+            taldict['tal tag'] = namens
+        return name, attrlist, taldict, metaldict, i18ndict


=== Zope3/src/zope/tal/interfaces.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/interfaces.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,147 @@
+"""Interface that a TALES engine provides to the METAL/TAL implementation."""
+
+from zope.interface import Attribute, Interface
+
+
+class ITALESCompiler(Interface):
+    """Compile-time interface provided by a TALES implementation.
+
+    The TAL compiler needs an instance of this interface to support
+    compilation of TALES expressions embedded in documents containing
+    TAL and METAL constructs.
+    """
+
+    def getCompilerError():
+        """Return the exception class raised for compilation errors.
+        """
+
+    def compile(expression):
+        """Return a compiled form of 'expression' for later evaluation.
+
+        'expression' is the source text of the expression.
+
+        The return value may be passed to the various evaluate*()
+        methods of the ITALESEngine interface.  No compatibility is
+        required for the values of the compiled expression between
+        different ITALESEngine implementations.
+        """
+
+
+class ITALESEngine(Interface):
+    """Render-time interface provided by a TALES implementation.
+
+    The TAL interpreter uses this interface to TALES to support
+    evaluation of the compiled expressions returned by
+    ITALESCompiler.compile().
+    """
+
+    def getDefault():
+        """Return the value of the 'default' TALES expression.
+
+        Checking a value for a match with 'default' should be done
+        using the 'is' operator in Python.
+        """
+
+    def setPosition((lineno, offset)):
+        """Inform the engine of the current position in the source file.
+
+        This is used to allow the evaluation engine to report
+        execution errors so that site developers can more easily
+        locate the offending expression.
+        """
+
+    def setSourceFile(filename):
+        """Inform the engine of the name of the current source file.
+
+        This is used to allow the evaluation engine to report
+        execution errors so that site developers can more easily
+        locate the offending expression.
+        """
+
+    def beginScope():
+        """Push a new scope onto the stack of open scopes.
+        """
+
+    def endScope():
+        """Pop one scope from the stack of open scopes.
+        """
+
+    def evaluate(compiled_expression):
+        """Evaluate an arbitrary expression.
+
+        No constraints are imposed on the return value.
+        """
+
+    def evaluateBoolean(compiled_expression):
+        """Evaluate an expression that must return a Boolean value.
+        """
+
+    def evaluateMacro(compiled_expression):
+        """Evaluate an expression that must return a macro program.
+        """
+
+    def evaluateStructure(compiled_expression):
+        """Evaluate an expression that must return a structured
+        document fragment.
+
+        The result of evaluating 'compiled_expression' must be a
+        string containing a parsable HTML or XML fragment.  Any TAL
+        markup cnotained in the result string will be interpreted.
+        """
+
+    def evaluateText(compiled_expression):
+        """Evaluate an expression that must return text.
+
+        The returned text should be suitable for direct inclusion in
+        the output: any HTML or XML escaping or quoting is the
+        responsibility of the expression itself.
+        """
+
+    def evaluateValue(compiled_expression):
+        """Evaluate an arbitrary expression.
+
+        No constraints are imposed on the return value.
+        """
+
+    def createErrorInfo(exception, (lineno, offset)):
+        """Returns an ITALESErrorInfo object.
+
+        The returned object is used to provide information about the
+        error condition for the on-error handler.
+        """
+
+    def setGlobal(name, value):
+        """Set a global variable.
+
+        The variable will be named 'name' and have the value 'value'.
+        """
+
+    def setLocal(name, value):
+        """Set a local variable in the current scope.
+
+        The variable will be named 'name' and have the value 'value'.
+        """
+
+    def setRepeat(name, compiled_expression):
+        """
+        """
+
+    def translate(domain, msgid, mapping):
+        """
+        See ITranslationService.translate()
+        """
+
+
+class ITALESErrorInfo(Interface):
+
+    type = Attribute("type",
+                     "The exception class.")
+
+    value = Attribute("value",
+                      "The exception instance.")
+
+    lineno = Attribute("lineno",
+                       "The line number the error occurred on in the source.")
+
+    offset = Attribute("offset",
+                       "The character offset at which the error occurred.")


=== Zope3/src/zope/tal/ndiff.py 1.1 => 1.2 === (550/650 lines abridged)
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/ndiff.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,647 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+# Module ndiff version 1.6.0
+# Released to the public domain 08-Dec-2000,
+# by Tim Peters (tim.one@home.com).
+
+# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
+
+"""ndiff [-q] file1 file2
+    or
+ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
+
+Print a human-friendly file difference report to stdout.  Both inter-
+and intra-line differences are noted.  In the second form, recreate file1
+(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
+
+In the first form, if -q ("quiet") is not specified, the first two lines
+of output are
+
+-: file1
++: file2
+
+Each remaining line begins with a two-letter code:
+
+    "- "    line unique to file1
+    "+ "    line unique to file2
+    "  "    line common to both files
+    "? "    line not present in either input file
+
+Lines beginning with "? " attempt to guide the eye to intraline
+differences, and were not present in either input file.  These lines can be
+confusing if the source files contain tab characters.
+
+The first file can be recovered by retaining only lines that begin with

[-=- -=- -=- 550 lines omitted -=- -=- -=-]

+    try:
+        opts, args = getopt.getopt(args, "qr:")
+    except getopt.error, detail:
+        return fail(str(detail))
+    noisy = 1
+    qseen = rseen = 0
+    for opt, val in opts:
+        if opt == "-q":
+            qseen = 1
+            noisy = 0
+        elif opt == "-r":
+            rseen = 1
+            whichfile = val
+    if qseen and rseen:
+        return fail("can't specify both -q and -r")
+    if rseen:
+        if args:
+            return fail("no args allowed with -r option")
+        if whichfile in "12":
+            restore(whichfile)
+            return 1
+        return fail("-r value must be 1 or 2")
+    if len(args) != 2:
+        return fail("need 2 filename args")
+    f1name, f2name = args
+    if noisy:
+        print '-:', f1name
+        print '+:', f2name
+    return fcompare(f1name, f2name)
+
+def restore(which):
+    import sys
+    tag = {"1": "- ", "2": "+ "}[which]
+    prefixes = ("  ", tag)
+    for line in sys.stdin.readlines():
+        if line[:2] in prefixes:
+            print line[2:],
+
+if __name__ == '__main__':
+    import sys
+    args = sys.argv[1:]
+    if "-profile" in args:
+        import profile, pstats
+        args.remove("-profile")
+        statf = "ndiff.pro"
+        profile.run("main(args)", statf)
+        stats = pstats.Stats(statf)
+        stats.strip_dirs().sort_stats('time').print_stats()
+    else:
+        main(args)


=== Zope3/src/zope/tal/readme.txt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/readme.txt	Wed Dec 25 09:15:29 2002
@@ -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?


=== Zope3/src/zope/tal/runtest.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/runtest.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,152 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+Driver program to run METAL and TAL regression tests.
+"""
+
+import glob
+import os
+import sys
+import traceback
+
+from cStringIO import StringIO
+
+if __name__ == "__main__":
+    import setpath                      # Local hack to tweak sys.path etc.
+
+import zope.tal.driver
+import zope.tal.tests.utils
+
+def showdiff(a, b):
+    import ndiff
+    cruncher = ndiff.SequenceMatcher(ndiff.IS_LINE_JUNK, a, b)
+    for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
+        if tag == "equal":
+            continue
+        print nicerange(alo, ahi) + tag[0] + nicerange(blo, bhi)
+        ndiff.dump('<', a, alo, ahi)
+        if a and b:
+            print '---'
+        ndiff.dump('>', b, blo, bhi)
+
+def nicerange(lo, hi):
+    if hi <= lo+1:
+        return str(lo+1)
+    else:
+        return "%d,%d" % (lo+1, hi)
+
+def main():
+    opts = []
+    args = sys.argv[1:]
+    quiet = 0
+    unittesting = 0
+    if args and args[0] == "-q":
+        quiet = 1
+        del args[0]
+    if args and args[0] == "-Q":
+        unittesting = 1
+        del args[0]
+    while args and args[0].startswith('-'):
+        opts.append(args[0])
+        del args[0]
+    if not args:
+        prefix = os.path.join("tests", "input", "test*.")
+        if zope.tal.tests.utils.skipxml:
+            xmlargs = []
+        else:
+            xmlargs = glob.glob(prefix + "xml")
+            xmlargs.sort()
+        htmlargs = glob.glob(prefix + "html")
+        htmlargs.sort()
+        args = xmlargs + htmlargs
+        if not args:
+            sys.stderr.write("No tests found -- please supply filenames\n")
+            sys.exit(1)
+    errors = 0
+    for arg in args:
+        locopts = []
+        if arg.find("metal") >= 0 and "-m" not in opts:
+            locopts.append("-m")
+        if not unittesting:
+            print arg,
+            sys.stdout.flush()
+        if zope.tal.tests.utils.skipxml and arg.endswith(".xml"):
+            print "SKIPPED (XML parser not available)"
+            continue
+        save = sys.stdout, sys.argv
+        try:
+            try:
+                sys.stdout = stdout = StringIO()
+                sys.argv = [""] + opts + locopts + [arg]
+                zope.tal.driver.main()
+            finally:
+                sys.stdout, sys.argv = save
+        except SystemExit:
+            raise
+        except:
+            errors = 1
+            if quiet:
+                print sys.exc_type
+                sys.stdout.flush()
+            else:
+                if unittesting:
+                    print
+                else:
+                    print "Failed:"
+                    sys.stdout.flush()
+                traceback.print_exc()
+            continue
+        head, tail = os.path.split(arg)
+        outfile = os.path.join(
+            head.replace("input", "output"),
+            tail)
+        try:
+            f = open(outfile)
+        except IOError:
+            expected = None
+            print "(missing file %s)" % outfile,
+        else:
+            expected = f.readlines()
+            f.close()
+        stdout.seek(0)
+        if hasattr(stdout, "readlines"):
+            actual = stdout.readlines()
+        else:
+            actual = readlines(stdout)
+        if actual == expected:
+            if not unittesting:
+                print "OK"
+        else:
+            if unittesting:
+                print
+            else:
+                print "not OK"
+            errors = 1
+            if not quiet and expected is not None:
+                showdiff(expected, actual)
+    if errors:
+        sys.exit(1)
+
+def readlines(f):
+    L = []
+    while 1:
+        line = f.readline()
+        if not line:
+            break
+        L.append(line)
+    return L
+
+if __name__ == "__main__":
+    main()


=== Zope3/src/zope/tal/setpath.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/setpath.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+Read a module search path from .path file.
+
+If .path file isn't found in the directory of the setpath.py module, then try
+to import ZODB.  If that succeeds, we assume the path is already set up
+correctly.  If that import fails, an IOError is raised.
+"""
+import os
+import sys
+
+dir = os.path.dirname(__file__)
+path = os.path.join(dir, ".path")
+try:
+    f = open(path)
+except IOError:
+    try:
+        # If we can import ZODB, our sys.path is set up well enough already
+        import zodb
+    except ImportError:
+        raise IOError("Can't find ZODB package.  Please edit %s to point to "
+                      "your Zope's lib/python directory" % path)
+else:
+    for line in f.readlines():
+        line = line.strip()
+        if line and line[0] != '#':
+            for dir in line.split(os.pathsep):
+                dir = os.path.expanduser(os.path.expandvars(dir))
+                if dir not in sys.path:
+                    sys.path.append(dir)
+        # Must import this first to initialize Persistence properly
+        import zodb


=== Zope3/src/zope/tal/taldefs.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/taldefs.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,190 @@
+##############################################################################
+#
+# 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 zope.tal.interfaces import ITALESErrorInfo
+
+TAL_VERSION = "1.4"
+
+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"
+ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n"
+
+NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*"
+
+KNOWN_METAL_ATTRIBUTES = [
+    "define-macro",
+    "use-macro",
+    "define-slot",
+    "fill-slot",
+    ]
+
+KNOWN_TAL_ATTRIBUTES = [
+    "define",
+    "condition",
+    "content",
+    "replace",
+    "repeat",
+    "attributes",
+    "on-error",
+    "omit-tag",
+    "tal tag",
+    ]
+
+KNOWN_I18N_ATTRIBUTES = [
+    "translate",
+    "domain",
+    "target",
+    "source",
+    "attributes",
+    "data",
+    "name",
+    ]
+
+class TALError(Exception):
+
+    def __init__(self, msg, position=(None, None)):
+        assert msg != ""
+        self.msg = msg
+        self.lineno = position[0]
+        self.offset = position[1]
+        self.filename = None
+
+    def setFile(self, filename):
+        self.filename = filename
+
+    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)
+        if self.filename is not None:
+            result = result + ', in file %s' % self.filename
+        return result
+
+class METALError(TALError):
+    pass
+
+class TALESError(TALError):
+    pass
+
+class I18NError(TALError):
+    pass
+
+
+class ErrorInfo:
+
+    __implements__ = ITALESErrorInfo
+
+    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)
+        name = name.lower()
+        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:
+    arg = arg.replace(";;", "\0")
+    parts = arg.split(';')
+    parts = [p.replace("\0", ";") for p in parts]
+    if len(parts) > 1 and not parts[-1].strip():
+        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], tuple) 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], tuple) and len(program[0]) == 2):
+        opcode, version = program[0]
+        if opcode == "version":
+            return version
+    return None
+
+import re
+_ent1_re = re.compile('&(?![A-Z#])', re.I)
+_entch_re = re.compile('&([A-Z][A-Z0-9]*)(?![A-Z0-9;])', re.I)
+_entn1_re = re.compile('&#(?![0-9X])', re.I)
+_entnx_re = re.compile('&(#X[A-F0-9]*)(?![A-F0-9;])', re.I)
+_entnd_re = re.compile('&(#[0-9][0-9]*)(?![0-9;])')
+del re
+
+def attrEscape(s):
+    """Replace special characters '&<>' by character entities,
+    except when '&' already begins a syntactically valid entity."""
+    s = _ent1_re.sub('&amp;', s)
+    s = _entch_re.sub(r'&amp;\1', s)
+    s = _entn1_re.sub('&amp;#', s)
+    s = _entnx_re.sub(r'&amp;\1', s)
+    s = _entnd_re.sub(r'&amp;\1', s)
+    s = s.replace('<', '&lt;')
+    s = s.replace('>', '&gt;')
+    s = s.replace('"', '&quot;')
+    return s
+
+import cgi
+def quote(s, escape=cgi.escape):
+    return '"%s"' % escape(s, 1)
+del cgi


=== Zope3/src/zope/tal/talgenerator.py 1.1 => 1.2 === (688/788 lines abridged)
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/talgenerator.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,785 @@
+##############################################################################
+#
+# 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 cgi
+import re
+
+from zope.tal import taldefs
+from zope.tal.taldefs import NAME_RE, TAL_VERSION
+from zope.tal.taldefs import I18NError, METALError, TALError
+from zope.tal.taldefs import parseSubstitution
+from zope.tal.translationcontext import TranslationContext, DEFAULT_DOMAIN
+
+I18N_REPLACE = 1
+I18N_CONTENT = 2
+I18N_EXPRESSION = 3
+
+
+class TALGenerator:
+
+    inMacroUse = 0
+    inMacroDef = 0
+    source_file = None
+
+    def __init__(self, expressionCompiler=None, xml=1, source_file=None):
+        if not expressionCompiler:
+            from zope.tal.dummyengine import DummyEngine
+            expressionCompiler = DummyEngine()
+        self.expressionCompiler = expressionCompiler
+        self.CompilerError = expressionCompiler.getCompilerError()
+        # This holds the emitted opcodes representing the input
+        self.program = []
+        # The program stack for when we need to do some sub-evaluation for an
+        # intermediate result.  E.g. in an i18n:name tag for which the

[-=- -=- -=- 688 lines omitted -=- -=- -=-]

+        # If i18n:name appeared in the same tag as tal:replace then we're
+        # going to do the substitution a little bit differently.  The results
+        # of the expression go into the i18n substitution dictionary.
+        if replace:
+            self.emitSubstitution(replace, repldict)
+        elif varname:
+            if varname[1] is not None:
+                i18nNameAction = I18N_EXPRESSION
+            # o varname[0] is the variable name
+            # o i18nNameAction is either
+            #   - I18N_REPLACE for implicit tal:replace
+            #   - I18N_CONTENT for tal:content
+            #   - I18N_EXPRESSION for explicit tal:replace
+            # o varname[1] will be None for the first two actions and the
+            #   replacement tal expression for the third action.
+            self.emitI18nVariable(varname[0], i18nNameAction, varname[1])
+        # Do not test for "msgid is not None", i.e. we only want to test for
+        # explicit msgids here.  See comment above.
+        if msgid is not None and varname:
+            self.emitTranslation(msgid, i18ndata)
+        if repeat:
+            self.emitRepeat(repeat)
+        if condition:
+            self.emitCondition(condition)
+        if onError:
+            self.emitOnError(name, onError)
+        if scope:
+            self.emit("endScope")
+        if i18ncontext:
+            self.emit("endI18nContext")
+            assert self.i18nContext.parent is not None
+            self.i18nContext = self.i18nContext.parent
+        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()


=== Zope3/src/zope/tal/talgettext.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/talgettext.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+"""Program to extract internationalization markup from Page Templates.
+
+Once you have marked up a Page Template file with i18n: namespace tags, use
+this program to extract GNU gettext .po file entries.
+
+Usage: talgettext.py [options] files
+Options:
+    -h / --help
+        Print this message and exit.
+"""
+
+import getopt
+import os
+import sys
+
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talinterpreter import TALInterpreter
+from zope.tal.dummyengine import DummyEngine
+from zope.tal.interfaces import ITALESEngine
+
+
+def usage(code, msg=''):
+    # Python 2.1 required
+    print >> sys.stderr, __doc__
+    if msg:
+        print >> sys.stderr, msg
+    sys.exit(code)
+
+
+class POEngine(DummyEngine):
+    __implements__ = ITALESEngine
+
+    catalog = {}
+
+    def evaluatePathOrVar(self, expr):
+        return 'who cares'
+
+    def translate(self, domain, msgid, mapping):
+        self.catalog[msgid] = ''
+
+
+def main():
+    try:
+        opts, args = getopt.getopt(
+            sys.argv[1:],
+            'ho:',
+            ['help', 'output='])
+    except getopt.error, msg:
+        usage(1, msg)
+
+    outfile = None
+    for opt, arg in opts:
+        if opt in ('-h', '--help'):
+            usage(0)
+        elif opt in ('-o', '--output'):
+            outfile = arg
+
+    if not args:
+        print 'nothing to do'
+        return
+
+    # We don't care about the rendered output of the .pt file
+    class Devnull:
+        def write(self, s):
+            pass
+
+    engine = POEngine()
+    for file in args:
+        p = HTMLTALParser()
+        p.parseFile(file)
+        program, macros = p.getCode()
+        TALInterpreter(program, macros, engine, stream=Devnull())()
+
+    # Now print all the entries in the engine
+    msgids = engine.catalog.keys()
+    msgids.sort()
+    for msgid in msgids:
+        msgstr = engine.catalog[msgid]
+        print 'msgid "%s"' % msgid
+        print 'msgstr "%s"' % msgstr
+        print
+
+
+if __name__ == '__main__':
+    main()


=== Zope3/src/zope/tal/talinterpreter.py 1.1 => 1.2 === (635/735 lines abridged)
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/talinterpreter.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,732 @@
+##############################################################################
+#
+# 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 getopt
+import re
+import sys
+
+from cgi import escape
+# Do not use cStringIO here!  It's not unicode aware. :(
+from StringIO import StringIO
+
+from zope.tal.taldefs import quote, TAL_VERSION, TALError, METALError
+from zope.tal.taldefs import isCurrentVersion
+from zope.tal.taldefs import getProgramVersion, getProgramMode
+from zope.tal.talgenerator import TALGenerator
+from zope.tal.translationcontext import TranslationContext
+
+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"
+]
+
+def normalize(text):
+    # Now we need to normalize the whitespace in implicit message ids and
+    # implicit $name substitution values by stripping leading and trailing
+    # whitespace, and folding all internal whitespace to a single space.
+    return ' '.join(text.split())

[-=- -=- -=- 635 lines omitted -=- -=- -=-]

+            slot = slots.get(slotName)
+            if slot is not None:
+                prev_source = self.sourceFile
+                self.interpret(slot)
+                if self.sourceFile != prev_source:
+                    self.engine.setSourceFile(prev_source)
+                    self.sourceFile = prev_source
+                self.pushMacro(macroName, slots, entering=0)
+                return
+            self.pushMacro(macroName, slots)
+            # Falling out of the 'if' allows the macro to be interpreted.
+        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


=== Zope3/src/zope/tal/talparser.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/talparser.py	Wed Dec 25 09:15:29 2002
@@ -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.
+#
+##############################################################################
+"""
+Parse XML and compile to TALInterpreter intermediate code.
+"""
+
+from zope.tal.taldefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS
+from zope.tal.talgenerator import TALGenerator
+from zope.tal.xmlparser import XMLParser
+
+
+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, i18ndict \
+              = self.process_ns(name, attrlist)
+        attrlist = self.xmlnsattrs() + attrlist
+        self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict)
+
+    def process_ns(self, name, attrlist):
+        taldict = {}
+        metaldict = {}
+        i18ndict = {}
+        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.lower(), value
+            if ns == 'metal':
+                metaldict[keybase] = value
+                item = item + ("metal",)
+            elif ns == 'tal':
+                taldict[keybase] = value
+                item = item + ("tal",)
+            elif ns == 'i18n':
+                assert 0, "dealing with i18n: " + `(keybase, value)`
+                i18ndict[keybase] = value
+                item = item + ('i18n',)
+            fixedattrlist.append(item)
+        if namens in ('metal', 'tal', 'i18n'):
+            taldict['tal tag'] = namens
+        return name, fixedattrlist, taldict, metaldict, i18ndict
+
+    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, ZOPE_I18N_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 = name.split(' ')
+            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'
+            elif uri == ZOPE_I18N_NS:
+                ns = 'i18n'
+            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 zope.tal.talinterpreter import TALInterpreter
+    from zope.tal.dummyengine import DummyEngine
+    engine = DummyEngine(macros)
+    TALInterpreter(program, macros, engine, sys.stdout, wrap=0)()
+
+if __name__ == "__main__":
+    test()


=== Zope3/src/zope/tal/timer.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/timer.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,59 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+Helper program to time compilation and interpretation
+"""
+
+import getopt
+import sys
+import time
+
+from cPickle import dumps, loads
+from cStringIO import StringIO
+
+from zope.tal.driver import FILE, compilefile, interpretit
+
+
+def main():
+    count = 10
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "n:")
+    except getopt.error, msg:
+        print msg
+        sys.exit(2)
+    for o, a in opts:
+        if o == "-n":
+            count = int(a)
+    if not args:
+        args = [FILE]
+    for file in args:
+        print file
+        dummyfile = StringIO()
+        it = timefunc(count, compilefile, file)
+        timefunc(count, interpretit, it, None, dummyfile)
+
+def timefunc(count, func, *args):
+    sys.stderr.write("%-14s: " % func.__name__)
+    sys.stderr.flush()
+    t0 = time.clock()
+    for i in range(count):
+        result = apply(func, args)
+    t1 = time.clock()
+    sys.stderr.write("%6.3f secs for %d calls, i.e. %4.0f msecs per call\n"
+                     % ((t1-t0), count, 1000*(t1-t0)/count))
+    return result
+
+if __name__ == "__main__":
+    main()


=== Zope3/src/zope/tal/translationcontext.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/translationcontext.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Translation context object for the TALInterpreter's I18N support.
+
+The translation context provides a container for the information
+needed to perform translation of a marked string from a page template.
+
+$Id$
+"""
+
+DEFAULT_DOMAIN = "default"
+
+class TranslationContext:
+    """Information about the I18N settings of a TAL processor."""
+
+    def __init__(self, parent=None, domain=None, target=None, source=None):
+        if parent:
+            if not domain:
+                domain = parent.domain
+            if not target:
+                target = parent.target
+            if not source:
+                source = parent.source
+        elif domain is None:
+            domain = DEFAULT_DOMAIN
+
+        self.parent = parent
+        self.domain = domain
+        self.target = target
+        self.source = source


=== Zope3/src/zope/tal/xmlparser.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:16:00 2002
+++ Zope3/src/zope/tal/xmlparser.py	Wed Dec 25 09:15:29 2002
@@ -0,0 +1,85 @@
+##############################################################################
+#
+# 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 logging
+
+
+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:
+                logging.warn("TAL.XMLParser: 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:
+                    logging.error("TAL.XMLParser: Can't set "
+                                  "expat handler %s" % name)
+
+    def createParser(self, encoding=None):
+        global XMLParseError
+        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)