[Checkins] SVN: z3c.pt/trunk/ Engine rewrite. See docs/HISTORY.txt for a changelog.

Malthe Borch mborch at gmail.com
Fri Dec 21 12:02:49 EST 2007


Log message for revision 82387:
  Engine rewrite. See docs/HISTORY.txt for a changelog.

Changed:
  U   z3c.pt/trunk/README.txt
  A   z3c.pt/trunk/docs/
  A   z3c.pt/trunk/docs/HISTORY.txt
  U   z3c.pt/trunk/setup.py
  U   z3c.pt/trunk/z3c/pt/BENCHMARKS.txt
  U   z3c.pt/trunk/z3c/pt/README.txt
  U   z3c.pt/trunk/z3c/pt/VERSION.txt
  U   z3c.pt/trunk/z3c/pt/__init__.py
  A   z3c.pt/trunk/z3c/pt/attributes.py
  A   z3c.pt/trunk/z3c/pt/clauses.py
  U   z3c.pt/trunk/z3c/pt/codegen.py
  A   z3c.pt/trunk/z3c/pt/i18n.txt
  U   z3c.pt/trunk/z3c/pt/io.py
  U   z3c.pt/trunk/z3c/pt/pagetemplate.py
  D   z3c.pt/trunk/z3c/pt/tal.py
  D   z3c.pt/trunk/z3c/pt/tal.txt
  U   z3c.pt/trunk/z3c/pt/tests/test_doctests.py
  A   z3c.pt/trunk/z3c/pt/tests/view.pt
  U   z3c.pt/trunk/z3c/pt/translation.py
  A   z3c.pt/trunk/z3c/pt/utils.py

-=-
Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/README.txt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,6 +1,6 @@
 Overview
 --------
-
+		      
 The z3c.pt package provides an alternative implementation of the TAL
 template language.
 
@@ -9,9 +9,9 @@
   - Templates are bytecode-compiled
   - Only Python-expressions are supported
   - Depends only on lxml
+    
+The METAL macro language is not supported.
 
-The METAL macro language is not supported; i18n is on the to-do.
-
 Template and expression language
 --------------------------------
 
@@ -38,5 +38,4 @@
 
    can be used instead of ``dictionary['key']``.
 
-*) http://wiki.zope.org/ZPT/TALSpecification14
-  
+*) http://wiki.zope.org/ZPT/TALSpecification14
\ No newline at end of file

Added: z3c.pt/trunk/docs/HISTORY.txt
===================================================================
--- z3c.pt/trunk/docs/HISTORY.txt	                        (rev 0)
+++ z3c.pt/trunk/docs/HISTORY.txt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,28 @@
+Changelog
+---------
+
+Version 0.3 - December 21, 2007
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Added ``ViewPageTemplateFile`` class
+  [malthe]
+  
+- Added support for i18n
+  [malthe]
+  
+- Engine rewrite; improved code generation abstractions
+  [malthe]
+
+  
+Version 0.2 - December 5, 2007
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Major optimizations to the generated code
+  [malthe]
+
+
+Version 0.1 - December 3, 2007
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- First public release
+  [malthe]

Modified: z3c.pt/trunk/setup.py
===================================================================
--- z3c.pt/trunk/setup.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/setup.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,12 +1,12 @@
 from setuptools import setup, find_packages
 import sys, os
 
