[Checkins] SVN: z3c.pt/trunk/ Major refactoring; added attribute-access fallback to dictionary lookup. Bumped version number.

Malthe Borch mborch at gmail.com
Wed Dec 5 11:24:00 EST 2007


Log message for revision 82141:
  Major refactoring; added attribute-access fallback to dictionary lookup. Bumped version number.

Changed:
  U   z3c.pt/trunk/README.txt
  U   z3c.pt/trunk/setup.py
  U   z3c.pt/trunk/z3c/pt/README.txt
  U   z3c.pt/trunk/z3c/pt/VERSION.txt
  A   z3c.pt/trunk/z3c/pt/codegen.py
  A   z3c.pt/trunk/z3c/pt/codegen.txt
  U   z3c.pt/trunk/z3c/pt/io.py
  U   z3c.pt/trunk/z3c/pt/pagetemplate.py
  U   z3c.pt/trunk/z3c/pt/tal.py
  U   z3c.pt/trunk/z3c/pt/tal.txt
  U   z3c.pt/trunk/z3c/pt/tests/test_doctests.py
  A   z3c.pt/trunk/z3c/pt/transformer.py
  D   z3c.pt/trunk/z3c/pt/translate.txt
  A   z3c.pt/trunk/z3c/pt/translation.py
  A   z3c.pt/trunk/z3c/pt/translation.txt

-=-
Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/README.txt	2007-12-05 16:23:59 UTC (rev 82141)
@@ -12,14 +12,12 @@
 
 The METAL macro language is not supported; i18n is on the to-do.
 
-Template language
------------------
+Template and expression language
+--------------------------------
 
-The template language is based loosely on the TAL 1.4 specification
-found here:
+The template and expression language is based loosely on the TAL 1.4
+specification*. Some notable changes:
 
-  * http://wiki.zope.org/ZPT/TALSpecification14
-  
 1. Only Python-expressions are allowed. Expressions can have
    try-except fallbacks using the vertical bar syntax:
 
@@ -33,3 +31,12 @@
    repeat variable is not available in this case.
 
       tal:repeat="i <some generator>"
+
+4. Attribute-access to dictionary entries is allowed, e.g.
+
+      dictionary.key
+
+   can be used instead of ``dictionary['key']``.
+
+*) http://wiki.zope.org/ZPT/TALSpecification14
+  

Modified: z3c.pt/trunk/setup.py
===================================================================
--- z3c.pt/trunk/setup.py	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/setup.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -24,6 +24,7 @@
       zip_safe=False,
       install_requires=[
           'setuptools',
+          'lxml',
           # -*- Extra requirements: -*-
       ],
       entry_points="""

Modified: z3c.pt/trunk/z3c/pt/README.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/README.txt	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/README.txt	2007-12-05 16:23:59 UTC (rev 82141)
@@ -9,7 +9,7 @@
   >>> from z3c.pt import PageTemplate
   >>> template = PageTemplate("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal/python">
   ...   Hello World!
   ... </div>
   ... """)

Modified: z3c.pt/trunk/z3c/pt/VERSION.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/VERSION.txt	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/VERSION.txt	2007-12-05 16:23:59 UTC (rev 82141)
@@ -1 +1 @@
-0.1
+0.2

