[ZPT] CVS: Packages/TAL - CHANGES.txt:1.2.2.1 HISTORY.txt:1.1.2.1 markbench.py:1.2.2.1 DummyEngine.py:1.24.2.1 HTMLParser.py:1.15.6.1 TALDefs.py:1.19.2.1 TALGenerator.py:1.38.2.1 TALInterpreter.py:1.45.2.1 driver.py:1.23.4.1 runtest.py:1.17.2.1

evan@serenade.digicool.com evan@serenade.digicool.com
Sat, 16 Jun 2001 11:50:07 -0400


Update of /cvs-repository/Packages/TAL
In directory serenade:/home/evan/Zope/pt/lib/python/TAL

Modified Files:
      Tag: tal-1_3_0
	DummyEngine.py HTMLParser.py TALDefs.py TALGenerator.py 
	TALInterpreter.py driver.py runtest.py 
Added Files:
      Tag: tal-1_3_0
	CHANGES.txt HISTORY.txt markbench.py 
Log Message:
Merge trunk changes for release 1.3.2



--- Added File CHANGES.txt in package Packages/TAL ---
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.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

      Bugs Fixed

--- Added File HISTORY.txt in package Packages/TAL ---
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.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

--- Added File markbench.py in package Packages/TAL ---
#! /usr/bin/env python1.5

'''Run benchmarks of TAL vs. DTML'''

try:
    import warnings
except ImportError:
    pass
else:
    warnings.filterwarnings("ignore", category=DeprecationWarning)

import os
os.environ['NO_SECURITY'] = 'true'

import sys, time

if __name__ == "__main__":
    import setpath

from DocumentTemplate.DT_HTML import HTMLFile
from HTMLTALParser import HTMLTALParser
from TALInterpreter import TALInterpreter
from DummyEngine import DummyEngine
from cStringIO import StringIO

def time_apply(f, args, kwargs, count):
    for i in range(4):
        apply(f, args, kwargs)
    r = [None] * count
    t0 = time.clock()
    for i in r:
        pass
    t1 = time.clock()
    for i in r:
        apply(f, args, kwargs)
    t = time.clock() - t1 - (t1 - t0)
    return t / count

def time_zpt(fn, count):
    from Products.PageTemplates.PageTemplate import PageTemplate
    pt = PageTemplate()
    pt.write(open(fn).read())
    return time_apply(pt.pt_render, (), {'extra_context': data}, count)

def time_tal(fn, count):
    p = HTMLTALParser()
    p.parseFile(fn)
    program, macros = p.getCode()
    engine = DummyEngine(macros)
    engine.globals = data
    tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0,
                         tal=1, strictinsert=0)
    return time_apply(tal, (), {}, count)

def time_dtml(fn, count):
    html = HTMLFile(fn)
    return time_apply(html, (), data, count)

def profile_zpt(fn, count, profiler):
    from Products.PageTemplates.PageTemplate import PageTemplate
    pt = PageTemplate()
    pt.write(open(fn).read())
    for i in range(4):
        pt.pt_render(extra_context=data)
    r = [None] * count
    for i in r:
        profiler.runcall(pt.pt_render, 0, data)

def profile_tal(fn, count, profiler):
    p = HTMLTALParser()
    p.parseFile(fn)
    program, macros = p.getCode()
    engine = DummyEngine(macros)
    engine.globals = data
    tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0,
                         tal=1, strictinsert=0)
    for i in range(4):
        tal()
    r = [None] * count
    for i in r:
        profiler.runcall(tal)

tal_fn = 'benchmark/tal%.2d.html'
dtml_fn = 'benchmark/dtml%.2d.html'

def compare(n, count, profiler=None):
    t1 = int(time_zpt(tal_fn % n, count) * 1000 + 0.5)
    t2 = int(time_tal(tal_fn % n, count) * 1000 + 0.5)
    t3 = int(time_dtml(dtml_fn % n, count) * 1000 + 0.5)
    print '%.2d: %10s %10s %10s' % (n, t1, t2, t3)
    if profiler:
        profile_tal(tal_fn % n, count, profiler)