-version = '0.2'
+version = '0.3'
 
 setup(name='z3c.pt',
       version=version,
       description="An implementation of the TAL template language.",
-      long_description=open("README.txt").read(),
+      long_description=open("README.txt").read() + open("docs/HISTORY.txt").read(),
       classifiers=[
         "Programming Language :: Python",
         "Topic :: Text Processing :: Markup :: HTML",
@@ -25,6 +25,7 @@
       install_requires=[
           'setuptools',
           'lxml',
+          'zope.i18n',
           # -*- Extra requirements: -*-
       ],
       entry_points="""

Modified: z3c.pt/trunk/z3c/pt/BENCHMARKS.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/BENCHMARKS.txt	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/BENCHMARKS.txt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -2,8 +2,8 @@
 ==========
 
                   zope.pagetemplate     z3c.pt
-Hello World       4.482                 1     
-1000 x 10 table   4.513                 1
+Hello World       2.90                  1     
+1000 x 10 table   6.108                 1
 
 Source
 ------
@@ -42,7 +42,7 @@
   ...   </tr>
   ... </table>""")
 
-  >>> for i in range(20): a = template(table=table)
+  >>> # for i in range(40): a = template(table=table)
   
   >>> template = z3PageTemplate()
   >>> template.pt_edit("""\
@@ -54,7 +54,7 @@
   ...   </tr>
   ... </table>""", 'text/xhtml')
 
-  >>> # for i in range(20): a = template(table=table)
+  >>> # for i in range(40): a = template(table=table)
 
   >>> from StringIO import StringIO
   >>> def bigtable(table):

Modified: z3c.pt/trunk/z3c/pt/README.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/README.txt	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/README.txt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -23,12 +23,30 @@
 
   >>> from z3c.pt import PageTemplateFile
   >>> from z3c.pt import tests
-  >>> filename = tests.__path__[0]+'/helloworld.pt'
-  
-  >>> template = PageTemplateFile(filename)
+  >>> path = tests.__path__[0]
+  >>> template = PageTemplateFile(path+'/helloworld.pt')
   >>> print template()
   <div>
      Hello World!
   </div>
 
 Keyword-parameters are passed on to the template namespace as-is.
+
+From a view:
+
+  >>> from z3c.pt import ViewPageTemplateFile
+  >>> class MockView(object):
+  ...     template = ViewPageTemplateFile(path+'/view.pt')
+  ...
+  ...     def __init__(self):
+  ...         self.request = u'my request'
+  ...         self.context = u'my context'
+
+  >>> view = MockView()
+  >>> print view.template(test=u'my test')
+  <div>
+    <span><MockView object at ...</span>
+    <span>my context</span>
+    <span>my request</span>
+    <span>{'test': u'my test'}</span>
+  </div>

Modified: z3c.pt/trunk/z3c/pt/VERSION.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/VERSION.txt	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/VERSION.txt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1 +1 @@
-0.2
+0.3

Modified: z3c.pt/trunk/z3c/pt/__init__.py
===================================================================
--- z3c.pt/trunk/z3c/pt/__init__.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/__init__.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,2 +1,3 @@
 from pagetemplate import PageTemplate
 from pagetemplate import PageTemplateFile
+from pagetemplate import ViewPageTemplateFile

Added: z3c.pt/trunk/z3c/pt/attributes.py
===================================================================
--- z3c.pt/trunk/z3c/pt/attributes.py	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/attributes.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,224 @@
+import parser
+
+def name(string):
+    return string
+    
+def expression(string):
+    """
+    Specification:
+
+    expression :: = python_expression [ |* expression ]
+    python_expresion ::= a python expression string
+
+    *) Using | as logical or is not supported.
+
+      >>> expression("4 + 5")
+      ['4 + 5']
+
+    Complex expressions:
+
+      >>> expression("a.non_defined_method() | 1")
+      ['a.non_defined_method() ', '1']
+
+    Expression with non-semantic horizontal bar.
+
+      >>> expression("'|'")
+      ["'|'"]
+
+    Expression with non-semantic horizontal bar and semantic bar.
+
+      >>> expression("a.non_defined_method() | '|'")
+      ['a.non_defined_method() ', "'|'"]
+
+    """
+
+    string = string.replace('\n', '').strip()
+
+    if not string:
+        return []
+        
+    expressions = []
+
+    i = j = 0
+    while i < len(string):
+        j = string.find('|', j + 1)
+        if j == -1:
+            j = len(string)
+
+        expr = string[i:j].lstrip()
+
+        try:
+            # we use the ``parser`` module to determine if
+            # an expression is a valid python expression
+            parser.expr(expr)
+        except SyntaxError, e:
+            if j < len(string):
+                continue
+
+            raise e
+
+        expressions.append(expr)
+        i = j + 1
+
+    return expressions
+
+def variable(string):
+    """
+    Specification:
+    
+    variables :: = variable_name [, variables]
+
+    Single variable:
+
+      >>> variable("variable")
+      ('variable',)
+
+    Multiple variables:
+
+      >>> variable("variable1, variable2")
+      ('variable1', 'variable2')
+      
+    """
+
+    variables = []
+    for var in string.split(', '):
+        var = var.strip()
+
+        if var in ('repeat',):
+            raise ValueError, "Invalid variable name '%s' (reserved)." % variable
+
+        if var.startswith('_'):
+            raise ValueError, \
+                  "Invalid variable name '%s' (starts with an underscore)." % variable            
+        variables.append(var)
+
+    return tuple(variables)
+
+def mapping(string):
+    """
+
+      >>> mapping("abc def")
+      [('abc', 'def')]
+
+      >>> mapping("abc")
+      [('abc', None)]
+
+      >>> mapping("abc; def ghi")
+      [('abc', None), ('def', 'ghi')]
+
+    """
+
+    defs = string.split(';')
+    mappings = []
+    for d in defs:
+        d = d.strip()
+        while '  ' in d:
+            d = d.replace('  ', ' ')
+
+        parts = d.split(' ')
+        if len(parts) == 1:
+            mappings.append((d, None))
+        elif len(parts) == 2:
+            mappings.append((parts[0], parts[1]))
+        else:
+            raise ValueError, "Invalid mapping (%s)." % string
+
+    return mappings
+    
+def definitions(string):
+    """
+    Specification:
+
+    argument ::= define_var [';' define_var]
+    define_var ::= Name python_expression
+
+    Single define:
+
+      >>> definitions("variable expression")
+      [(['variable'], ['expression'])]
+    
+    Multiple defines:
+
+      >>> definitions("variable1 expression1; variable2 expression2")
+      [(['variable1'], ['expression1']), (['variable2'], ['expression2'])]
+
+    Tuple define:
+
+      >>> definitions("(variable1, variable2) (expression1, expression2)")
+      [(['variable1', 'variable2'], ['(expression1, expression2)'])]
+
+    Use of unescaped semicolon in an expression:
+
+      >>> definitions("variable ';'")
+      [(['variable'], ["';'"])]
+    
+    A define clause that ends in a semicolon:
+
+      >>> definitions("variable expression;")
+      [(['variable'], ['expression'])]
+
+    A define clause with a trivial expression (we do allow this):
+    
+      >>> definitions("variable")
+      [(['variable'], None)]
+
+    A proper define clause following one with a trivial expression:
+
+      >>> definitions("variable1 expression; variable2")
+      [(['variable1'], ['expression']), (['variable2'], None)]
+      
+    """
+
+    string = string.replace('\n', '').strip()
+
+    defines = []
+
+    i = 0
+    while i < len(string):
+        while string[i] == ' ':
+            i += 1
+
+        # get variable definition
+        if string[i] == '(':
+            j = string.find(')', i+1)
+            if j == -1:
+                raise ValueError, "Invalid variable tuple definition (%s)." % string
+            var = variable(string[i+1:j])
+            j += 1
+        else:
+            j = string.find(' ', i + 1)
+            if j == -1:
+                var = variable(string[i:])
+                j = len(string)
+            else:
+                var = variable(string[i:j])
+
+        # get expression
+        i = j
+        while j < len(string):
+            j = string.find(';', j+1)
+            if j == -1:
+                j = len(string)
+
+            try:
+                expr = expression(string[i:j])
+            except SyntaxError, e:
+                if j < len(string):
+                    continue
+                raise e
+            break
+        else:
+            expr = None
+
+        defines.append((list(var), expr))
+
+        i = j + 1
+
+    return defines
+
+def definition(string):
+    defs = definitions(string)
+    if len(defs) != 1:
+        raise ValueError, "Multiple definitions not allowed."
+
+    return defs[0]

Added: z3c.pt/trunk/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/z3c/pt/clauses.py	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/clauses.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,458 @@
+from attributes import expression
+import utils
+
+class Assign(object):
+    """
+      >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+      >>> _scope = []
+
+    Simple assignment:
+
+      >>> assign = Assign(expression("1"))
+      >>> assign.begin(stream, 'a')
+      >>> exec stream.getvalue()
+      >>> a == 1
+      True
+      >>> assign.end(stream)
+
+    Try-except chain:
+
+      >>> assign = Assign(expression("float('abc') | 1"))
+      >>> assign.begin(stream, 'b')
+      >>> exec stream.getvalue()
+      >>> b == 1
+      True
+      >>> assign.end(stream)
+      
+     """
+    
+    def __init__(self, expressions, variable=None):
+        self.expressions = expressions
+        self.variable = variable
+
+    def begin(self, stream, variable=None):
+        """First n - 1 expressions must be try-except wrapped."""
+
+        variable = variable or self.variable
+            
+        for expression in self.expressions[:-1]:
+            stream.write("try:")
+            stream.indent()
+            stream.write("%s = %s" % (variable, expression))
+            stream.outdent()
+            stream.write("except Exception, e:")
+            stream.indent()
+
+        expression = self.expressions[-1]
+        stream.write("%s = %s" % (variable, expression))
+
+        stream.outdent(len(self.expressions)-1)
+        
+    def end(self, stream):
+        pass
+        
+class Define(object):
+    """
+      >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+      >>> _scope = []
+
+    Variable scope:
+
+      >>> define = Define("a", expression("b"))
+      >>> b = object()
+      >>> define.begin(stream)
+      >>> exec stream.getvalue()
+      >>> a is b
+      True
+      >>> del a
+      >>> _scope.remove('a')
+      >>> define.end(stream)
+      >>> exec stream.getvalue()
+      >>> a
+      Traceback (most recent call last):
+          ...
+      NameError: name 'a' is not defined
+      >>> b is not None
+      True
+
+    Multiple defines:
+
+      >>> stream = CodeIO()
+      >>> define1 = Define("a", expression("b"))
+      >>> define2 = Define("c", expression("d"))
+      >>> d = object()
+      >>> define1.begin(stream)
+      >>> define2.begin(stream)
+      >>> exec stream.getvalue()
+      >>> a is b and c is d
+      True
+      >>> define2.end(stream)
+      >>> define1.end(stream)
+      >>> del a; del c
+      >>> _scope.remove('a'); _scope.remove('c')
+      >>> exec stream.getvalue()
+      >>> a
+      Traceback (most recent call last):
+          ...
+      NameError: name 'a' is not defined
+      >>> c
+      Traceback (most recent call last):
+          ...
+      NameError: name 'c' is not defined
+      >>> b is not None and d is not None
+      True
+
+    Tuple assignments:
+
+      >>> stream = CodeIO()
+      >>> define = Define("(e, f)", expression("[1, 2]"))
+      >>> define.begin(stream)
+      >>> exec stream.getvalue()
+      >>> e == 1 and f == 2
+      True
+      >>> define.end(stream)
+
+    Verify scope is preserved on tuple assignment:
+
+      >>> e = None; f = None
+      >>> _scope.append('e'); _scope.append('f')
+      >>> exec stream.getvalue()
+      >>> e is None and f is None
+      True
+
+    Using semicolons in expressions within a define:
+
+      >>> stream = CodeIO()
+      >>> define = Define("a", expression("';'"))
+      >>> define.begin(stream)
+      >>> exec stream.getvalue()
+      >>> a
+      ';'
+      >>> define.end(stream)
+
+    Scope:
+
+      >>> stream = CodeIO()
+      >>> a = 1
+      >>> _scope.append('a')
+      >>> define = Define("a", expression("2"))
+      >>> define.begin(stream)
+      >>> define.end(stream)
+      >>> exec stream.getvalue()
+      >>> a
+      1
+    
+    """
+    def __init__(self, definition, expression, scope=()):
+        if not isinstance(definition, (list, tuple)):
+            definition = (definition,)
+
+        if len(definition) == 1:
+            variable = definition[0]
+        else:
+            variable = u"(%s,)" % ", ".join(definition)
+
+        self.assign = Assign(expression, variable)
+
+        # only register definitions for variables that have not
+        # been defined in this scope
+        self.definitions = [var for var in definition if var not in scope]
+            
+        if scope:
+            scope.extend(self.definitions)
+        if not scope:
+            scope = utils.scope()
+
+        self.scope = scope
+        
+    def begin(self, stream):
+        temp = stream.savevariable(self.scope, '{}')
+
+        # save local variables already in in scope
+        for var in self.definitions:
+            stream.write("if '%s' in _scope: %s['%s'] = %s" % (var, temp, var, var))
+            stream.write("else: _scope.append('%s')" % var)
+            
+        self.assign.begin(stream)
+
+    def end(self, stream):
+        temp = stream.restorevariable(self.scope)
+
+        self.assign.end(stream)
+        
+        for var in reversed(self.definitions):
+            stream.write("if '%s' in %s:" % (var, temp))
+            stream.indent()
+            stream.write("%s = %s['%s']" % (var, temp, var))
+            stream.outdent()
+            stream.write("else:")
+            stream.indent()
+            stream.write("del %s" % var)
+            stream.write("_scope.remove('%s')" % var)
+            stream.outdent()
+            
+class Condition(object):
+    """
+      >>> from z3c.pt.io import CodeIO
+
+    Unlimited scope:
+    
+      >>> stream = CodeIO()
+      >>> true = Condition(expression("True"))
+      >>> false = Condition(expression("False"))
+      >>> true.begin(stream)
+      >>> stream.write("print 'Hello'")
+      >>> true.end(stream)
+      >>> false.begin(stream)
+      >>> stream.write("print 'Universe!'")
+      >>> false.end(stream)
+      >>> stream.write("print 'World!'")
+      >>> exec stream.getvalue()
+      Hello
+      World!
+
+    Limited scope:
+
+      >>> stream = CodeIO()
+      >>> from StringIO import StringIO
+      >>> _out = StringIO()
+      >>> true = Condition(expression("True"), [Write(expression("'Hello'"))])
+      >>> false = Condition(expression("False"), [Write(expression("'Hallo'"))])
+      >>> true.begin(stream)
+      >>> true.end(stream)
+      >>> false.begin(stream)
+      >>> false.end(stream)
+      >>> exec stream.getvalue()
+      >>> _out.getvalue()
+      'Hello'
+    """
+      
+    def __init__(self, expression, clauses=None):
+        self.assign = Assign(expression)
+        self.clauses = clauses
+        
+    def begin(self, stream):
+        temp = stream.save()
+        self.assign.begin(stream, temp)
+        stream.write("if %s:" % temp)
+        stream.indent()
+        if self.clauses:
+            for clause in self.clauses:
+                clause.begin(stream)
+            for clause in reversed(self.clauses):
+                clause.end(stream)
+            stream.outdent()
+        
+    def end(self, stream):
+        if not self.clauses:
+            stream.outdent()
+        self.assign.end(stream)
+        stream.restore()
+
+class Else(object):
+    def __init__(self, clauses=None):
+        self.clauses = clauses
+        
+    def begin(self, stream):
+        stream.write("else:")
+        stream.indent()
+        if self.clauses:
+            for clause in self.clauses:
+                clause.begin(stream)
+            for clause in reversed(self.clauses):
+                clause.end(stream)
+            stream.outdent()
+        
+    def end(self, stream):
+        if not self.clauses:
+            stream.outdent()
+
+class Group(object):
+    def __init__(self, clauses):
+        self.clauses = clauses
+        
+    def begin(self, stream):
+        for clause in self.clauses:
+            clause.begin(stream)
+        for clause in reversed(self.clauses):
+            clause.end(stream)
+        
+    def end(self, stream):
+        pass
+    
+class Tag(object):
+    """
+      >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+      >>> _scope = []
+      >>> from StringIO import StringIO
+
+      >>> _out = StringIO()
+      >>> tag = Tag('div', dict(alt=expression(repr('Hello World!'))))
+      >>> tag.begin(stream)
+      >>> stream.out('Hello Universe!')
+      >>> tag.end(stream)
+      >>> exec stream.getvalue()
+      >>> _out.getvalue()
+      '<div alt="Hello World!">Hello Universe!</div>'
+      
+    """
+
+    def __init__(self, tag, attributes):
+        i = tag.find('}')
+        if i != -1:
+            self.tag = tag[i+1:]
+        else:
+            self.tag = tag
+
+        self.attributes = attributes
+        
+    def begin(self, stream):
+        stream.out('<%s' % self.tag)
+
+        # attributes
+        for attribute, expression in self.attributes.items():
+            stream.out(' %s="' % attribute)
+            write = Write(expression)
+            write.begin(stream)
+            write.end(stream)
+            stream.out('"')
+
+        stream.out(">")
+
+    def end(self, stream):
+        stream.out('</%s>' % self.tag)
+
+class Repeat(object):
+    """
+      >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+      >>> _scope = []
+
+    We need to set up the repeat object.
+
+      >>> from z3c.pt import utils
+      >>> repeat = utils.repeatdict()
+
+    Simple repeat loop and repeat data structure:
+
+      >>> _repeat = Repeat("i", expression("range(5)"))
+      >>> _repeat.begin(stream)
+      >>> stream.write("r = repeat['i']")
+      >>> stream.write("print (i, r.index, r.start, r.end, r.number(), r.odd(), r.even())")
+      >>> exec stream.getvalue()
+      (0, 0, True, False, 1, False, True)
+      (1, 1, False, False, 2, True, False)
+      (2, 2, False, False, 3, False, True)
+      (3, 3, False, False, 4, True, False)
+      (4, 4, False, True, 5, False, True)
+      >>> _repeat.end(stream)
+      
+    """
+        
+    def __init__(self, v, e, scope=()):
+        self.variable = v
+        self.define = Define(v, expression("None"), scope)
+        self.assign = Assign(e)
+
+    def begin(self, stream):
+        variable = self.variable
+        iterator = stream.save()
+
+        # assign iterator
+        self.assign.begin(stream, iterator)
+
+        # initialize variable scope
+        self.define.begin(stream)
+
+        # initialize iterator
+        stream.write("repeat['%s'] = %s = %s.__iter__()" % (variable, iterator, iterator))
+
+        # loop
+        stream.write("while %s:" % iterator)
+        stream.indent()
+        stream.write("%s = %s.next()" % (variable, iterator))
+
+    def end(self, stream):
+        # cook before leaving loop
+        stream.cook()
+        
+        stream.outdent()
+        self.define.end(stream)
+        self.assign.end(stream)
+        stream.restore()
+
+class Write(object):
+    """
+      >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+      >>> from StringIO import StringIO
+
+      >>> _out = StringIO()
+      >>> write = Write(expression("'New York'"))
+      >>> write.begin(stream)
+      >>> write.end(stream)
+      >>> exec stream.getvalue()
+      >>> _out.getvalue()
+      'New York'
+      >>> _out = StringIO()
+      >>> write = Write(expression("undefined | ', New York!'"))
+      >>> write.begin(stream)
+      >>> write.end(stream)
+      >>> exec stream.getvalue()
+      >>> _out.getvalue()
+      'New York, New York!'
+    """
+    
+    def __init__(self, expressions):
+        self.assign = Assign(expressions)
+        self.expressions = expressions
+        self.count = len(expressions)
+        
+    def begin(self, stream):
+        temp = stream.save()
+                
+        if self.count == 1:
+            stream.write("_out.write(%s)" % self.expressions[0])
+        else:
+            self.assign.begin(stream, temp)
+            stream.write("_out.write(%s)" % temp)
+        
+    def end(self, stream):
+        if self.count != 1:
+            self.assign.end(stream)
+        stream.restore()
+
+class Out(object):
+    """
+      >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+      >>> from StringIO import StringIO
+      >>> _out = StringIO()
+      
+      >>> out = Out('Hello World!')
+      >>> out.begin(stream)
+      >>> out.end(stream)
+      >>> exec stream.getvalue()
+      >>> _out.getvalue()
+      'Hello World!'      
+    """
+    
+    def __init__(self, string, defer=False):
+        self.string = string
+        self.defer = defer
+        
+    def begin(self, stream):
+        if not self.defer:
+            stream.out(self.string)
+
+    def end(self, stream):
+        if self.defer:
+            stream.out(self.string)
+
+class Translate(object):
+    """
+    The translate clause works retrospectively.
+    """
+
+    def begin(self, stream):
+        raise
+
+    def end(self, stream):
+        raise

Modified: z3c.pt/trunk/z3c/pt/codegen.py
===================================================================
--- z3c.pt/trunk/z3c/pt/codegen.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/codegen.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -42,7 +42,7 @@
     
     def __init__(self, source):
         """Create the code object from a string."""
-        
+
         node = parse(source, self.mode)
 
         # build tree

Added: z3c.pt/trunk/z3c/pt/i18n.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/i18n.txt	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/i18n.txt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,113 @@
+i18n
+----
+
+This document highlights the i18n-support in the engine. The
+implementation is based on this document:
+
+  * http://wiki.zope.org/zope3/ZPTInternationalizationSupport
+
+With the exception of i18n:data and i18n:source, the implementation is
+complete.
+
+Let's provide German mock translations for all msgids:
+
+  >>> from zope import component
+  >>> from zope import interface
+
+  >>> from zope.i18n.interfaces import ITranslationDomain
+  >>> class MockTranslationDomain(object):
+  ...     interface.implements(ITranslationDomain)
+  ...
+  ...     def translate(self, msgid, mapping=None, context=None,
+  ...                   target_language=None, default=None):
+  ...         if target_language != 'de':
+  ...             return default
+  ...
+  ...         return "Mock translation of '%s'." % msgid
+
+  >>> td = MockTranslationDomain()
+  >>> component.provideUtility(td, ITranslationDomain, name="test")
+
+Translation of tag contents
+---------------------------
+
+First, a simple example:
+
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+  ...   <span i18n:domain="test" i18n:translate="test_msgid">
+  ...     Default
+  ...   </span>
+  ... </div>"""
+
+First we need to turn the template into a callable:
+
+  >>> from z3c.pt.pagetemplate import PageTemplate
+  >>> template = PageTemplate(body)
+
+Let's try rendering this template without passing a target language.
+    
+  >>> print template.render()
+  <div>
+    <span>
+      Default
+    </span>
+  </div>
+
+Now we'll render the template again---passing German as the language.
+
+  >>> print template.render(target_language='de')
+  <div>
+    <span>Mock translation of 'test_msgid'.</span>
+  </div>
+
+Let's try infering the translation message id from the tag body.
+  
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+  ...   <span i18n:domain="test" i18n:translate="">
+  ...     Default
+  ...   </span>
+  ... </div>"""
+
+Not passing a language:
+
+  >>> template = PageTemplate(body)
+  >>> print template.render()
+  <div>
+    <span>
+      Default
+    </span>
+  </div>
+
+Passing German:
+  
+  >>> print template.render(target_language='de')
+  <div>
+    <span>Mock translation of 'Default'.</span>
+  </div>
+
+We could also add a named block inside the tag.
+
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+  ...   <p i18n:domain="test" i18n:translate="">
+  ...     <span i18n:name="count">18</span> bananas.
+  ...   </p>
+  ... </div>"""
+
+  >>> template = PageTemplate(body)
+  >>> print template.render()
+  <div>
+    <p>
+      <span>18</span> bananas.
+    </p>
+  </div>
+    
+  >>> print template.render(target_language='de')
+  <div>
+    <p>Mock translation of '${count} bananas.'.</p>
+  </div>

Modified: z3c.pt/trunk/z3c/pt/io.py
===================================================================
--- z3c.pt/trunk/z3c/pt/io.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/io.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -5,49 +5,81 @@
     A high-level I/O class to write Python code to a stream.
     Indentation is managed using ``indent`` and ``outdent``.
 
-    Convenience methods for keeping track of temporary
-    variables.
+    Also:
     
+    * Convenience methods for keeping track of temporary
+    variables (see ``save``, ``restore`` and ``getvariable``).
+
+    * Methods to process clauses (see ``begin`` and ``end``).
+    
     """
 
-    variable_prefix = '_saved'
+    t_prefix = '_tmp'
+    v_prefix = '_var'
     
     def __init__(self, indentation=0, indentation_string="\t"):
         StringIO.__init__(self)
         self.indentation = indentation
         self.indentation_string = indentation_string
-        self.counter = 0
         self.queue = u''
-    
+        self.scope = {}
+
+        self.t_counter = 0
+        self.v_counter = 0        
+
     def save(self, variable=None):
-        self.counter += 1
+        self.t_counter += 1
         if variable:
-            self.write("%s%d = %s" % (self.variable_prefix, self.counter, variable))
+            self.write("%s%d = %s" % (self.t_prefix, self.t_counter, variable))
         else:
-            return "%s%d" % (self.variable_prefix, self.counter)
+            return "%s%d" % (self.t_prefix, self.t_counter)
         
     def restore(self, variable=None):
         if variable:
-            self.write("%s = %s%d" % (variable, self.variable_prefix, self.counter))
+            self.write("%s = %s%d" % (variable, self.t_prefix, self.t_counter))
         else:
-            return "%s%d" % (self.variable_prefix, self.counter)
+            return "%s%d" % (self.t_prefix, self.t_counter)
         
-        self.counter -= 1
-        
+        self.t_counter -= 1
+
+    def savevariable(self, obj, expression="None"):
+        if obj in self.scope:
+            return self.scope[obj]
+
+        self.v_counter += 1
+        variable = "%s%d" % (self.v_prefix, self.v_counter)
+
+        self.write("%s = %s" % (variable, expression))
+
+        self.scope[obj] = variable
+        return variable
+
+    def restorevariable(self, obj, expression="None"):
+        if obj in self.scope:
+            return self.scope[obj]
+
+        variable = "%s%d" % (self.v_prefix, self.v_counter)
+        self.v_counter -= 1
+
+        self.scope[obj] = variable
+        return variable
+
     def indent(self, amount=1):
         self.indentation += amount
 
     def outdent(self, amount=1):
+        self.cook()
         self.indentation -= amount
 
     def out(self, string):
+        self.cook()
         self.queue += string
 
     def cook(self):
         if self.queue:
             queue = self.queue
             self.queue = ''
-            self.write("_out.write('%s')" % queue)
+            self.write("_out.write('%s')" % queue.replace('\n', '\\n'))
         
     def write(self, string):
         self.cook()
@@ -56,3 +88,18 @@
     def getvalue(self):
         self.cook()
         return StringIO.getvalue(self)
+
+    def begin(self, clauses):
+        if isinstance(clauses, (list, tuple)):
+            for clause in clauses:
+                self.begin(clause)
+        else:
+            clauses.begin(self)
+            
+    def end(self, clauses):
+        if isinstance(clauses, (list, tuple)):
+            for clause in reversed(clauses):
+                self.end(clause)
+        else:
+            clauses.end(self)
+        

Modified: z3c.pt/trunk/z3c/pt/pagetemplate.py
===================================================================
--- z3c.pt/trunk/z3c/pt/pagetemplate.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/pagetemplate.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,36 +1,64 @@
 import translation
 import codegen
-                                                 
+import io
+
 class PageTemplate(object):
     def __init__(self, body):
         self.body = body
-        self.render = None
+        self.init()
         
-    def cook(self):
-        source, _globals = translation.translate(self.body)
+    def init(self):
+        self.registry = {}
+        
+    def cook(self, params):
+        source, _globals = translation.translate(self.body, params)
         suite = codegen.Suite(source)
 
         _locals = {}
 
         exec suite.code in _globals, _locals
+        
+        return _locals['render']
 
-        self.render = _locals['render']
+    def render(self, **kwargs):
+        signature = hash(",".join(kwargs.keys()))
 
-    @property
-    def template(self):
-        if self.render is None:
-            self.cook()
+        template = self.registry.get(signature)
+        if not template:
+            self.registry[signature] = template = self.cook(kwargs.keys())
 
-        return self.render
-        
+        return template(**kwargs)
+            
     def __call__(self, **kwargs):
-        return self.template(**kwargs)
+        return self.render(**kwargs)
 
 class PageTemplateFile(PageTemplate):
     def __init__(self, filename):
         self.filename = filename
-        self.render = None
 
+    def get_filename(self):
+        return getattr(self, '_filename', None)
+
+    def set_filename(self, filename):
+        self._filename = filename
+        self.init()
+
+    filename = property(get_filename, set_filename)
+        
     @property
     def body(self):
         return open(self.filename, 'r').read()
+
+class ViewPageTemplateFile(property):
+    def __init__(self, filename):
+        self.template = PageTemplateFile(filename)
+
+        def render(view):
+            def render(**kwargs):
+                return self.template.render(view=view,
+                                            context=view.context,
+                                            request=view.request,
+                                            options=kwargs)
+            return render
+        
+        property.__init__(self, render)

Deleted: z3c.pt/trunk/z3c/pt/tal.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/tal.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,406 +0,0 @@
-import parser
-import cgi
-import xml.sax.saxutils
-import itertools
-
-def expression(string):
-    """
-    Python-expressions in try-except construct.
-    
-    Specification:
-
-    expression :: = python_expression [ |* expression ]
-    python_expresion ::= a python expression string
-
-    *) Using | as logical or is not supported.
-
-    """
-
-    string = string.replace('\n', '')
-
-    expression = []
-
-    i = j = 0
-    while i < len(string):
-        j = string.find('|', j + 1)
-        if j == -1:
-            j = len(string)
-
-        expr = string[i:j].lstrip()
-
-        try:
-            # we use the ``parser`` module to determine if
-            # an expression is a valid python expression
-            parser.expr(expr)
-        except SyntaxError, e:
-            if j < len(string):
-                continue
-
-            raise e
-
-        expression.append(expr)
-        i = j + 1
-
-    return expression
-
-def variable(string):
-    for var in string.split(', '):
-        var = var.strip()
-
-        if var in ('repeat',):
-            raise ValueError, "Invalid variable name '%s' (reserved)." % variable
-
-        if var.startswith('_'):
-            raise ValueError, \
-                  "Invalid variable name '%s' (starts with an underscore)." % variable            
-        yield var
-    
-def definition(string):
-    """
-    TAL define-expression:
-
-    Specification:
-
-    argument ::= define_var [';' define_var]
-    define_var ::= Name python_expression
-
-    """
-
-    string = string.replace('\n', '').strip()
-
-    defines = []
-
-    i = 0
-    while i < len(string):
-        while string[i] == ' ':
-            i += 1
-
-        # get variable definition
-        if string[i] == '(':
-            j = string.find(')', i+1)
-            if j == -1:
-                raise ValueError, "Invalid variable tuple definition (%s)." % string
-            var = variable(string[i+1:j])
-            j += 1
-        else:
-            j = string.find(' ', i + 1)
-            if j == -1:
-                raise ValueError, "Invalid define clause (%s)." % string
-            var = variable(string[i:j])
-
-        # get expression
-        i = j
-        while j < len(string):
-            j = string.find(';', j+1)
-            if j == -1:
-                j = len(string)
-
-            try:
-                expr = expression(string[i:j])
-            except SyntaxError, e:
-                continue
-            break
-        else:
-            raise e
-
-        defines.append((list(var), expr))
-
-        i = j + 1
-
-    return defines
-
-class repeatitem(object):
-    def __init__(self, iterator, length):
-        self.length = length
-        self.iterator = iterator
-        
-    @property
-    def index(self):
-        return self.length - len(self.iterator) - 1
-        
-    @property
-    def start(self):
-        return self.index == 0
-
-    @property
-    def end(self):
-        return self.index == self.length - 1
-
-    def number(self):
-        return self.index + 1
-
-    def odd(self):
-        return bool(self.index % 2)
-
-    def even(self):
-        return not self.odd()
-
-class repeatdict(dict):
-    def __setitem__(self, key, iterator):
-        try:
-            length = len(iterator)
-        except TypeError:
-            length = None
-            
-        dict.__setitem__(self, key, (iterator, length))
-        
-    def __getitem__(self, key):
-        value, length = dict.__getitem__(self, key)
-
-        if not isinstance(value, repeatitem):
-            value = repeatitem(value, length)
-            self.__setitem__(key, value)
-
-        return value
-    
-class Assign(object):
-    def __init__(self, expression):
-        self.expressions = expression
-            
-    def begin(self, stream, variable):
-        """First n - 1 expressions must be try-except wrapped."""
-
-        for expression in self.expressions[:-1]:
-            stream.write("try:")
-            stream.indent()
-            stream.write("%s = %s" % (variable, expression))
-            stream.outdent()
-            stream.write("except Exception, e:")
-            stream.indent()
-
-        expression = self.expressions[-1]
-        stream.write("%s = %s" % (variable, expression))
-        
-    def end(self, stream):
-        stream.outdent(len(self.expressions)-1)
-
-class Define(object):    
-    def __init__(self, value):
-        self.defines = [(v, Assign(e)) for v, e in definition(value)]
-        self.variables = list(itertools.chain(*[v for (v, e) in self.defines]))
-
-    def update(self, node):
-        return node
-            
-    def begin(self, stream):
-        # save local variables already in scope
-        save = stream.save()
-        stream.write("%s = {}" % save)
-
-        for var in self.variables:
-            stream.write("if '%s' in _scope: %s['%s'] = %s" % (var, save, var, var))
-            stream.write("else: _scope.append('%s')" % var)
-        
-        for variables, assign in self.defines:
-            if len(variables) == 1:
-                assign.begin(stream, variables[0])
-            else:
-                assign.begin(stream, u"(%s,)" % ", ".join(variables))
-            assign.end(stream)
-        
-    def end(self, stream):
-        restore = stream.restore()
-
-        for variable in reversed(self.variables):
-            # restore local variables previously in scope
-            stream.write("if '%s' in %s:" % (variable, restore))
-            stream.indent()
-            stream.write("%s = %s['%s']" % (variable, restore, variable))
-            stream.outdent()
-            stream.write("else:")
-            stream.indent()
-            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))
-
-    def update(self, node):
-        return node
-
-    def begin(self, stream):
-        variable = '_condition'
-
-        self.assign.begin(stream, variable)
-        stream.write("if %s:" % variable)
-        stream.indent()
-
-    def end(self, stream):
-        self.assign.end(stream)
-        stream.outdent()
-
-class Repeat(object):
-    def __init__(self, value):
-        string = value.lstrip().replace('\n', '')
-
-        space = string.find(' ')
-        if space == -1:
-            raise ValueError, "Invalid repeat clause (%s)." % value
-
-        self.variable = string[:space]
-        self.assign = Assign(expression(string[space:]))
-        
-    def update(self, node):
-        return node
-
-    def begin(self, stream):
-        variable = self.variable
-        iterator = stream.save()
-        
-        self.assign.begin(stream, iterator)
-
-        # define variable scope
-        self.define = Define("%s None" % self.variable)
-        self.define.begin(stream)
-
-        # initialize iterator
-        stream.write("repeat['%s'] = %s = %s.__iter__()" % (variable, iterator, iterator))
-
-        # loop
-        stream.write("while %s:" % iterator)
-        stream.indent()
-        stream.write("%s = %s.next()" % (variable, iterator))
-        
-    def end(self, stream):
-        # cook before leaving loop
-        stream.cook()
-        
-        stream.outdent()
-        self.define.end(stream)
-        self.assign.end(stream)
-        stream.restore()
-        
-class Attribute(object):
-    def __init__(self, value):
-        self.attributes = [(v, Assign(e)) for v, e in definition(value)]
-
-    def update(self, node):
-        for variables, expression in self.attributes:
-            for variable in variables:
-                if variable in node.attrib:
-                    del node.attrib[variable]
-
-        return node
-    
-    def begin(self, stream):
-        stream.write("_attrs = {}")
-        for variables, assign in self.attributes:
-            for variable in variables:
-                assign.begin(stream, "_attrs['%s']" % variable)
-                assign.end(stream)
-
-    def end(self, stream):
-        pass
-
-class Content(object):
-    def __init__(self, value):
-        self.assign = Assign(expression(value))
-
-    def update(self, node):
-        node.text = ''
-        for element in node.getchildren():
-            node.remove(element)
-
-        return node
-        
-    def begin(self, stream):
-        self.assign.begin(stream, '_content')
-        stream.write("_out.write(_content)")
-        
-    def end(self, stream):
-        self.assign.end(stream)
-
-class Replace(Content):
-    def update(self, node):
-        return None
-    
-class Tag(object):
-    def __init__(self, node):
-        self.node = node
-        self.tail = node.tail
-                
-    @property
-    def tag(self):
-        tag = self.node.tag
-        if tag.startswith('{'):
-            return tag[tag.find('}')+1:]
-
-        return tag
-
-    def update(self, node):
-        self.node = node
-        return node
-    
-    def begin(self, stream):
-        if self.node is None:
-            return
-        stream.out('<%s' % self.tag)
-        stream.write("for _name, _value in _attrs.items():")
-        stream.indent()
-        stream.write("""_out.write(' %s=\"%s\"' % (_name, _escape(_value, '\"')))""")
-        stream.outdent()
-        stream.write("_attrs.clear()")
-        for name, value in self.node.attrib.items():
-            stream.out(' %s=%s' % (name, xml.sax.saxutils.quoteattr(value)))
-
-        if self.node.text is None:
-            stream.out(' />')
-        else:
-            stream.out('>')
-        
-        text = self.node.text
-        if text is not None:
-            text = cgi.escape(text.replace('\n', '\\n'), '"')
-            stream.out(text)
-
-    def end(self, stream):
-        if self.node is not None and self.node.text is not None:
-            stream.out('</%s>' % self.tag)
-
-        if self.tail is not None:
-            tail = cgi.escape(self.tail.replace('\n', '\\n'), "'")
-            stream.out(tail)
-
-def handler(key=None):
-    def decorate(f):
-        def g(node):
-            if key is None:
-                return f(node, None)
-            return f(node, node.get(key))
-        g.__ns__ = key
-        return g
-    return decorate
-
- at handler("{http://xml.zope.org/namespaces/tal}define")
-def define(node, value):
-    return Define(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}condition")
-def condition(node, value):
-    return Condition(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}repeat")
-def repeat(node, value):
-    return Repeat(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}attributes")
-def attribute(node, value):
-    return Attribute(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}content")
-def content(node, value):
-    return Content(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}replace")
-def replace(node, value):
-    return Replace(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}omit-tag")
-def omit(node, value):
-    raise NotImplemented, "The tal:omit-tag statement is not supported yet."
-
- at handler()
-def tag(node, value):
-    return Tag(node)