Added: z3c.pt/trunk/z3c/pt/codegen.py
===================================================================
--- z3c.pt/trunk/z3c/pt/codegen.py	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/codegen.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,63 @@
+from compiler import ast, parse
+from compiler.pycodegen import ModuleCodeGenerator
+
+from transformer import ASTTransformer
+
+marker = object()
+
+CONSTANTS = frozenset(['False', 'True', 'None', 'NotImplemented', 'Ellipsis'])
+
+class TemplateASTTransformer(ASTTransformer):
+    def __init__(self):
+        self.locals = [CONSTANTS]
+        
+    def visitGetattr(self, node):
+        """
+        Allow fallback to dictionary lookup if attribute does not exist.
+
+        Variables starting with an underscore are exempt.
+
+        """
+        
+        if hasattr(node.expr, 'name') and node.expr.name.startswith('_'):
+            return ast.Getattr(node.expr, node.attrname)
+
+        expr = self.visit(node.expr)
+        name = ast.Const(node.attrname)
+
+        # hasattr(obj, key) and getattr(obj, key) or not
+        # hasattr(obj, key) and obj[key]
+        return ast.Or([ast.And(
+            [ast.CallFunc(ast.Name('hasattr'), [expr, name], None, None),
+             ast.CallFunc(ast.Name('getattr'), [expr, name], None, None)]),
+                    ast.And([
+            ast.Not(ast.CallFunc(ast.Name('hasattr'), [expr, name], None, None)),
+            ast.Subscript(expr, 'OP_APPLY', [name])])])
+    
+class Suite(object):
+    __slots__ = ['code']
+
+    xform = TemplateASTTransformer
+    mode = 'exec'
+    
+    def __init__(self, source):
+        """Create the code object from a string."""
+        
+        node = parse(source, self.mode)
+
+        # build tree
+        transform = self.xform()
+        tree = transform.visit(node)
+        filename = tree.filename = '<script>'
+
+        # generate code
+        gen = ModuleCodeGenerator(tree)
+        gen.optimized = True
+
+        self.code = gen.getCode()
+        
+    def __hash__(self):
+        return hash(self.code)
+
+    def __repr__(self):
+        return '%s(%r)' % (self.__class__.__name__, self.source)

Added: z3c.pt/trunk/z3c/pt/codegen.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/codegen.txt	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/codegen.txt	2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,31 @@
+Codegen
+=======
+
+The ``Codegen`` module is responsible for the low-level compilation
+of the page template.
+
+Suite
+-----
+
+The ``Suite`` class compiles a source code suite and makes a code
+object available.
+
+  >>> from z3c.pt.codegen import Suite
+  >>> suite = Suite("""\
+  ... print 'Hello World!'
+  ... """)
+  >>> exec suite.code
+  Hello World!
+
+AST transformations
+-------------------
+  
+We allow attribute access to dictionary entries to minimize verbosity
+in templates. It works by wrapping the get attribute nodes in a method
+that tries a dictionary lookup if attribute lookup failed.
+
+  >>> suite = Suite("""\
+  ... a = {'b': 1}
+  ... assert a['b'] == a.b
+  ... """)
+  >>> exec suite.code

Modified: z3c.pt/trunk/z3c/pt/io.py
===================================================================
--- z3c.pt/trunk/z3c/pt/io.py	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/io.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -12,9 +12,9 @@
 
     variable_prefix = '_saved'
     
-    def __init__(self, indentation_string="\t"):
+    def __init__(self, indentation=0, indentation_string="\t"):
         StringIO.__init__(self)
-        self.indentation = 0
+        self.indentation = indentation
         self.indentation_string = indentation_string
         self.counter = 0
         self.queue = u''

Modified: z3c.pt/trunk/z3c/pt/pagetemplate.py
===================================================================
--- z3c.pt/trunk/z3c/pt/pagetemplate.py	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/pagetemplate.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -1,67 +1,21 @@
-import lxml.etree
-import StringIO
-
-import tal
-import io
-
-ns_lookup_table = [(f.__ns__, f) for f in (tal.define,
-                                           tal.condition,
-                                           tal.omit,
-                                           tal.repeat,
-                                           tal.attribute,
-                                           tal.replace,
-                                           tal.tag,
-                                           tal.content)]
+import translation
+import codegen
                                                  
 class PageTemplate(object):
     def __init__(self, body):
         self.body = body
         self.render = None
         
-    def translate(self):
-        """Translates page template body to Python-code."""
+    def cook(self):
+        source, _globals = translation.translate(self.body)
+        suite = codegen.Suite(source)
 