def main(count, profiler=None):
    n = 1
    print '##: %10s %10s %10s' % ('ZPT', 'TAL', 'DTML')
    while os.path.isfile(tal_fn % n) and os.path.isfile(dtml_fn % n):
        compare(n, count, profiler)
        n = n + 1

data = {'x':'X', 'r2': range(2), 'r8': range(8), 'r64': range(64)}
for i in range(10):
    data['x%s' % i] = 'X%s' % i

if __name__ == "__main__":
    filename = "markbench.prof"
    profiler = None
    if len(sys.argv) > 1 and sys.argv[1] == "-p":
        import profile
        profiler = profile.Profile()
        del sys.argv[1]

    if len(sys.argv) > 1:
        for arg in sys.argv[1:]:
            compare(int(arg), 25, profiler)
    else:
        main(25, profiler)

    if profiler is not None:
        profiler.dump_stats(filename)
        import pstats
        p = pstats.Stats(filename)
        p.strip_dirs()
        p.sort_stats('time', 'calls')
        try:
            p.print_stats(20)
        except IOError, e:
            if e.errno != errno.EPIPE:
                raise

--- Updated File DummyEngine.py in package Packages/TAL --
--- DummyEngine.py	2001/05/16 17:12:48	1.24
+++ DummyEngine.py	2001/06/16 15:49:36	1.24.2.1
@@ -88,12 +88,16 @@
 
 import re
 import sys
-import string
+from string import rfind, strip
 
+import driver
+
 from TALDefs import NAME_RE, TALError, TALESError
 
 Default = []
 
+name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match
+
 class DummyEngine:
 
     def __init__(self, macros=None):
@@ -128,8 +132,9 @@
         self.globals[name] = value
 
     def evaluate(self, expression):
-        expression = self.uncompile(expression)
-        m = re.match(r"(?s)(%s):(.*)\Z" % NAME_RE, expression)
+        assert expression[:1] == "$" == expression[-1:], expression
+        expression = expression[1:-1]
+        m = name_match(expression)
         if m:
             type, expr = m.group(1, 2)
         else:
@@ -138,7 +143,7 @@
         if type in ("string", "str"):
             return expr
         if type in ("path", "var", "global", "local"):
-            expr = string.strip(expr)
+            expr = strip(expr)
             if self.locals.has_key(expr):
                 return self.locals[expr]
             elif self.globals.has_key(expr):
@@ -146,15 +151,13 @@
             else:
                 raise TALESError("unknown variable: %s" % `expr`)
         if type == "not":
-            v = self.evaluate(expr)
-            return not v
+            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:
-                t, v, tb = info = sys.exc_info()
                 raise TALESError("evaluation error in %s" % `expr`,
                                  info=sys.exc_info())
         raise TALESError("unrecognized expression: " + `expression`)
@@ -180,15 +183,15 @@
         return self.evaluate(expr)
 
     def evaluateMacro(self, macroName):
-        macroName = self.uncompile(macroName)
+        assert macroName[:1] == "$" == macroName[-1:], macroName
+        macroName = macroName[1:-1]
         file, localName = self.findMacroFile(macroName)
         if not file:
             # Local macro
             macro = self.macros[localName]
         else:
             # External macro