Deleted: z3c.pt/trunk/z3c/pt/tal.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.txt	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/tal.txt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,267 +0,0 @@
-TAL
-===
-
-Tests for the TAL language implementation.
-
-  >>> from z3c.pt import tal
-
-expression
-----------
-
-Simple expression:
-
-  >>> tal.expression("4 + 5")
-  ['4 + 5']
-
-Complex expressions:
-
-  >>> tal.expression("a.non_defined_method() | 1")
-  ['a.non_defined_method() ', '1']
-
-Expression with non-semantic horizontal bar.
-
-  >>> tal.expression("'|'")
-  ["'|'"]
-    
-Expression with non-semantic horizontal bar and semantic bar.
-
-  >>> tal.expression("a.non_defined_method() | '|'")
-  ['a.non_defined_method() ', "'|'"]
-
-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)
-  >>> exec stream.getvalue()
-  >>> a is b
-  True
-  >>> del a
-  >>> _scope.remove('a')
-  >>> define.end(stream)
-  >>> exec stream.getvalue()
-  >>> a
-  Traceback (most recent call last):
-      ...
-  NameError: name 'a' is not defined
-  >>> b is not None
-  True
-  
-Multiple defines:
-
-  >>> stream = CodeIO()
-  >>> define = tal.Define("a b; c d")
-  >>> d = object()
-  >>> define.begin(stream)
-  >>> exec stream.getvalue()
-  >>> a is b and c is d
-  True
-  >>> define.end(stream)
-  >>> del a; del c
-  >>> _scope.remove('a'); _scope.remove('c')
-  >>> exec stream.getvalue()
-  >>> a
-  Traceback (most recent call last):
-      ...
-  NameError: name 'a' is not defined
-  >>> c
-  Traceback (most recent call last):
-      ...
-  NameError: name 'c' is not defined
-  >>> b is not None and d is not None
-  True
-
-Tuple assignments:
-
-  >>> stream = CodeIO()
-  >>> define = tal.Define("(e, f) [1, 2]")
-  >>> define.begin(stream)
-  >>> exec stream.getvalue()
-  >>> e == 1 and f == 2
-  True
-  >>> define.end(stream)
-
-Verify scope is preserved on tuple assignment:
-
-  >>> e = None; f = None
-  >>> _scope.append('e'); _scope.append('f')
-  >>> exec stream.getvalue()
-  >>> e is None and f is None
-  True
-  
-Using semicolons in expressions within a define:
-
-  >>> stream = CodeIO()
-  >>> define = tal.Define("a ';'")
-  >>> define.begin(stream)
-  >>> exec stream.getvalue()
-  >>> a
-  ';'
-  >>> define.end(stream)
-
-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)
-  >>> exec stream.getvalue()
-  >>> a
-  1
-  
-Conditions
-----------
-
-  >>> stream = CodeIO()
-  
-True:
-
-  >>> a = 0
-  >>> condition = tal.Condition("True")
-  >>> define = tal.Define("a 1")
-  >>> condition.begin(stream)
-  >>> define.begin(stream)
-  >>> exec stream.getvalue()
-  >>> a
-  1
-  >>> define.end(stream)
-  >>> condition.end(stream)
-
-False:
-
-  >>> a = 0
-  >>> condition = tal.Condition("False")
-  >>> define = tal.Define("a 1")
-  >>> condition.begin(stream)
-  >>> define.begin(stream)
-  >>> exec stream.getvalue()
-  >>> a
-  0
-  >>> define.end(stream)
-  >>> condition.end(stream)
-
-Repeat
-------
-
-  >>> from z3c.pt.tal import repeatdict as _repeat
-  >>> repeat = _repeat()
-  
-  >>> stream = CodeIO()
-  
-  >>> _repeat = tal.Repeat("i range(5)")
-  >>> _repeat.begin(stream)
-  >>> stream.write("r = repeat['i']")
-  >>> stream.write("print (i, r.index, r.start, r.end, r.number(), r.odd(), r.even())")
-  >>> exec stream.getvalue()
-  (0, 0, True, False, 1, False, True)
-  (1, 1, False, False, 2, True, False)
-  (2, 2, False, False, 3, False, True)
-  (3, 3, False, False, 4, True, False)
-  (4, 4, False, True, 5, False, True)
-  >>> _repeat.end(stream)
-
-Attribute
----------
-
-  >>> from z3c.pt.tal import Attribute
-  >>> stream = CodeIO()
-  
-  >>> attribute = Attribute("class 'plain'")
-  >>> attribute.begin(stream)
-  >>> attribute.end(stream)
-  >>> exec stream.getvalue()
-  >>> _attrs
-  {'class': 'plain'}
-  >>> del _attrs
-  
-Attributes that are bound to expressions will be removed on ``update``:
-
-  >>> from lxml.etree import Element
-  >>> img = Element('img')
-  >>> img.set('src', u'logo.gif')
-  >>> img.set('class', u'stx')
-  >>> img = attribute.update(img)
-  >>> img.attrib
-  {'src': 'logo.gif'}
-
-Content
--------
-
-  >>> from z3c.pt.tal import Content
-  >>> stream = CodeIO()
-
-  >>> from StringIO import StringIO
-  >>> _out = StringIO()
-  
-  >>> content = tal.Content(u"'Hello World!'")
-  >>> content.begin(stream)
-  >>> exec stream.getvalue()
-  >>> _out.getvalue()
-  'Hello World!'
-  >>> content.end(stream)
-
-Tag
----
-
-Define required global symbols.
-
-  >>> from cgi import escape as _escape
-  >>> _attrs = {}
-  
-  >>> stream = CodeIO()
-  >>> br = Element('br')  
-  >>> tag = tal.Tag(br)
-  >>> tag.begin(stream)
-  >>> tag.end(stream)
-  >>> _out = StringIO()
-  >>> exec stream.getvalue()
-  >>> _out.getvalue()
-  '<br />'
-
-  >>> stream = CodeIO()
-  >>> div = Element('div')
-  >>> div.text = ''
-  >>> tag = tal.Tag(div)
-  >>> tag.begin(stream)
-  >>> tag.end(stream)
-  >>> _out = StringIO()
-  >>> exec stream.getvalue()
-  >>> _out.getvalue()
-  '<div></div>'