-        tree = lxml.etree.parse(StringIO.StringIO(self.body))
-        root = tree.getroot()
+        _locals = {}
 
-        stream = io.CodeIO(indentation_string='  ')
+        exec suite.code in _globals, _locals
 
-        # imports and symbols
-        stream.write("from cgi import escape as _escape")
-        stream.write("from z3c.pt.tal import repeatdict as _repeatdict")
-        stream.write("from StringIO import StringIO as _StringIO")
-        
-        stream.write("def render(**_kwargs):")
-        stream.indent()
+        self.render = _locals['render']
 
-        # globalize imports and set up process variables
-        stream.write("global _StringIO, _repeatdict, _escape")
-        stream.write("repeat = _repeatdict()")
-        stream.write("_attrs = {}")
-        stream.write("_scope = _kwargs.keys()")
-
-        # output
-        stream.write("_out = _StringIO()")
-        
-        # set up keyword args
-        stream.write("for _variable, _value in _kwargs.items():")
-        stream.indent()
-        stream.write("globals()[_variable] = _value")
-        stream.outdent()
-
-        # translate tree
-        translate(root, stream)
-        
-        # output
-        stream.write("return _out.getvalue()")
-        stream.outdent()
-        
-        return stream.getvalue()
-
-    def cook(self):
-        exec self.translate()
-        self.render = render
-        
     @property
     def template(self):
         if self.render is None:
@@ -80,31 +34,3 @@
     @property
     def body(self):
         return open(self.filename, 'r').read()
-
-def translate(node, stream):
-    keys = node.keys() + [None]
-    
-    handlers = [handler(node) for key, handler in ns_lookup_table \
-                if key in keys]
-
-    # remove namespace attributes
-    for key, handler in ns_lookup_table:
-        if key is not None and key in node.attrib:
-            del node.attrib[key]
-
-    # update node
-    for handler in handlers:
-        node = handler.update(node)
-
-    # begin tag
-    for handler in handlers:
-        handler.begin(stream)
-
-    # process children
-    if node:
-        for element in node:
-            translate(element, stream)
-
-    # end tag
-    for handler in reversed(handlers):
-        handler.end(stream)

Modified: z3c.pt/trunk/z3c/pt/tal.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.py	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/tal.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -170,7 +170,7 @@
 
         expression = self.expressions[-1]
         stream.write("%s = %s" % (variable, expression))
-
+        
     def end(self, stream):
         stream.outdent(len(self.expressions)-1)
 
@@ -212,7 +212,7 @@
             stream.write("del %s" % variable)
             stream.write("_scope.remove('%s')" % variable)
             stream.outdent()
-            
+
 class Condition(object):
     def __init__(self, value):
         self.assign = Assign(expression(value))

Modified: z3c.pt/trunk/z3c/pt/tal.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.txt	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/tal.txt	2007-12-05 16:23:59 UTC (rev 82141)
@@ -27,16 +27,31 @@
 
   >>> tal.expression("a.non_defined_method() | '|'")
   ['a.non_defined_method() ', "'|'"]
-    
-Define
+
+Assign
 ------
 
+Simple assignment.
+
   >>> from z3c.pt.io import CodeIO
   >>> stream = CodeIO()
   >>> _scope = []
+
+  >>> assign = tal.Assign(tal.expression("1"))
+  >>> assign.begin(stream, 'a')
+  >>> exec stream.getvalue()
+  >>> a == 1
+  True
+  >>> del a
+  >>> assign.end(stream)
+  >>> exec stream.getvalue()
   
+Define
+------
+
 Variable scope:
 
+  >>> stream = CodeIO()
   >>> define = tal.Define("a b")
   >>> b = object()
   >>> define.begin(stream)
@@ -56,6 +71,7 @@
   
 Multiple defines:
 