-            from driver import compilefile
-            program, macros = compilefile(file)
+            program, macros = driver.compilefile(file)
             macro = macros.get(localName)
             if not macro:
                 raise TALESError("macro %s not found in file %s" %
@@ -199,14 +202,13 @@
         file, localName = self.findMacroFile(macroName)
         if not file:
             return file, localName
-        from driver import parsefile
-        doc = parsefile(file)
+        doc = driver.parsefile(file)
         return doc, localName
 
     def findMacroFile(self, macroName):
         if not macroName:
             raise TALESError("empty macro name")
-        i = string.rfind(macroName, '/')
+        i = rfind(macroName, '/')
         if i < 0:
             # No slash -- must be a locally defined macro
             return None, macroName

--- Updated File HTMLParser.py in package Packages/TAL --
--- HTMLParser.py	2001/04/09 17:23:50	1.15
+++ HTMLParser.py	2001/06/16 15:49:36	1.15.6.1
@@ -74,15 +74,19 @@
 
 
 # HTML parser class -- find tags and call handler functions.
-# Usage: p = HTMLParser(); p.feed(data); ...; p.close().
-# The dtd is defined by deriving a class which defines methods
-# with special names to handle tags: start_foo and end_foo to handle
-# <foo> and </foo>, respectively, or do_foo to handle <foo> by itself.
-# (Tags are converted to lower case for this purpose.)  The data
-# between tags is passed to the parser by calling self.handle_data()
-# with some 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 argument.
+# 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.
 
 class HTMLParser:
 

--- Updated File TALDefs.py in package Packages/TAL --
--- TALDefs.py	2001/05/17 04:16:45	1.19
+++ TALDefs.py	2001/06/16 15:49:36	1.19.2.1
@@ -86,8 +86,10 @@
 Common definitions used by TAL and METAL compilation an transformation.
 """
 
-TAL_VERSION = "1.3"
+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
 
@@ -195,7 +197,7 @@
 
 def getProgramMode(program):
     version = getProgramVersion(program)
-    if (version == TAL_VERSION and isinstance(program[1], type(())) and
+    if (version == TAL_VERSION and isinstance(program[1], TupleType) and
         len(program[1]) == 2):
         opcode, mode = program[1]
         if opcode == "mode":
@@ -203,15 +205,14 @@
     return None
 
 def getProgramVersion(program):
-    if (isinstance(program, type([])) and len(program) >= 2 and
-        isinstance(program[0], type(())) and len(program[0]) == 2):
+    if (isinstance(program, ListType) and 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
-_cgi = cgi
+def quote(s, escape=cgi.escape):
+    return '"%s"' % escape(s, 1)
 del cgi
-def quote(s):
-    return '"%s"' % _cgi.escape(s, 1)

--- Updated File TALGenerator.py in package Packages/TAL --
--- TALGenerator.py	2001/05/18 01:24:04	1.38
+++ TALGenerator.py	2001/06/16 15:49:36	1.38.2.1
@@ -91,12 +91,12 @@
 import cgi
 
 from TALDefs import *
-from DummyEngine import DummyEngine
 
 class TALGenerator:
 
     def __init__(self, expressionCompiler=None, xml=1):
         if not expressionCompiler:
+            from DummyEngine import DummyEngine
             expressionCompiler = DummyEngine()
         self.expressionCompiler = expressionCompiler
         self.program = []
@@ -127,42 +127,97 @@
                 item = program[cursor]
             except IndexError:
                 item = (None, None)
-            if item[0] == "rawtext":
+            opcode = item[0]
+            if opcode == "rawtext":
                 collect.append(item[1])
                 continue
-            if item[0] == "endTag":
+            if opcode == "endTag":
                 collect.append("</%s>" % item[1])
                 continue
-            if item[0] == "startTag":
+            if opcode == "startTag":
                 if self.optimizeStartTag(collect, item[1], item[2], ">"):
                     continue
-            if item[0] == "startEndTag":
+            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:
-                output.append(("rawtext", text))
-            if item[0] != None:
-                output.append(item)
+                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 output
+        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 item in attrlist:
+        for i in range(len(attrlist)):
+            item = attrlist[i]
             if len(item) > 2:
-                return 0
+                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(" %s" % item[0])
+                new.append(" " + item[0])
             else:
                 new.append(" %s=%s" % (item[0], quote(item[1])))
-        new.append(end)
-        collect.extend(new)
-        return 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)
@@ -410,8 +465,11 @@
             self.pushProgram()
             todo["defineSlot"] = defineSlot
         if taldict:
-            self.emit("beginScope")
-            self.emit("rawAttrs", self.makeAttrDict(attrlist))
+            dict = {}
+            for item in attrlist:
+                key, value = item[:2]
+                dict[key] = value
+            self.emit("beginScope", dict)
             todo["scope"] = 1
         if onError:
             self.pushProgram() # handler
@@ -451,13 +509,6 @@
         self.todoPush(todo)
         if isend:
             self.emitEndElement(name, isend)
-
-    def makeAttrDict(self, attrlist):
-        dict = {}
-        for item in attrlist:
-            key, value = item[:2]
-            dict[key] = value
-        return dict
 
     def emitEndElement(self, name, isend=0, implied=0):
         todo = self.todoPop()

--- Updated File TALInterpreter.py in package Packages/TAL --
--- TALInterpreter.py	2001/05/18 22:18:03	1.45
+++ TALInterpreter.py	2001/06/16 15:49:36	1.45.2.1
@@ -87,11 +87,16 @@
 """
 
 import sys
-import string
 import getopt
-import cgi
 
+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
@@ -148,6 +153,7 @@
             self.repldict = None
         return TALGenerator.replaceAttrs(self, attrlist, repldict)
 
+
 class TALInterpreter:
 
     def __init__(self, program, macros, engine, stream=None,
@@ -159,10 +165,15 @@
         self.TALESError = engine.getTALESError()
         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)
@@ -171,7 +182,9 @@
         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
@@ -183,12 +196,15 @@
 
     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.do_endScope()
+            self.engine.endScope()
+            self.scopeLevel = self.scopeLevel - 1
 
     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
 
@@ -202,10 +218,11 @@
         return self.macroStack.pop()
 
     def macroContext(self, what):
-        i = len(self.macroStack)
+        macroStack = self.macroStack
+        i = len(macroStack)
         while i > 0:
             i = i-1
-            if self.macroStack[i][0] == what:
+            if macroStack[i][0] == what:
                 return i
         return -1
 
@@ -216,35 +233,42 @@
         assert self.level == 0
         assert self.scopeLevel == 0
         if self.col > 0:
-            self.stream_write("\n")
+            self._stream_write("\n")
+            self.col = 0
 
-    def stream_write(self, s):
-        self.stream.write(s)
-        i = string.rfind(s, '\n')
+    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):
-        self.level = self.level + 1
+    def interpret(self, program, None=None):
+        oldlevel = self.level
+        self.level = oldlevel + 1
+        handlers = self.dispatch
         try:
-            for item in program:
-                methodName = "do_" + item[0]
-                args = item[1:]
-                if self.debug:
-                    s = "%s%s%s\n" % ("    "*self.level, methodName,
+            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)
-                method = getattr(self, methodName)
-                apply(method, args)
+                    handlers[opcode](self, args)
+            else:
+                for (opcode, args) in program:
+                    handlers[opcode](self, args)
         finally:
-            self.level = self.level - 1
+            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")
@@ -253,74 +277,116 @@
             self.endsep = " />"
         else:
             self.endsep = "/>"
+        self.endlen = len(self.endsep)
+    bytecode_handlers["mode"] = do_mode
 
     def do_setPosition(self, position):
         self.position = position
+    bytecode_handlers["setPosition"] = do_setPosition
 
-    def do_startEndTag(self, name, attrList):
-        self.startTagCommon(name, attrList, self.endsep)
+    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 + namelen
+        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 do_startTag(self, name, attrList):
-        self.startTagCommon(name, attrList, ">")
+    def attrAction(self, item):
+        name, value, action = item[:3]
+        if action > 1 and not self.showtal:
+            return 0, name, value
+        ok = 1
+        if action == 2 and self.metal:
+            i = rfind(name, ":") + 1
+            prefix, suffix = name[:i], name[i:]
+            ##self.dumpMacroStack(prefix, suffix, value)
+            what, macroName, slots = self.macroStack[-1]
+            if suffix == "define-macro":
+                if what == "use-macro":
+                    name = prefix + "use-macro"
+                    value = macroName
+                else:
+                    assert what == "define-macro"
+                    i = self.macroContext("use-macro")
+                    if i >= 0:
+                        j = self.macroContext("define-slot")
+                        if j > i:
+                            name = prefix + "use-macro"
+                        else:
+                            return 0, name, value
+            elif suffix == "define-slot":
+                assert what == "define-slot"
+                if self.macroContext("use-macro") >= 0:
+                    name = prefix + "fill-slot"
 