Modified: z3c.pt/trunk/z3c/pt/tests/test_doctests.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tests/test_doctests.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/tests/test_doctests.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,21 +1,24 @@
 import zope.testing
 import unittest
 
-OPTIONFLAGS = (zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
+OPTIONFLAGS = (#zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
                zope.testing.doctest.ELLIPSIS |
                zope.testing.doctest.NORMALIZE_WHITESPACE)
 
 import zope.component.testing
 
 def test_suite():
-    filesuites = ['README.txt', 'BENCHMARKS.txt', 'tal.txt', 'translation.txt', 'codegen.txt']
-        
+    filesuites = ['README.txt', 'BENCHMARKS.txt', 'translation.txt', 'i18n.txt', 'codegen.txt']
+    testsuites = ['z3c.pt.translation', 'z3c.pt.attributes', 'z3c.pt.clauses']
+
     return unittest.TestSuite(
+        [zope.testing.doctest.DocTestSuite(doctest,
+                                           optionflags=OPTIONFLAGS) for doctest in testsuites] + 
         [zope.testing.doctest.DocFileSuite(doctest,
-                                          optionflags=OPTIONFLAGS,
-                                          setUp=zope.component.testing.setUp,
-                                          tearDown=zope.component.testing.tearDown,
-                                          package="z3c.pt") for doctest in filesuites]
+                                           optionflags=OPTIONFLAGS,
+                                           setUp=zope.component.testing.setUp,
+                                           tearDown=zope.component.testing.tearDown,
+                                           package="z3c.pt") for doctest in filesuites]
         )
 
 if __name__ == '__main__':