+  >>> stream = CodeIO()
   >>> define = tal.Define("a b; c d")
   >>> d = object()
   >>> define.begin(stream)
@@ -79,6 +95,7 @@
 
 Tuple assignments:
 
+  >>> stream = CodeIO()
   >>> define = tal.Define("(e, f) [1, 2]")
   >>> define.begin(stream)
   >>> exec stream.getvalue()
@@ -96,6 +113,7 @@
   
 Using semicolons in expressions within a define:
 
+  >>> stream = CodeIO()
   >>> define = tal.Define("a ';'")
   >>> define.begin(stream)
   >>> exec stream.getvalue()
@@ -105,16 +123,22 @@
 
 Ending an define clause with a semicolon.
 
+  >>> stream = CodeIO()
   >>> define = tal.Define("a 4 + 5;")
   >>> define.begin(stream)
   >>> exec stream.getvalue()
   >>> a
   9
   >>> define.end(stream)
-
+  >>> del a
+  >>> _scope.remove('a')
+  >>> exec stream.getvalue()
+  
 Scope:
 
+  >>> stream = CodeIO()
   >>> a = 1
+  >>> _scope.append('a')
   >>> define = tal.Define("a 2")
   >>> define.begin(stream)
   >>> define.end(stream)

Modified: z3c.pt/trunk/z3c/pt/tests/test_doctests.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tests/test_doctests.py	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/tests/test_doctests.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -3,21 +3,20 @@
 
 OPTIONFLAGS = (zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
                zope.testing.doctest.ELLIPSIS |
-#               zope.testing.doctest.REPORT_NDIFF | 
                zope.testing.doctest.NORMALIZE_WHITESPACE)
 
 import zope.component.testing
 
 def test_suite():
-    doctests = ['README.txt', 'BENCHMARKS.txt', 'tal.txt', 'translate.txt']
-    
-    return unittest.TestSuite((
-        zope.testing.doctest.DocFileSuite(doctest,
+    filesuites = ['README.txt', 'BENCHMARKS.txt', 'tal.txt', 'translation.txt', 'codegen.txt']
+        
+    return unittest.TestSuite(
+        [zope.testing.doctest.DocFileSuite(doctest,
                                           optionflags=OPTIONFLAGS,
                                           setUp=zope.component.testing.setUp,
                                           tearDown=zope.component.testing.tearDown,
-                                          package="z3c.pt") for doctest in doctests
-        ))
+                                          package="z3c.pt") for doctest in filesuites]
+        )
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')