-    def startTagCommon(self, name, attrList, end):
-        if not attrList:
-            self.stream_write("<%s%s" % (name, end))
-            return
-        self.stream_write("<" + name)
-        align = self.col+1
-        if align >= self.wrap/2:
-            align = 4 # Avoid a narrow column far to the right
-        for item in attrList:
-            if len(item) == 2:
-                name, value = item[:2]
-            else:
-                ok, name, value = self.attrAction(item)
-                if not ok:
-                    continue
-            if value is None:
-                s = name
-            else:
-                s = "%s=%s" % (name, quote(value))
-            if (self.wrap and
-                self.col >= align and
-                self.col + 1 + len(s) > self.wrap):
-                self.stream_write("\n" + " "*align + s)
-            else:
-                self.stream_write(" " + s)
-        self.stream_write(end)
+        if value is None:
+            value = name
+        else:
+            value = "%s=%s" % (name, quote(value))
+        return ok, name, value
 
-    actionIndex = {"replace":0, "insert":1, "metal":2, "tal":3, "xmlns":4}
-    def attrAction(self, item):
-        name, value = item[:2]
-        try:
-            action = self.actionIndex[item[2]]
-        except KeyError:
-            raise TALError, ('Error in TAL program', self.position)
-        if not self.showtal and action > 1:
+    def attrAction_tal(self, item):
+        name, value, action = item[:3]
+        if action > 1 and not self.showtal:
             return 0, name, value
         ok = 1
-        if action <= 1 and self.tal:
-            if self.html and string.lower(name) in BOOLEAN_HTML_ATTRS:
+        if action <= 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 not evalue:
-                    ok = 0
-                else:
+                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:
-                    value = evalue
-                    if value is None:
+                    if evalue is None:
                         ok = 0
+                    value = evalue
         elif action == 2 and self.metal:
-            i = string.rfind(name, ":") + 1
+            i = rfind(name, ":") + 1
             prefix, suffix = name[:i], name[i:]
             ##self.dumpMacroStack(prefix, suffix, value)
             what, macroName, slots = self.macroStack[-1]
@@ -342,9 +408,13 @@
                 if self.macroContext("use-macro") >= 0:
                     name = prefix + "fill-slot"
 
-        elif action == 1: # Unexecuted insert
-            ok = 0
+        if ok:
+            if value is None:
+                value = name
+            else:
+                value = "%s=%s" % (name, quote(value))
         return ok, name, value
+    bytecode_handlers["<attrAction>"] = attrAction
 
     def dumpMacroStack(self, prefix, suffix, value):
         sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value))
@@ -353,51 +423,83 @@
             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.position = position
+        if closeprev:
+            engine = self.engine
+            engine.endScope()
+            engine.beginScope()
+        else:
+            self.engine.beginScope()
+            self.scopeLevel = self.scopeLevel + 1
 
-    def do_endTag(self, name):
-        self.stream_write("</%s>" % name)
+    def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)):
+        self._stream_write(s)
+        self.col = col
+        self.position = 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):
+    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):
+    def do_endScope(self, notused=None):
         self.engine.endScope()
         self.scopeLevel = self.scopeLevel - 1
+    bytecode_handlers["endScope"] = do_endScope
 
-    def do_setLocal(self, name, expr):
-        if not self.tal:
-            return
-        value = self.engine.evaluateValue(expr)
-        self.engine.setLocal(name, value)
+    def do_setLocal(self, notused):
+        pass
 
-    def do_setGlobal(self, name, expr):
-        if not self.tal:
-            return
-        value = self.engine.evaluateValue(expr)
-        self.engine.setGlobal(name, value)
+    def do_setLocal_tal(self, (name, expr)):
+        self.engine.setLocal(name, self.engine.evaluateValue(expr))
+    bytecode_handlers["setLocal"] = do_setLocal
 
-    def do_rawAttrs(self, dict):
-        if self.tal:
-            self.engine.setLocal("attrs", dict)
+    def do_setGlobal_tal(self, (name, expr)):
+        self.engine.setGlobal(name, self.engine.evaluateValue(expr))
+    bytecode_handlers["setGlobal"] = do_setLocal
 
-    def do_insertText(self, expr, block):
-        if not self.tal:
-            self.interpret(block)
-            return
-        text = self.engine.evaluateText(expr)
+    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(block)
+            self.interpret(stuff[1])
             return