Added: z3c.pt/trunk/z3c/pt/tests/view.pt
===================================================================
--- z3c.pt/trunk/z3c/pt/tests/view.pt	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/tests/view.pt	2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,7 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:tal="http://xml.zope.org/namespaces/tal">
+  <span tal:content="view" />
+  <span tal:content="context" />
+  <span tal:content="request" />
+  <span tal:content="options" />
+</div>

Modified: z3c.pt/trunk/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.py	2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/translation.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,76 +1,288 @@
 from StringIO import StringIO
 import lxml.etree
 
-import cgi
-import tal
 import io
+import utils
+import attributes as attrs
+import clauses
 
-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(%starget_language=None):
+\tglobal utils
 
-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
+\t_out = utils.initialize_stream()
+\t_scope = locals().keys()
+    
+\t(_attributes, repeat) = utils.initialize_tal()
+\t(_domain, _translate) = utils.initialize_i18n()
+\t(_escape, _marker) = utils.initialize_helpers()
+
+\t_target_language = target_language
 %s
 \treturn _out.getvalue()
 """
 
-def translate(body):
-    tree = lxml.etree.parse(StringIO(body))
-    root = tree.getroot()
+def attribute(ns, factory):
+    def get(self):
+        value = self.attrib.get(ns)
+        if value is not None:
+            return factory(value)
+        
+    return property(get)
 
-    stream = io.CodeIO(indentation=1, indentation_string="\t")
-    visit(root, stream)
+class Element(lxml.etree.ElementBase):
+    def begin(self, stream):
+        stream.begin(self.clauses())
+        
+    def end(self, stream):
+        stream.end(self.clauses())
 
-    source = wrapper % stream.getvalue()
+    def body(self, stream):
+        skip = self.replace or self.content or self.i18n_translate is not None
+        if not skip:
+            for element in self:
+                element.visit(stream)
+                        
+    def visit(self, stream):
+        self.begin(stream)
+        self.body(stream)
+        self.end(stream)
 
-    _globals = dict(_StringIO=StringIO,
-                    _repeatdict=tal.repeatdict,
-                    _escape=cgi.escape)
+    def clauses(self):
+        _ = []
 
-    return source, _globals
+        scope = utils.scope()
 
-def visit(node, stream):
-    """Translates a node and outputs to a code stream."""
+        # i18n domain
+        if self.i18n_domain is not None:
+            _.append(clauses.Define("_domain", [repr(self.i18n_domain)], scope))
+
+        # defines
+        if self.define is not None:
+            for variables, expression in self.define:
+                _.append(clauses.Define(variables, expression, scope))
+
+        # condition
+        if self.condition is not None:
+            _.append(clauses.Condition(self.condition))
+
+        # repeat
+        if self.repeat is not None:
+            variables, expression = self.repeat
+            if len(variables) != 1:
+                raise ValueError, "Cannot unpack more than one variable in a repeat statement."
+            _.append(clauses.Repeat(variables[0], expression, scope))
+
+        # tag tail (deferred)
+        if self.tail:
+            _.append(clauses.Out(self.tail, defer=True))
+        
+        # tag
+        if self.replace is None:
+            tag = clauses.Tag(self.tag, self._attributes())
+
+            if self.omit is not None:
+                _.append(clauses.Condition(_not(self.omit), [tag]))
+            else:
+                _.append(tag)
+
+        # tag text (if we're not replacing tag body)
+        if self.text and not (self.replace or self.content) and self.i18n_translate is None:
+            _.append(clauses.Out(self.text))
+
+        # dynamic content and content translation
+        replace = self.replace
+        content = self.content
+
+        if replace and content:
+            raise ValueError, "Can't use replace clause together with content clause."
+
+        expression = replace or content
+        if expression:
+            if self.i18n_translate is not None:
+                if self.i18n_translate != "":
+                    raise ValueError, "Can't use message id with dynamic content translation."
+                _.append(clauses.Translate())
+
+            # TODO: structure
+            _.append(clauses.Write(expression))
+        else:
+            if self.i18n_translate is not None:
+                msgid = self.i18n_translate
+                if not msgid:
+                    msgid = self._msgid()
+
+                # for each named block, create a new output stream
+                # and use the value in the translation mapping dict
+
+                elements = [e for e in self if e.i18n_name]
+
+                if elements:
+                    mapping = '_mapping'
+                    _.append(clauses.Assign(['{}'], mapping))
+                else:
+                    mapping = 'None'
+                    
+                for element in elements:
+                    name = element.i18n_name
+                    
+                    subclauses = []
+                    subclauses.append(clauses.Define('_out', ['utils.initialize_stream()'], scope))
+                    subclauses.append(clauses.Group(element.clauses()))
+                    subclauses.append(clauses.Assign(['_out.getvalue()'],
+                                                     "%s['%s']" % (mapping, name)))
+
+                    _.append(clauses.Group(subclauses))
+
+                _.append(clauses.Assign(
+                    _translate([repr(msgid)], mapping=mapping, default='_marker'),
+                    '_result'))
+
+                # write translation to output if successful, otherwise
+                # fallback to default rendition; 
+
+                _.append(clauses.Condition(['_result is not _marker'],
+                                           [clauses.Write(['_result'])]))
+
+                subclauses = []
+                if self.text:
+                    subclauses.append(clauses.Out(self.text))
+                for element in self:
+                    name = element.i18n_name
+                    if name:
+                        subclauses.append(clauses.Write(["%s['%s']" % (mapping, name)]))
+                        #subclauses.append(clauses.Out(element.tail))
+                    else:
+                        subclauses.append(clauses.Out(lxml.etree.tostring(element)))
+
+                if subclauses:
+                    _.append(clauses.Else(subclauses))
+
+        return _
     
-    keys = node.keys() + [None]
+    def _msgid(self):
+        """Create an i18n msgid from the tag contents."""
+
+        out = StringIO(self.text)
+        for element in self:
+            name = element.i18n_name
+            if name:
+                out.write("${%s}" % name)
+                out.write(element.tail)
+            else:
+                out.write(lxml.etree.tostring(element))
+
+        msgid = out.getvalue().strip()
+        msgid = msgid.replace('  ', ' ').replace('\n', '')
+        
+        return msgid
+            
+    def _attributes(self):
+        """Aggregate static, dynamic and translatable attributes."""
+
+        attributes = {}
+
+        # static attributes are at the bottom of the food chain
+        static = [key for key in self.keys() if not key.startswith('{')]
+        for key in static:
+            attributes[key] = self.attrib[key]
+
+        # dynamic attributes
+        attrs = self.attributes
+        if attrs is not None:
+            for variables, expression in attrs:
+                if len(variables) != 1:
+                    raise ValueError, "Tuple definitions in assignment clause is not supported."
+
+                variable = variables[0]
+                attributes[variable] = _escape(expression, '"')
+        else:
+            attrs = []
+
+        dynamic = [key for (key, expression) in attrs]
+
+        # translated attributes
+        if self.i18n_attributes:
+            for variable, msgid in self.i18n_attributes:
+                if variable in static:
+                    static_expression = repr(attributes[variable])
+
+                if msgid:
+                    if variable in dynamic:
+                        raise ValueError, "Message id not allowed in conjunction with " + \
+                                          "a dynamic attribute."
+
+                    expression = [repr(msgid)]
+
+                    if variable in static:
+                        expression = _translate(expression, default=static_expression)            
+                    else:
+                        expression = _translate(expression)
+                else:
+                    if variable in dynamic:
+                        expression = _translate(attributes[variable])
+                    elif variable in static:
+                        expression = _translate(static_expression)                
+                    else:
+                        raise ValueError, "Must be either static or dynamic attribute " + \
+                                          "when no message id is supplied."
+
+                attributes[variable] = expression
+
+        return attributes
+
+    define = attribute(
+        "{http://xml.zope.org/namespaces/tal}define", attrs.definitions)
+    condition = attribute(
+        "{http://xml.zope.org/namespaces/tal}condition", attrs.expression)
+    repeat = attribute(
+        "{http://xml.zope.org/namespaces/tal}repeat", attrs.definition)
+    attributes = attribute(
+        "{http://xml.zope.org/namespaces/tal}attributes", attrs.definitions)
+    content = attribute(
+        "{http://xml.zope.org/namespaces/tal}content", attrs.expression)
+    replace = attribute(
+        "{http://xml.zope.org/namespaces/tal}replace", attrs.expression)
+    omit = attribute(
+        "{http://xml.zope.org/namespaces/tal}omit-tag", attrs.expression)
+    i18n_translate = attribute(
+        "{http://xml.zope.org/namespaces/i18n}translate", attrs.name)
+    i18n_attributes = attribute(
+        "{http://xml.zope.org/namespaces/i18n}attrs", attrs.mapping)
+    i18n_domain = attribute(
+        "{http://xml.zope.org/namespaces/i18n}domain", attrs.name)
+    i18n_name = attribute(
+        "{http://xml.zope.org/namespaces/i18n}name", attrs.name)
     
-    handlers = [handler(node) for key, handler in ns_lookup_table \
-                if key in keys]
+# set up namespace
+lookup = lxml.etree.ElementNamespaceClassLookup()
+parser = lxml.etree.XMLParser()
+parser.setElementClassLookup(lookup)
 
-    # remove namespace attributes
-    for key, handler in ns_lookup_table:
-        if key is not None and key in node.attrib:
-            del node.attrib[key]
+namespace = lxml.etree.Namespace('http://www.w3.org/1999/xhtml')
+namespace[None] = Element
 
-    # update node
-    for handler in handlers:
-        node = handler.update(node)
+def translate(body, params=[]):
+    tree = lxml.etree.parse(StringIO(body), parser)
+    root = tree.getroot()
 
-    # begin tag
-    for handler in handlers:
-        handler.begin(stream)
+    stream = io.CodeIO(indentation=1, indentation_string="\t")
 
-    # process children
-    if node:
-        for element in node:
-            visit(element, stream)
+    root.visit(stream)
 
-    # end tag
-    for handler in reversed(handlers):
-        handler.end(stream)
+    code = stream.getvalue()
+    args = ', '.join(params)
+    if args: args += ', '
+    
+    return wrapper % (args, code), {'utils': utils}
 
-    return stream
+def _translate(expressions, mapping=None, default=None):
+    return [("_translate(%s, domain=_domain, mapping=%s, " + \
+             "target_language=_target_language, default=%s)") %
+            (exp, mapping, default) for exp in expressions]
+
+def _escape(expressions, delim):
+    return ["_escape(%s, '\\%s')" % (exp, delim) for exp in expressions]
+
+def _not(expressions):
+    return ["not (%s)" % exp for exp in expressions]

Added: z3c.pt/trunk/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/z3c/pt/utils.py	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/utils.py	2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,84 @@
+import zope.i18n
+
+import cgi
+from StringIO import StringIO
+
+def handler(key=None):
+    def decorate(f):
+        def g(node):
+            if key is None:
+                return f(node, None)
+            return f(node, node.get(key))
+        g.__ns__ = key
+        return g
+    return decorate
+
+def initialize_i18n():
+    return (None, zope.i18n.translate)
+
+def initialize_tal():
+    return ({}, repeatdict())
+
+def initialize_helpers():
+    return (cgi.escape, object())
+
+def initialize_stream():
+    return StringIO()
+    
+def getLanguage(request):
+    return ''
+
+s_counter = 0
+
+class scope(list):
+    def __init__(self, *args):
+        global s_counter
+        self.hash = s_counter
+        s_counter += 1
+
+    def __hash__(self):
+        return self.hash
+
+class repeatitem(object):
+    def __init__(self, iterator, length):
+        self.length = length
+        self.iterator = iterator
+        
+    @property
+    def index(self):
+        return self.length - len(self.iterator) - 1
+        
+    @property
+    def start(self):
+        return self.index == 0
+
+    @property
+    def end(self):
+        return self.index == self.length - 1
+
+    def number(self):
+        return self.index + 1
+
+    def odd(self):
+        return bool(self.index % 2)
+
+    def even(self):
+        return not self.odd()
+
+class repeatdict(dict):
+    def __setitem__(self, key, iterator):
+        try:
+            length = len(iterator)
+        except TypeError:
+            length = None
+            
+        dict.__setitem__(self, key, (iterator, length))
+        
+    def __getitem__(self, key):
+        value, length = dict.__getitem__(self, key)
+
+        if not isinstance(value, repeatitem):
+            value = repeatitem(value, length)
+            self.__setitem__(key, value)
+
+        return value



More information about the Checkins mailing list