Added: z3c.pt/trunk/z3c/pt/transformer.py
===================================================================
--- z3c.pt/trunk/z3c/pt/transformer.py	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/transformer.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,235 @@
+from compiler import ast, parse
+
+class ASTTransformer(object):
+    """General purpose base class for AST transformations.
+    
+    Every visitor method can be overridden to return an AST node that has been
+    altered or replaced in some way.
+    """
+
+    def visit(self, node):
+        if node is None:
+            return None
+        if type(node) is tuple:
+            return tuple([self.visit(n) for n in node])
+        visitor = getattr(self, 'visit%s' % node.__class__.__name__,
+                          self._visitDefault)
+        return visitor(node)
+
+    def _clone(self, node, *args):
+        lineno = getattr(node, 'lineno', None)
+        node = node.__class__(*args)
+        if lineno is not None:
+            node.lineno = lineno
+        if isinstance(node, (ast.Class, ast.Function, ast.GenExpr, ast.Lambda)):
+            node.filename = '<string>' # workaround for bug in pycodegen
+        return node
+
+    def _visitDefault(self, node):
+        return node
+
+    def visitExpression(self, node):
+        return self._clone(node, self.visit(node.node))
+
+    def visitModule(self, node):
+        return self._clone(node, node.doc, self.visit(node.node))
+
+    def visitStmt(self, node):
+        return self._clone(node, [self.visit(x) for x in node.nodes])
+
+    # Classes, Functions & Accessors
+
+    def visitCallFunc(self, node):
+        return self._clone(node, self.visit(node.node),
+            [self.visit(x) for x in node.args],
+            node.star_args and self.visit(node.star_args) or None,
+            node.dstar_args and self.visit(node.dstar_args) or None
+        )
+
+    def visitClass(self, node):
+        return self._clone(node, node.name, [self.visit(x) for x in node.bases],
+            node.doc, self.visit(node.code)
+        )
+
+    def visitFunction(self, node):
+        args = []
+        if hasattr(node, 'decorators'):
+            args.append(self.visit(node.decorators))
+        return self._clone(node, *args + [
+            node.name,
+            node.argnames,
+            [self.visit(x) for x in node.defaults],
+            node.flags,
+            node.doc,
+            self.visit(node.code)
+        ])
+
+    def visitGetattr(self, node):
+        return self._clone(node, self.visit(node.expr), node.attrname)
+
+    def visitLambda(self, node):
+        node = self._clone(node, node.argnames,
+            [self.visit(x) for x in node.defaults], node.flags,
+            self.visit(node.code)
+        )
+        return node
+
+    def visitSubscript(self, node):
+        return self._clone(node, self.visit(node.expr), node.flags,
+            [self.visit(x) for x in node.subs]
+        )
+
+    # Statements
+
+    def visitAssert(self, node):
+        return self._clone(node, self.visit(node.test), self.visit(node.fail))
+
+    def visitAssign(self, node):
+        return self._clone(node, [self.visit(x) for x in node.nodes],
+            self.visit(node.expr)
+        )
+
+    def visitAssAttr(self, node):
+        return self._clone(node, self.visit(node.expr), node.attrname,
+            node.flags
+        )
+
+    def visitAugAssign(self, node):
+        return self._clone(node, self.visit(node.node), node.op,
+            self.visit(node.expr)
+        )
+
+    def visitDecorators(self, node):
+        return self._clone(node, [self.visit(x) for x in node.nodes])
+
+    def visitExec(self, node):
+        return self._clone(node, self.visit(node.expr), self.visit(node.locals),
+            self.visit(node.globals)
+        )
+
+    def visitFor(self, node):
+        return self._clone(node, self.visit(node.assign), self.visit(node.list),
+            self.visit(node.body), self.visit(node.else_)
+        )
+
+    def visitIf(self, node):
+        return self._clone(node, [self.visit(x) for x in node.tests],
+            self.visit(node.else_)
+        )
+
+    def _visitPrint(self, node):
+        return self._clone(node, [self.visit(x) for x in node.nodes],
+            self.visit(node.dest)
+        )
+    visitPrint = visitPrintnl = _visitPrint
+
+    def visitRaise(self, node):
+        return self._clone(node, self.visit(node.expr1), self.visit(node.expr2),
+            self.visit(node.expr3)
+        )
+
+    def visitReturn(self, node):
+        return self._clone(node, self.visit(node.value))
+
+    def visitTryExcept(self, node):
+        return self._clone(node, self.visit(node.body), self.visit(node.handlers),
+            self.visit(node.else_)
+        )
+
+    def visitTryFinally(self, node):
+        return self._clone(node, self.visit(node.body), self.visit(node.final))
+
+    def visitWhile(self, node):
+        return self._clone(node, self.visit(node.test), self.visit(node.body),
+            self.visit(node.else_)
+        )
+
+    def visitWith(self, node):
+        return self._clone(node, self.visit(node.expr),
+            [self.visit(x) for x in node.vars], self.visit(node.body)
+        )
+
+    def visitYield(self, node):
+        return self._clone(node, self.visit(node.value))
+
+    # Operators
+
+    def _visitBoolOp(self, node):
+        return self._clone(node, [self.visit(x) for x in node.nodes])
+    visitAnd = visitOr = visitBitand = visitBitor = visitBitxor = _visitBoolOp
+    visitAssTuple = visitAssList = _visitBoolOp
+
+    def _visitBinOp(self, node):
+        return self._clone(node,
+            (self.visit(node.left), self.visit(node.right))
+        )
+    visitAdd = visitSub = _visitBinOp
+    visitDiv = visitFloorDiv = visitMod = visitMul = visitPower = _visitBinOp
+    visitLeftShift = visitRightShift = _visitBinOp
+
+    def visitCompare(self, node):
+        return self._clone(node, self.visit(node.expr),
+            [(op, self.visit(n)) for op, n in  node.ops]
+        )
+
+    def _visitUnaryOp(self, node):
+        return self._clone(node, self.visit(node.expr))
+    visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp
+    visitBackquote = visitDiscard = _visitUnaryOp
+
+    def visitIfExp(self, node):
+        return self._clone(node, self.visit(node.test), self.visit(node.then),
+            self.visit(node.else_)
+        )
+
+    # Identifiers, Literals and Comprehensions
+
+    def visitDict(self, node):
+        return self._clone(node, 
+            [(self.visit(k), self.visit(v)) for k, v in node.items]
+        )
+
+    def visitGenExpr(self, node):
+        return self._clone(node, self.visit(node.code))
+
+    def visitGenExprFor(self, node):
+        return self._clone(node, self.visit(node.assign), self.visit(node.iter),
+            [self.visit(x) for x in node.ifs]
+        )
+
+    def visitGenExprIf(self, node):
+        return self._clone(node, self.visit(node.test))
+
+    def visitGenExprInner(self, node):
+        quals = [self.visit(x) for x in node.quals]
+        return self._clone(node, self.visit(node.expr), quals)
+
+    def visitKeyword(self, node):
+        return self._clone(node, node.name, self.visit(node.expr))
+
+    def visitList(self, node):
+        return self._clone(node, [self.visit(n) for n in node.nodes])
+
+    def visitListComp(self, node):
+        quals = [self.visit(x) for x in node.quals]
+        return self._clone(node, self.visit(node.expr), quals)
+
+    def visitListCompFor(self, node):
+        return self._clone(node, self.visit(node.assign), self.visit(node.list),
+            [self.visit(x) for x in node.ifs]
+        )
+
+    def visitListCompIf(self, node):
+        return self._clone(node, self.visit(node.test))
+
+    def visitSlice(self, node):
+        return self._clone(node, self.visit(node.expr), node.flags,
+            node.lower and self.visit(node.lower) or None,
+            node.upper and self.visit(node.upper) or None
+        )
+
+    def visitSliceobj(self, node):
+        return self._clone(node, [self.visit(x) for x in node.nodes])
+
+    def visitTuple(self, node):
+        return self._clone(node, [self.visit(n) for n in node.nodes])