-        text = cgi.escape(text)
-        self.stream_write(text)
+        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, expr, repldict, block):
-        if not self.tal:
-            self.interpret(block)
-            return
+    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
@@ -405,7 +507,7 @@
             self.interpret(block)
             return
         text = str(structure)
-        if not repldict and not self.strictinsert:
+        if not (repldict or self.strictinsert):
             # Take a shortcut, no error checking
             self.stream_write(text)
             return
@@ -413,6 +515,7 @@
             self.insertHTMLStructure(text, repldict)
         else:
             self.insertXMLStructure(text, repldict)
+    bytecode_handlers["insertStructure"] = do_insertStructure
 
     def insertHTMLStructure(self, text, repldict):
         from HTMLTALParser import HTMLTALParser
@@ -435,30 +538,40 @@
         program, macros = gen.getCode()
         self.interpret(program)
 
-    def do_loop(self, name, expr, block):
-        if not self.tal:
-            self.interpret(block)
-            return
+    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_rawtext(self, text):
-        self.stream_write(text)
+    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):
+    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):
+    def do_defineMacro(self, (macroName, macro)):
         if not self.metal:
             self.interpret(macro)
             return
         self.pushMacro("define-macro", macroName, None)
         self.interpret(macro)
         self.popMacro()
+    bytecode_handlers["defineMacro"] = do_defineMacro
 
-    def do_useMacro(self, macroName, macroExpr, compiledSlots, block):
+    def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)):
         if not self.metal:
             self.interpret(block)
             return
@@ -477,16 +590,18 @@
         self.pushMacro("use-macro", macroName, compiledSlots)
         self.interpret(macro)
         self.popMacro()
+    bytecode_handlers["useMacro"] = do_useMacro
 
-    def do_fillSlot(self, slotName, block):
+    def do_fillSlot(self, (slotName, block)):
         if not self.metal:
             self.interpret(block)
             return
         self.pushMacro("fill-slot", slotName, None)
         self.interpret(block)
         self.popMacro()
+    bytecode_handlers["fillSlot"] = do_fillSlot
 
-    def do_defineSlot(self, slotName, block):
+    def do_defineSlot(self, (slotName, block)):
         if not self.metal:
             self.interpret(block)
             return
@@ -500,27 +615,41 @@
         else:
             self.interpret(block)
         self.popMacro()
+    bytecode_handlers["defineSlot"] = do_defineSlot
 
-    def do_onError(self, block, handler):
-        if not self.tal:
-            self.interpret(block)
-            return
+    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 self.TALESError, err:
             self.restoreState(state)
             engine = self.engine
             engine.beginScope()
-            err.lineno = self.position[0]
-            err.offset = self.position[1]
+            err.lineno, err.offset = self.position
             engine.setLocal('error', err)
             self.interpret(handler)
             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
+
 
 def test():
     from driver import FILE, parsefile

--- Updated File driver.py in package Packages/TAL --
--- driver.py	2001/04/27 17:25:04	1.23
+++ driver.py	2001/06/16 15:49:36	1.23.4.1
@@ -93,15 +93,12 @@
 
 import getopt
 
-try:
+if __name__ == "__main__":
     import setpath                      # Local hack to tweak sys.path etc.
-    import Products.ParsedXML
-except ImportError:
-    pass
 
 # Import local classes
 import TALDefs
-from DummyEngine import DummyEngine
+import DummyEngine
 
 FILE = "tests/input/test01.xml"
 
@@ -160,7 +157,7 @@
     program, macros = it
     assert TALDefs.isCurrentVersion(program)
     if engine is None:
-        engine = DummyEngine(macros)
+        engine = DummyEngine.DummyEngine(macros)
     TALInterpreter(program, macros, engine, stream, wrap=0,
                    tal=tal, showtal=showtal, strictinsert=strictinsert)()
 

--- Updated File runtest.py in package Packages/TAL --
--- runtest.py	2001/05/18 19:17:17	1.17
+++ runtest.py	2001/06/16 15:49:36	1.17.2.1
@@ -94,6 +94,9 @@
 import glob
 import traceback
 
+if __name__ == "__main__":
+    import setpath                      # Local hack to tweak sys.path etc.
+
 import driver
 
 def showdiff(a, b):