Deleted: z3c.pt/trunk/z3c/pt/translate.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/translate.txt	2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/translate.txt	2007-12-05 16:23:59 UTC (rev 82141)
@@ -1,60 +0,0 @@
-Translation
------------
-
-  >>> from z3c.pt.pagetemplate import translate
-  >>> from z3c.pt import io
-
-A few imports.
-  
-  >>> from lxml import etree
-  >>> from StringIO import StringIO
-
-The following symbols are expected to be in the local symbol table.
-
-  >>> from cgi import escape as _escape
-  >>> from z3c.pt.tal import repeatdict as _repeat
-  >>> repeat = _repeat()
-  >>> _attrs = {}
-  
-Example:
-
-  >>> tree = etree.fromstring("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <span tal:define="a 'abc'"
-  ...         tal:attributes="class 'def' + a"
-  ...         tal:content="a + 'ghi'" />
-  ...   <ul>
-  ...     <li tal:repeat="i range(5)">
-  ...       <span tal:replace="'Item ' + str(i) + ')'" />
-  ...     </li>
-  ...   </ul>
-  ... </div>
-  ... """)
-
-  >>> stream = io.CodeIO(indentation_string="  ")
-  >>> translate(tree, stream)
-  >>> _out = StringIO()
-  >>> _scope = []
-  >>> exec stream.getvalue()
-  >>> print _out.getvalue()
-  <div>
-    <span class="defabc">abcghi</span>
-    <ul>
-       <li>
-          Item 0)
-       </li>
-       <li>
-          Item 1)
-       </li>
-       <li>
-          Item 2)
-       </li>
-       <li>
-          Item 3)
-       </li>
-       <li>
-          Item 4)
-       </li>
-     </ul>
-  </div>

Added: z3c.pt/trunk/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.py	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/translation.py	2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,76 @@
+from StringIO import StringIO
+import lxml.etree
+
+import cgi
+import tal
+import io
+
+ns_lookup_table = [(f.__ns__, f) for f in \
+    (tal.define,
+     tal.condition,
+     tal.omit,
+     tal.repeat,
+     tal.attribute,
+     tal.replace,
+     tal.tag,
+     tal.content)]
+
+wrapper = """\
+def render(**_kwargs):
+\trepeat = _repeatdict()
+\t_attrs = {}
+\t_scope = _kwargs.keys()
+\t_out = _StringIO()
+\t_globals = globals()
+\tfor _variable, _value in _kwargs.items():
+\t\t_globals[_variable] = _value
+%s
+\treturn _out.getvalue()
+"""
+
+def translate(body):
+    tree = lxml.etree.parse(StringIO(body))
+    root = tree.getroot()
+
+    stream = io.CodeIO(indentation=1, indentation_string="\t")
+    visit(root, stream)
+
+    source = wrapper % stream.getvalue()
+
+    _globals = dict(_StringIO=StringIO,
+                    _repeatdict=tal.repeatdict,
+                    _escape=cgi.escape)
+
+    return source, _globals
+
+def visit(node, stream):
+    """Translates a node and outputs to a code stream."""
+    
+    keys = node.keys() + [None]
+    
+    handlers = [handler(node) for key, handler in ns_lookup_table \
+                if key in keys]
+
+    # remove namespace attributes
+    for key, handler in ns_lookup_table:
+        if key is not None and key in node.attrib:
+            del node.attrib[key]
+
+    # update node
+    for handler in handlers:
+        node = handler.update(node)
+
+    # begin tag
+    for handler in handlers:
+        handler.begin(stream)
+
+    # process children
+    if node:
+        for element in node:
+            visit(element, stream)
+
+    # end tag
+    for handler in reversed(handlers):
+        handler.end(stream)
+
+    return stream

Copied: z3c.pt/trunk/z3c/pt/translation.txt (from rev 82100, z3c.pt/trunk/z3c/pt/translate.txt)
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.txt	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/translation.txt	2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,45 @@
+Translation
+-----------
+
+  >>> from z3c.pt.translation import translate
+
+Example:
+
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span tal:define="a 'abc'"
+  ...         tal:attributes="class 'def' + a"
+  ...         tal:content="a + 'ghi'" />
+  ...   <ul>
+  ...     <li tal:repeat="i range(5)">
+  ...       <span tal:replace="'Item ' + str(i) + ')'" />
+  ...     </li>
+  ...   </ul>
+  ... </div>
+  ... """
+
+  >>> source, _globals = translate(body)
+  >>> _locals = {}
+  >>> exec source in _globals, _locals
+  >>> print _locals['render']()
+  <div>
+    <span class="defabc">abcghi</span>
+    <ul>
+       <li>
+          Item 0)
+       </li>
+       <li>
+          Item 1)
+       </li>
+       <li>
+          Item 2)
+       </li>
+       <li>
+          Item 3)
+       </li>
+       <li>
+          Item 4)
+       </li>
+     </ul>
+  </div>



More information about the Checkins mailing list