[Checkins] SVN: z3c.pt/trunk/ Added Genshi-support.

Malthe Borch mborch at gmail.com
Wed Aug 6 21:09:03 EDT 2008


Log message for revision 89478:
  Added Genshi-support.

Changed:
  U   z3c.pt/trunk/CHANGES.txt
  U   z3c.pt/trunk/README.txt
  U   z3c.pt/trunk/setup.py
  U   z3c.pt/trunk/src/z3c/pt/README.txt
  U   z3c.pt/trunk/src/z3c/pt/clauses.py
  U   z3c.pt/trunk/src/z3c/pt/config.py
  U   z3c.pt/trunk/src/z3c/pt/configure.zcml
  U   z3c.pt/trunk/src/z3c/pt/expressions.py
  U   z3c.pt/trunk/src/z3c/pt/generation.py
  U   z3c.pt/trunk/src/z3c/pt/template.py
  U   z3c.pt/trunk/src/z3c/pt/testing.py
  U   z3c.pt/trunk/src/z3c/pt/translation.py
  U   z3c.pt/trunk/src/z3c/pt/translation.txt
  U   z3c.pt/trunk/src/z3c/pt/types.py
  U   z3c.pt/trunk/src/z3c/pt/utils.py

-=-
Modified: z3c.pt/trunk/CHANGES.txt
===================================================================
--- z3c.pt/trunk/CHANGES.txt	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/CHANGES.txt	2008-08-07 01:09:02 UTC (rev 89478)
@@ -1,9 +1,18 @@
 Changelog
 ---------
 
-Version 0.8.x
+Version 0.9.x
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+Version 0.9 - August 7, 2008
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Added support for Genshi-templates.
+  [malthe]
+
+- Cleanup and refactoring of translation module.
+  [malthe]
+  
 - If the template source contains a DOCTYPE declaration, output it
   during rendering. [chrism]
 

Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/README.txt	2008-08-07 01:09:02 UTC (rev 89478)
@@ -1,21 +1,25 @@
 Overview
 --------
 
-The z3c.pt package provides an alternative implementation of the TAL
-template language including i18n. It also provides a simple text
-template class that allows expression interpolation.
+The z3c.pt package provides a fast template engine that supports the
+following dialects of the attribute template language:
 
-Casual benchmarks pegs it 12x more performant than ``zope.pagetemplate``.
+* Zope TAL
+* Zope i18n
+* Genshi
 
+Non-structural documents are supported through Genshi's variable
+interpolation syntax which is also available for XML templates.
+
+Casual benchmarks pegs it 16x more performant than the reference
+implementations for Zope TAL and Genshi.
+
 In a nutshell:
 
-* Templates are parsed and compiled into Python bytecode
-* While rendering only Python code is executed and no parsing happens
+* Templates are serialized and compiled into Python bytecode
 * Pluggable expression implementation
-* Support for expression interpolation using the ${<expression>}-format
-* Non-XML friendly
 
-Note: The METAL macro language is not supported.
+Note: Zope's METAL macro language is not supported.
 
 
 Usage
@@ -26,14 +30,17 @@
 file (configure.zcml).
 
 
-Template and expression language
---------------------------------
+Compiler notes
+--------------
 
-The template and expression language is based loosely on the TAL 1.4
-specification*. Some notable changes:
+The compiler is largely compatible with the targeted dialects. The TAL
+implementation is based on the 1.4 language specification* while the
+Genshi implementation is based on the documents for the 0.5 release**.
 
-1. Tuples are allowed in the tal:define statement:
+Some notable changes:
 
+1. Tuple unpacking is allowed when defining variables:
+
       tal:define="(a, b, c) [1, 2, 3]"
 
 2. Generators are allowed in tal:repeat statements. Note that the
@@ -48,15 +55,15 @@
 
    can be used instead of ``dictionary['key']``.
 
-4. Expression interpolation is allowed in attributes and HTML content.
+4. Default expression type can be set using ``tal:default-expression``.
+   This is an alternative to providing the expression type before each
+   expression.
 
-       <a href="mailto:${context.email}">${context.email}</a>
+5. The XPath select function provided to py:match-elements uses lxml
+   and requires the use of the default namespace prefix "xmlns".
 
-5. Default expression type can be set using ``tal:default-expression``.
-   This is an alternative to providing the expression type before each
-   expression.
-   
 .. _TAL: http://wiki.zope.org/ZPT/TALSpecification14
+.. _Genshi: http://genshi.edgewall.org/wiki/Documentation/xml-templates.html
 
 
 Development
@@ -68,3 +75,5 @@
 
 http://svn.zope.org/z3c.pt/trunk#egg=z3c.pt-dev
 
+Want to contribute? Join #zope3-dev on Freenode IRC.
+

Modified: z3c.pt/trunk/setup.py
===================================================================
--- z3c.pt/trunk/setup.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/setup.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -1,6 +1,6 @@
 from setuptools import setup, find_packages
 
-version = '0.8.8dev'
+version = '0.9dev'
 
 setup(name='z3c.pt',
       version=version,

Modified: z3c.pt/trunk/src/z3c/pt/README.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/README.txt	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/README.txt	2008-08-07 01:09:02 UTC (rev 89478)
@@ -39,14 +39,17 @@
   >>> from z3c.pt import PageTemplate
   >>> template = PageTemplate("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   Hello World!
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <py:match path="xmlns:greeting">Hello ${select('@name')[0]}!</py:match>
+  ...   <greeting name="World" />
   ... </div>
   ... """)
 
   >>> print template()
   <div>
-     Hello World!
+    Hello World!
+  <BLANKLINE>
   </div>
 
 Providing the path to a template file:
@@ -57,7 +60,8 @@
   >>> template = PageTemplateFile(path+'/helloworld.pt')
   >>> print template()
   <div>
-     Hello World!
+    Hello World!
+  <BLANKLINE>
   </div>
 
 Keyword-parameters are passed on to the template namespace as-is.

Modified: z3c.pt/trunk/src/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -423,6 +423,19 @@
     def uses_variable(self, var):
         return False
 
+class Visit(object):
+    def __init__(self, element):
+        self.element = element
+        
+    def begin(self, stream):
+        self.element.visit(skip_macro=False)
+
+    def end(self, stream):
+        pass
+
+    def uses_variable(self, var):
+        return False    
+
 class Tag(object):
     """
       >>> from z3c.pt.generation import CodeIO
@@ -463,7 +476,7 @@
             
     """
 
-    def __init__(self, tag, attributes={}, selfclosing=False):
+    def __init__(self, tag, attributes={}, selfclosing=False, expression=None):
         i = tag.find('}')
 
         if i != -1:
@@ -473,11 +486,11 @@
 
         self.selfclosing = selfclosing
         self.attributes = attributes
+        self.expression = expression
         
     def begin(self, stream):
         stream.out('<%s' % self.tag)
 
-        # static attributes
         static = filter(
             lambda (attribute, value): \
             not isinstance(value, types.expression),
@@ -494,7 +507,26 @@
                 escape(expression, '"')))
 
         temp = stream.save()
+        temp2 = stream.save()
 
+        if self.expression:
+            stream.write("for %s, %s in (%s).items():" % (temp, temp2, self.expression))
+            stream.indent()
+            if unicode_required_flag:
+                stream.write("if isinstance(%s, unicode):" % temp2)
+                stream.indent()
+                stream.escape(temp2)
+                stream.write("_write(' %%s=\"%%s\"' %% (%s, %s))" % (temp, temp2))
+                stream.outdent()
+                stream.write("elif %s is not None:" % temp2)
+            else:
+                stream.write("if %s is not None:" % temp2)
+            stream.indent()
+            stream.write("%s = str(%s)" % (temp2, temp2))
+            stream.escape(temp2)
+            stream.write("_write(' %%s=\"%%s\"' %% (%s, %s))" % (temp, temp2))
+            stream.outdent()
+        
         for attribute, value in dynamic:
             assign = Assign(value)
             assign.begin(stream, temp)
@@ -503,24 +535,8 @@
                 stream.write("if isinstance(%s, unicode):" % temp)
                 stream.indent()
                 stream.write("_write(' %s=\"')" % attribute)
-                # Inlined escape function
                 stream.write("_esc = %s" % temp)
-                stream.write("if '&' in _esc:")
-                stream.indent()
-                stream.write("_esc = _esc.replace('&', '&amp;')")
-                stream.outdent()
-                stream.write("if '<' in _esc:")
-                stream.indent()
-                stream.write("_esc = _esc.replace('<', '&lt;')")
-                stream.outdent()
-                stream.write("if '>' in _esc:")
-                stream.indent()
-                stream.write("_esc = _esc.replace('>', '&gt;')")
-                stream.outdent()
-                stream.write("if '\"' in _esc:")
-                stream.indent()
-                stream.write("_esc = _esc.replace('\"', '&quot;')")
-                stream.outdent()
+                stream.escape("_esc")
                 stream.write("_write(_esc)")
                 stream.write("_write('\"')")
                 stream.outdent()
@@ -529,31 +545,16 @@
                 stream.write("if %s is not None:" % temp)
             stream.indent()
             stream.write("_write(' %s=\"')" % attribute)
-            # Inlined escape function
             stream.write("_esc = str(%s)" % temp)
-            stream.write("if '&' in _esc:")
-            stream.indent()
-            stream.write("_esc = _esc.replace('&', '&amp;')")
-            stream.outdent()
-            stream.write("if '<' in _esc:")
-            stream.indent()
-            stream.write("_esc = _esc.replace('<', '&lt;')")
-            stream.outdent()
-            stream.write("if '>' in _esc:")
-            stream.indent()
-            stream.write("_esc = _esc.replace('>', '&gt;')")
-            stream.outdent()
-            stream.write("if '\"' in _esc:")
-            stream.indent()
-            stream.write("_esc = _esc.replace('\"', '&quot;')")
-            stream.outdent()
+            stream.escape("_esc")
             stream.write("_write(_esc)")
             stream.write("_write('\"')")
             stream.outdent()
             assign.end(stream)
 
         stream.restore()
-
+        stream.restore()
+        
         if self.selfclosing:
             stream.out(" />")
         else:
@@ -860,3 +861,33 @@
 
     def uses_variable(self, var):
         return False
+
+class Method(object):
+    """
+      >>> from z3c.pt.generation import CodeIO
+      >>> from z3c.pt.testing import pyexp
+      >>> from StringIO import StringIO
+      
+      >>> _out = StringIO(); _write = _out.write; stream = CodeIO()
+      >>> method = Method('test', ('a', 'b', 'c'))
+      >>> method.begin(stream)
+      >>> stream.write('print a, b, c')
+      >>> method.end(stream)
+      >>> exec stream.getvalue()
+      >>> test(1, 2, 3)
+      1 2 3
+      
+    """
+
+    def __init__(self, name, args):
+        self.name = name
+        self.args = args
+        
+    def begin(self, stream):
+        stream.write('def %s(%s):' % (self.name, ", ".join(self.args)))
+        stream.indent()
+
+    def end(self, stream):
+        stream.outdent()
+        
+    

Modified: z3c.pt/trunk/src/z3c/pt/config.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/config.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/config.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -15,3 +15,11 @@
 DISABLE_I18N_KEY = 'Z3C_PT_DISABLE_I18N'
 DISABLE_I18N = os.environ.get(DISABLE_I18N_KEY, 'false')
 DISABLE_I18N = DISABLE_I18N.lower() in ('yes', 'true', 'on')
+
+XML_NS = "http://www.w3.org/1999/xhtml"
+TAL_NS = "http://xml.zope.org/namespaces/tal"
+I18N_NS = "http://xml.zope.org/namespaces/i18n"
+PY_NS = "http://genshi.edgewall.org"
+NS_MAP = dict(py=PY_NS, tal=PY_NS)
+            
+        

Modified: z3c.pt/trunk/src/z3c/pt/configure.zcml
===================================================================
--- z3c.pt/trunk/src/z3c/pt/configure.zcml	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/configure.zcml	2008-08-07 01:09:02 UTC (rev 89478)
@@ -4,11 +4,11 @@
   
   <utility
      name="python"
-     factory=".expressions.PythonTranslation" />
+     component=".expressions.PythonTranslation" />
 
   <utility
      name="path"
-     factory=".expressions.PathTranslation" />
+     component=".expressions.PathTranslation" />
 
   <adapter
      name="string"

Modified: z3c.pt/trunk/src/z3c/pt/expressions.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/expressions.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/expressions.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -19,6 +19,8 @@
 
     re_pragma = re.compile(r'^\s*(?P<pragma>[a-z]+):\s*')
     re_interpolation = re.compile(r'(?P<prefix>[^\\]\$|^\$){((?P<expression>.*)})?')
+    re_method = re.compile(r'^(?P<name>[A-Za-z0-9_]+)'
+                           '\((?P<args>[A-Za-z0-9_]+\s*(,\s*[A-Za-z0-9_]+)*)\)')
 
     def name(self, string):
         return string
@@ -173,7 +175,21 @@
         >>> definitions("(variable1, variable2) (expression1, expression2)")
         definitions((declaration('variable1', 'variable2'),
                     value('(expression1, expression2)')),)
+
+        Space, the 'in' operator and '=' may be used to separate
+        variable from expression.
+
+        >>> definitions("variable in expression")
+        definitions((declaration('variable',), value('expression')),)        
         
+        >>> definitions("variable1 = expression1; variable2 = expression2")
+        definitions((declaration('variable1',), value('expression1')),
+                    (declaration('variable2',), value('expression2')))
+
+        >>> definitions("variable1=expression1; variable2=expression2")
+        definitions((declaration('variable1',), value('expression1')),
+                    (declaration('variable2',), value('expression2')))
+        
         A define clause that ends in a semicolon:
         
         >>> definitions("variable expression;")
@@ -209,16 +225,28 @@
                 var = self.declaration(string[i+1:j])
                 j += 1
             else:
-                j = string.find(' ', i + 1)
-                if j == -1:
+                j = string.find('=', i + 1)
+                k = string.find(' ', i + 1)
+                if k < j and k > -1 or j < 0:
+                    j = k
+                
+                if j < 0:
                     var = self.declaration(string[i:])
                     j = len(string)
                 else:
                     var = self.declaration(string[i:j])
 
             # get expression
-            i = j
+            i = j + len(string) - j - len(string[j:].lstrip())
             while j < len(string):
+                token = string[i:]
+                if token.startswith('=='):
+                    raise ValueError("Invalid variable definition (%s)." % string)
+                elif token.startswith('='):
+                    i += 1
+                elif token.startswith('in'):
+                    i += 2
+                
                 j = string.find(';', j+1)
                 if j == -1:
                     j = len(string)
@@ -402,6 +430,25 @@
 
         return m
 
+    def method(self, string):
+        """Parse a method definition.
+
+        >>> method = ExpressionTranslation().method
+
+        >>> method('name(a, b, c)')
+        name(a, b, c)
+        
+        """
+
+        m = self.re_method.match(string)
+        if m is None:
+            return None
+
+        name = m.group('name')
+        args = [arg.strip() for arg in m.group('args').split(',')]
+
+        return types.method(name, args)
+        
 class PythonTranslation(ExpressionTranslation):
     def validate(self, string):
         """We use the ``parser`` module to determine if
@@ -414,7 +461,9 @@
             string = string.encode('utf-8')
 
         return types.value(string)
-            
+
+PythonTranslation = PythonTranslation()
+
 class StringTranslation(ExpressionTranslation):
     zope.component.adapts(interfaces.IExpressionTranslation)
 
@@ -576,3 +625,5 @@
             value = types.value('not(%s)' % value)
 
         return value
+
+PathTranslation = PathTranslation()

Modified: z3c.pt/trunk/src/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -72,15 +72,20 @@
     def __call__(self):
         # prepare template arguments
         args = self.params
-        # We need to ensure we have _context for the i18n handling in the
-        # arguments. The default template implementations pass this in
-        # explicitly.
+        
+        # we need to ensure we have _context for the i18n handling in
+        # the arguments. the default template implementations pass
+        # this in explicitly.
         if '_context' not in args:
             args = args + ('_context=None', )
         args = ', '.join(args)
         if args:
             args += ', '
 
+        # pass selectors
+        for selector in self.stream.selectors:
+            args += '%s=None, ' % selector
+
         code = self.stream.getvalue()
         return wrapper % (args, code), {'generation': z3c.pt.generation}
 
@@ -112,6 +117,7 @@
         self.indentation_string = indentation_string
         self.queue = ''
         self.scope = [set()]
+        self.selectors = {}
         self.annotations = {}
         
         self._variables = {}
@@ -164,6 +170,24 @@
         self.cook()
         return BufferIO.getvalue(self)
 
+    def escape(self, variable):
+        self.write("if '&' in %s:" % variable)
+        self.indent()
+        self.write("%s = %s.replace('&', '&amp;')" % (variable, variable))
+        self.outdent()
+        self.write("if '<' in %s:" % variable)
+        self.indent()
+        self.write("%s = %s.replace('<', '&lt;')" % (variable, variable))
+        self.outdent()
+        self.write("if '>' in %s:" % variable)
+        self.indent()
+        self.write("%s = %s.replace('>', '&gt;')" % (variable, variable))
+        self.outdent()
+        self.write("if '\"' in %s:" % variable)
+        self.indent()
+        self.write("%s = %s.replace('\"', '&quot;')" % (variable, variable))
+        self.outdent()
+        
     def begin(self, clauses):
         if isinstance(clauses, (list, tuple)):
             for clause in clauses:

Modified: z3c.pt/trunk/src/z3c/pt/template.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/template.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -38,6 +38,7 @@
 
         self.source = source
         self.source_write()
+        self.selectors = generator.stream.selectors
         self.annotations = generator.stream.annotations
         
         _globals.update(suite._globals)
@@ -48,13 +49,16 @@
         return _locals['render']
 
     def render(self, **kwargs):
-        # A ''.join of a dict uses only the keys
+        # a ''.join of a dict uses only the keys
         signature = self.signature + hash(''.join(kwargs))
 
         template = self.registry.get(signature, None)
         if template is None:
             self.registry[signature] = template = self.cook(kwargs.keys())
 
+        # pass in selectors
+        kwargs.update(self.selectors)
+
         if PROD_MODE:
             return template(**kwargs)
 

Modified: z3c.pt/trunk/src/z3c/pt/testing.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -1,5 +1,4 @@
 import expressions
 
 def pyexp(string):
-    translator = expressions.PythonTranslation()
-    return translator.expression(string)
+    return expressions.PythonTranslation.expression(string)

Modified: z3c.pt/trunk/src/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -3,46 +3,156 @@
 from StringIO import StringIO
 import lxml.etree
 
+import itertools
 import generation
 import clauses
 import interfaces
+import expressions
 import types
 import utils
+import config
 
 class Element(lxml.etree.ElementBase):
-    def begin(self, stream):
-        stream.scope.append(set())
-        stream.begin(self._clauses())
+    """Base compiler element class.
+
+    This class represents a node in the template tree. To start
+    compilation at this node, use the ``start`` method, providing a
+    code stream object.
+    """
+
+    _stream = None
+
+    def start(self, stream):
+        self._stream = stream
+        self.visit()
+
+    def update(self):
+        self._preprocess_genshi()
+
+    def begin(self):
+        self.stream.scope.append(set())
+        self.stream.begin(self._serialize())
         
-    def end(self, stream):
-        stream.end(self._clauses())
-        stream.scope.pop()
+    def end(self):
+        self.stream.end(self._serialize())
+        self.stream.scope.pop()
 
-    def body(self, stream):
-        skip = self.tal_replace or self.tal_content or self.i18n_translate is not None
+    def body(self):
+        skip = self._replace or self._content or \
+               self.i18n_translate is not None
+        
         if not skip:
-            for element in [e for e in self
-                              if isinstance(e, lxml.etree._Comment)]:
+            for element in self:
+                element.update()
+
+            for element in self:
+                element.visit()
+                    
+    def visit(self, skip_macro=True):
+        assert self.stream is not None, "Must use ``start`` method."
+        
+        if skip_macro and self.py_def is not None:
+            return
+
+        for element in self:
+            if isinstance(element, lxml.etree._Comment):
                 self._wrap_comment(element)
 
-            seen = set()
-            while len(seen) < len(self):
-                element = set(self).difference(seen).pop()
-                element.interpolate(stream)
-                seen.add(element)
+        self.update()
+        self.begin()
+        self.body()
+        self.end()
+
+    @property
+    def stream(self):
+        root = self.getroottree().getroot()
+        return root._stream
+    
+    @property
+    def translator(self):
+        while self.tal_default_expression is None:
+            self = self.getparent()
+            if self is None:
+                raise ValueError("Default expression not set.")
+            
+        return component.getUtility(
+            interfaces.IExpressionTranslation, name=self.tal_default_expression)
+
+    def _preprocess_genshi(self):
+        """Genshi preprocessing."""
+
+        stream = self.stream
+        
+        # Step 1: Convert py:choose, py:when, py:otherwise into
+        # tal:define, tal:condition
+        choose_expression = self._pull_attribute(utils.py_attr('choose'))
+        if choose_expression is not None:
+            choose_variable = stream.save()
+            
+            if choose_expression:
+                self._add_tal_define(choose_variable, choose_expression)
                 
-            for element in self:
-                element.visit(stream)
+            # select all elements that have the "py:when" controller,
+            # unless a "py:choose" expression sits in-between
+            variables = []
+            for element in self.xpath(
+                './*[@py:when]|.//*[not(@py:choose)]/*[@py:when]',
+                namespaces={'py': config.PY_NS}):
+
+                expression = element._pull_attribute(utils.py_attr('when'))
+                variable = stream.save()
+                variables.append(variable)
+
+                # add definition to ancestor
+                self._add_tal_define(variable, expression)
+                
+                # add condition to element
+                if choose_expression:
+                    expression = "python: %s == %s" % (
+                        choose_variable, variable)
+                else:
+                    expression = "python: %s" % variable
                     
-    def visit(self, stream):
-        self.begin(stream)
-        self.body(stream)
-        self.end(stream)
+                element.attrib[utils.tal_attr('condition')] = expression
 
-    def interpolate(self, stream):
-        """The current interpolation strategy is to translate the
-        interpolation statements into TAL."""
-        
+            # process any "py:otherwise"-controllers
+            for element in self.xpath(
+                './*[@py:otherwise]|.//*[not(@py:choose)]/*[@py:otherwise]',
+                namespaces={'py': config.PY_NS}):
+                if choose_expression:
+                    expression = "python: %s not in %s" % (
+                        choose_variable, repr(tuple(variables)))
+                else:
+                    expression = "python: not(%s)" % " or ".join(variables)
+                    
+                element.attrib[utils.tal_attr('condition')] = expression
+
+        # Step 2: Process "py:match" macros
+        for element in self:
+            if element.py_match is None:
+                continue
+            
+            nsmap = element.nsmap.copy()
+
+            # default namespace is not allowed in XPath
+            nsmap['xmlns'] = nsmap[None]
+            del nsmap[None]
+
+            # define macro
+            name = stream.save()
+            element.attrib[utils.py_attr('def')] = "%s(select)" % name
+
+            matches = self.getroottree().xpath(element.py_match, namespaces=nsmap)
+            for match in matches:
+                # save reference to bound xpath-function
+                select = stream.save()
+                stream.selectors[select] = match.xpath
+
+                # replace matched element with macro
+                expression = "%s(%s)" % (name, select)
+                match.attrib[utils.tal_attr('replace')] = expression
+                                
+        # Step 3: Variable interpolation
         translator = self.translator
         
         if self.text is not None:
@@ -51,58 +161,48 @@
                 if m is None:
                     break
 
-                t = parser.makeelement(
-                    '{http://xml.zope.org/namespaces/tal}interpolation')
+                t = parser.makeelement(utils.tal_attr('interpolation'))
                 t.attrib['replace'] = m.group('expression')
                 t.tail = self.text[m.end():]
                 self.insert(0, t)
+                t.update()
 
                 if m.start() == 0:
                     self.text = self.text[1:m.start()+1]
                 else:
                     self.text = self.text[:m.start()+1]
 
-        # interpolate tail
         if self.tail is not None:
             while self.tail:
                 m = translator.interpolate(self.tail)
                 if m is None:
                     break
 
-                t = parser.makeelement(
-                    '{http://xml.zope.org/namespaces/tal}interpolation')
+                t = parser.makeelement(utils.tal_attr('interpolation'))
                 t.attrib['replace'] = m.group('expression')
                 t.tail = self.tail[m.end():]
                 parent = self.getparent()
                 parent.insert(parent.index(self)+1, t)
-
+                t.update()
+                                
                 self.tail = self.tail[:m.start()+len(m.group('prefix'))-1]
                 
-        # interpolate attributes
-        for name in self._static_attributes():
+        for name in self._get_static_attributes():
             value = self.attrib[name]
 
             if translator.interpolate(value):
                 del self.attrib[name]
 
-                attributes = '{http://xml.zope.org/namespaces/tal}attributes'
+                attributes = utils.tal_attr('attributes')
                 expr = '%s string: %s' % (name, value)
                 if attributes in self.attrib:
                     self.attrib[attributes] += '; %s' % expr
                 else:
                     self.attrib[attributes] = expr
                     
-    @property
-    def translator(self):
-        while self.tal_default_expression is None:
-            self = self.getparent()
-            if self is None:
-                raise ValueError("Default expression not set.")
-            
-        return component.getUtility(
-            interfaces.IExpressionTranslation, name=self.tal_default_expression)
-                
-    def _clauses(self):
+    def _serialize(self):
+        """Serialize element into clause-statements."""
+        
         _ = []
 
         # i18n domain
@@ -111,17 +211,34 @@
                 "_domain", types.value(repr(self.i18n_domain))))
 
         # defines
-        if self.tal_define is not None:
-            for variables, expression in self.tal_define:
+        if self._define is not None:
+            for variables, expression in self._define:
                 _.append(clauses.Define(variables, expression))
 
+        # macro
+        for element in tuple(self):
+            if not isinstance(element, Element):
+                continue
+            
+            py_def = element.py_def
+            if py_def is not None:
+                # define macro
+                subclauses = []
+                subclauses.append(clauses.Method("_macro", py_def.args))
+                subclauses.append(clauses.Visit(element))
+                _.append(clauses.Group(subclauses))
+                
+                # assign to variable
+                _.append(clauses.Define(
+                    py_def.name, types.parts((types.value("_macro"),))))
+                    
         # condition
-        if self.tal_condition is not None:
-            _.append(clauses.Condition(self.tal_condition))
+        if self._condition is not None:
+            _.append(clauses.Condition(self._condition))
 
         # repeat
-        if self.tal_repeat is not None:
-            variables, expression = self.tal_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 "
@@ -133,20 +250,20 @@
             _.append(clauses.Out(self.tail.encode('utf-8'), defer=True))
 
         # compute dynamic flag
-        dynamic = (self.tal_replace or
-                   self.tal_content or
+        dynamic = (self._replace or
+                   self._content or
                    self.i18n_translate is not None)
         
         # tag
-        if self.tal_replace is None:
+        if self._replace is None:
             selfclosing = self.text is None and not dynamic and len(self) == 0
-            tag = clauses.Tag(self.tag, self._attributes(),
-                              selfclosing=selfclosing)
+            tag = clauses.Tag(self.tag, self._get_attributes(),
+                              expression=self.py_attrs, selfclosing=selfclosing)
 
-            if self.tal_omit:
-                _.append(clauses.Condition(_not(self.tal_omit), [tag],
+            if self._omit:
+                _.append(clauses.Condition(_not(self._omit), [tag],
                                            finalize=False))
-            elif self.tal_omit is not None:
+            elif self._omit is not None:
                 pass
             else:
                 _.append(tag)
@@ -156,8 +273,8 @@
             _.append(clauses.Out(self.text.encode('utf-8')))
 
         # dynamic content and content translation
-        replace = self.tal_replace
-        content = self.tal_content
+        replace = self._replace
+        content = self._content
 
         if replace and content:
             raise ValueError("Can't use replace clause together with "
@@ -180,7 +297,6 @@
 
                 # 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:
@@ -196,7 +312,7 @@
                     subclauses.append(clauses.Define(
                         ('_out', '_write'),
                         types.value('generation.initialize_stream()')))
-                    subclauses.append(clauses.Group(element._clauses()))
+                    subclauses.append(clauses.Visit(element))
                     subclauses.append(clauses.Assign(
                         types.value('_out.getvalue()'),
                         "%s['%s']" % (mapping, name)))
@@ -234,9 +350,7 @@
     def _wrap_comment(self, element):
         index = self.index(element)
 
-        t = parser.makeelement(
-            '{http://xml.zope.org/namespaces/tal}comment')
-
+        t = parser.makeelement(utils.tal_attr('comment'))
         t.attrib['omit-tag'] = ''
         t.tail = element.tail
         t.text = '<!--' + element.text + '-->'
@@ -246,6 +360,7 @@
 
         self.remove(element)
         self.insert(index, t)
+        t.update()
     
     def _msgid(self):
         """Create an i18n msgid from the tag contents."""
@@ -264,7 +379,7 @@
         
         return msgid
 
-    def _static_attributes(self):
+    def _get_static_attributes(self):
         attributes = {}
 
         for key in self.keys():
@@ -273,11 +388,11 @@
 
         return attributes
         
-    def _attributes(self):
+    def _get_attributes(self):
         """Aggregate static, dynamic and translatable attributes."""
 
         # static attributes are at the bottom of the food chain
-        attributes = self._static_attributes()
+        attributes = self._get_static_attributes()
         
         # dynamic attributes
         attrs = self.tal_attributes
@@ -323,32 +438,94 @@
 
         return attributes
 
+    def _pull_attribute(self, name, default=None):
+        if name in self.attrib:
+            value = self.attrib[name]
+            del self.attrib[name]
+            return value
+        return default
+    
+    def _add_tal_define(self, variable, expression):
+        name = utils.tal_attr('define')
+        define = "%s %s; " % (variable, expression)
+
+        if name in self.attrib:
+            self.attrib[name] += define
+        else:
+            self.attrib[name] = define
+    
+    @property
+    def _define(self):
+        return self.tal_define or self.py_with
+
+    @property
+    def _condition(self):
+        return self.tal_condition or self.py_if
+
+    @property
+    def _repeat(self):
+        return self.tal_repeat or self.py_for
+
+    @property
+    def _replace(self):
+        return self.tal_replace or self.py_replace
+
+    @property
+    def _content(self):
+        return self.tal_content or self.py_content
+
+    @property
+    def _omit(self):
+        if self.tal_omit is not None:
+            return self.tal_omit
+        return self.py_strip
+    
     tal_define = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}define", lambda p: p.definitions)
+        utils.tal_attr('define'), lambda p: p.definitions)
     tal_condition = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}condition",
-        lambda p: p.expression)
+        utils.tal_attr('condition'), lambda p: p.expression)
     tal_repeat = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}repeat", lambda p: p.definition)
+        utils.tal_attr('repeat'), lambda p: p.definition)
     tal_attributes = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}attributes",
-        lambda p: p.definitions)
+        utils.tal_attr('attributes'), lambda p: p.definitions)
     tal_content = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}content", lambda p: p.output)
+        utils.tal_attr('content'), lambda p: p.output)
     tal_replace = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}replace", lambda p: p.output)
+        utils.tal_attr('replace'), lambda p: p.output)
     tal_omit = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}omit-tag", lambda p: p.expression)
+        utils.tal_attr('omit-tag'), lambda p: p.expression)
     tal_default_expression = utils.attribute(
-        "{http://xml.zope.org/namespaces/tal}default-expression")
+        utils.tal_attr('default-expression'))
     i18n_translate = utils.attribute(
-        "{http://xml.zope.org/namespaces/i18n}translate")
+        utils.i18n_attr('translate'))
     i18n_attributes = utils.attribute(
-        "{http://xml.zope.org/namespaces/i18n}attributes", lambda p: p.mapping)
+        utils.i18n_attr('attributes'), lambda p: p.mapping)
     i18n_domain = utils.attribute(
-        "{http://xml.zope.org/namespaces/i18n}domain")
+        utils.i18n_attr('domain'))
     i18n_name = utils.attribute(
-        "{http://xml.zope.org/namespaces/i18n}name")
+        utils.i18n_attr('name'))
+    py_if = utils.attribute(
+        utils.py_attr('if'), lambda p: p.expression)
+    py_for = utils.attribute(
+        utils.py_attr('for'), lambda p: p.definition)
+    py_with = utils.attribute(
+        utils.py_attr('with'), lambda p: expressions.PythonTranslation.definitions)
+    py_choose = utils.attribute(
+        utils.py_attr('choose'), lambda p: p.expression)
+    py_when = utils.attribute(
+        utils.py_attr('when'), lambda p: p.expression)
+    py_match = utils.attribute(
+        utils.py_attr('match'))
+    py_def = utils.attribute(
+        utils.py_attr('def'), lambda p: p.method)
+    py_attrs = utils.attribute(
+        utils.py_attr('attrs'), lambda p: p.expression)
+    py_content = utils.attribute(
+        utils.py_attr('content'), lambda p: p.output)
+    py_replace = utils.attribute(
+        utils.py_attr('replace'), lambda p: p.output)
+    py_strip = utils.attribute(
+        utils.py_attr('strip'), lambda p: p.expression)
     
 class TALElement(Element):
     tal_define = utils.attribute("define", lambda p: p.definitions)
@@ -360,7 +537,7 @@
     tal_omit = utils.attribute("omit-tag", lambda p: p.expression, u"")
     tal_default_expression = utils.attribute("default-expression", lambda p: p.name)
     
-    def _static_attributes(self):
+    def _get_static_attributes(self):
         attributes = {}
 
         for key in self.keys():
@@ -377,19 +554,42 @@
 
         return attributes
 
+class PyElement(Element):
+    tal_omit = utils.attribute("omit-tag", lambda p: p.expression, u"")
+
+class PyIfElement(PyElement):
+    py_if = utils.attribute("test", lambda p: p.expression)
+
+class PyForElement(PyElement):
+    py_for = utils.attribute("each", lambda p: p.definition)
+
+class PyWithElement(PyElement):
+    py_with = utils.attribute(
+        "vars", lambda p: expressions.PythonTranslation.definitions)
+
+class PyDefElement(PyElement):
+    py_def = utils.attribute("function", lambda p: p.method)
+
+class PyMatchElement(PyElement):
+    py_match = utils.attribute("path")
+
 # set up namespaces for XML parsing
 lookup = lxml.etree.ElementNamespaceClassLookup()
 parser = lxml.etree.XMLParser()
 parser.setElementClassLookup(lookup)
 
 try:
-    lookup.get_namespace('http://www.w3.org/1999/xhtml')[None] = Element
-    lookup.get_namespace('http://xml.zope.org/namespaces/tal'
-                        )[None] = TALElement
+    ns_lookup = lookup.get_namespace
 except AttributeError:
-    lxml.etree.Namespace('http://www.w3.org/1999/xhtml')[None] = Element
-    lxml.etree.Namespace('http://xml.zope.org/namespaces/tal'
-                        )[None] = TALElement
+    ns_lookup = lxml.etree.Namespace
+    
+ns_lookup(config.XML_NS)[None] = Element
+ns_lookup(config.TAL_NS)[None] = TALElement
+ns_lookup(config.PY_NS)["if"] = PyIfElement
+ns_lookup(config.PY_NS)["for"] = PyForElement
+ns_lookup(config.PY_NS)["def"] = PyDefElement
+ns_lookup(config.PY_NS)["with"] = PyWithElement
+ns_lookup(config.PY_NS)["match"] = PyMatchElement
 
 def translate_xml(body, *args, **kwargs):
     tree = lxml.etree.parse(StringIO(body), parser)
@@ -401,7 +601,7 @@
         raise ValueError, "Must set default namespace."
 
     # set default expression name
-    key = '{http://xml.zope.org/namespaces/tal}default-expression'
+    key = utils.tal_attr('default-expression')
     if key not in root.attrib:
         root.attrib[key] = default_expression
 
@@ -409,9 +609,6 @@
     generator = generation.Generator(params)
     stream = generator.stream
 
-    # visit root
-    root.interpolate(stream)
-
     # output doctype if any
     tree = root.getroottree()
     if tree.docinfo.doctype:
@@ -422,16 +619,15 @@
         stream.end([doctype])
         stream.scope.pop()
 
-    root.visit(stream)
+    root.start(stream)
 
     return generator
 
 def translate_text(body, *args, **kwargs):
     xml = parser.makeelement(
-        '{http://www.w3.org/1999/xhtml}text',
-        nsmap={None: 'http://www.w3.org/1999/xhtml'})
+        utils.xml_attr('text'), nsmap={None: config.XML_NS})
     xml.text = body
-    xml.attrib['{http://xml.zope.org/namespaces/tal}omit-tag'] = ''
+    xml.attrib[utils.tal_attr('omit-tag')] = ''
     return translate_etree(xml, *args, **kwargs)
     
 def _translate(value, mapping=None, default=None):

Modified: z3c.pt/trunk/src/z3c/pt/translation.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.txt	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/translation.txt	2008-08-07 01:09:02 UTC (rev 89478)
@@ -11,41 +11,50 @@
   ...    _locals = {}
   ...    _globals.update(kwargs)
   ...    exec source in _globals, _locals
-  ...    return _locals['render']()
+  ...    return _locals['render'](**generator.stream.selectors)
 
-TAL
----
+  >>> from z3c.pt.translation import translate_xml
 
-We use the XML translator.
+XHTML
+-----
 
-  >>> from z3c.pt.translation import translate_xml
+:: Plain HTML document
 
-Basic HTML:
-
   >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <img alt="'Hello World!'" />
+  ... <div xmlns="http://www.w3.org/1999/xhtml">
+  ...   Hello World!
   ... </div>""", translate_xml)
     <div>
-      <img alt="'Hello World!'" />
+      Hello World!
     </div>
 
-With DOCTYPE:
+:: Setting DOCTYPE
 
   >>> print render("""\
   ... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   ...    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-  ... <html xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <img alt="'Hello World!'" />
+  ... <html xmlns="http://www.w3.org/1999/xhtml">
+  ...   Hello World!
   ... </html>""", translate_xml)
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     <html>
-      <img alt="'Hello World!'" />
+      Hello World!
     </html>
-    
-Attributes and contents:
+
+:: Unicode 
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml">
+  ...   <img alt="La Peña" />
+  ... </div>""", translate_xml)
+    <div>
+      <img alt="La Peña" />
+    </div>
+
+Zope TAL
+--------
+
+tal:define, tal:attributes, tal:contents    
   
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
@@ -64,43 +73,43 @@
       <span></span>
     </div>
 
-Repeats:
-
+tal:repeat
+    
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
   ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
   ...   <ul>
-  ...     <li tal:repeat="i range(5)"><span tal:replace="str(i) + ' ' + str(repeat['i'].even())" /></li>
+  ...     <li tal:repeat="i range(5)"><span tal:replace="'Item ' + str(i) + ')'" /></li>
   ...   </ul>
   ... </div>""", translate_xml)
     <div>
       <ul>
-        <li>0 True</li>
-        <li>1 False</li>
-        <li>2 True</li>
-        <li>3 False</li>
-        <li>4 True</li>
+        <li>Item 0)</li>
+        <li>Item 1)</li>
+        <li>Item 2)</li>
+        <li>Item 3)</li>
+        <li>Item 4)</li>
       </ul>
     </div>
 
+tal:repeat (repeat-variable)
+
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
   ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
   ...   <ul>
-  ...     <li tal:repeat="i range(5)"><span tal:replace="'Item ' + str(i) + ')'" /></li>
+  ...     <li tal:repeat="i range(3)"><span tal:replace="str(i) + ' ' + str(repeat['i'].even())" /></li>
   ...   </ul>
   ... </div>""", translate_xml)
     <div>
       <ul>
-        <li>Item 0)</li>
-        <li>Item 1)</li>
-        <li>Item 2)</li>
-        <li>Item 3)</li>
-        <li>Item 4)</li>
+        <li>0 True</li>
+        <li>1 False</li>
+        <li>2 True</li>
       </ul>
     </div>
 
-Conditions:
+tal:condition
 
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
@@ -118,7 +127,7 @@
       </div>
     </div>
 
-Comments:
+:: HTML comments
 
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
@@ -135,7 +144,7 @@
       <!-- a comment with an expression -->
     </div>
 
-Namespacing:
+:: TAL elements with namespace prefix
 
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
@@ -154,7 +163,7 @@
       True
     </div>
 
-Omitting tags:
+tal:omit-tag
 
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
@@ -169,20 +178,11 @@
       <p>A paragraph here.</p>
     </div>
 
-Unicode:
-
+:: Unicode with dynamic attributes and content
+    
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
   ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <img alt="La Peña" />
-  ... </div>""", translate_xml)
-    <div>
-      <img alt="La Peña" />
-    </div>
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
   ...   <img tal:attributes="title '%sHello%s' % (chr(60), chr(62))" />
   ...   <span tal:replace="structure '%sbr /%s' % (chr(60), chr(62))" />
   ...   <span tal:replace="'%sbr /%s' % (chr(60), chr(62))" />
@@ -195,50 +195,11 @@
       <span>La Peña</span>
     </div>
 
-Interpolation:
+:: Setting default expression
 
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
   ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <span>inter${'pol' + 'ati'}on</span>is ${int('test') | 'convenient'}!
-  ...   <span>${'a'}${'b'}${'c'} ${'d'}</span>
-  ...   <span tal:define="hello 'Hello'" class="${hello} World!" />
-  ...   <span class="my-${'class'} item${'Last'}" />
-  ...   <span style="position: ${'abs'}olute"
-  ...         class="my-${int('test') | 'class'} item${'Last'}" />
-  ... </div>""", translate_xml)
-    <div>
-      <span>interpolation</span>is convenient!
-      <span>abc d</span>
-      <span class="Hello World!" />
-      <span class="my-class itemLast" />
-      <span style="position: absolute" class="my-class itemLast" />
-    </div>
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <img alt="${'La Peña'}" />
-  ...   <img alt="Hello ${'La Peña'}" />
-  ...   <img alt="La Peña, oh ${'La Peña'}" />
-  ...   ${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}
-  ...   <img alt="${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}" />
-  ...   <img alt="Hello ${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}!" />
-  ... </div>""", translate_xml)
-    <div>
-      <img alt="La Peña" />
-      <img alt="Hello La Peña" />
-      <img alt="La Peña, oh La Peña" />
-      La Peña
-      <img alt="La Peña" />
-      <img alt="Hello La Peña!" />
-    </div>
-  
-Changing default expression:
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
   ...   <tal:path-expression-testing 
   ...         define="request object();
   ...                 mydict {'a': 1, 'c': {'a': 2}}">
@@ -269,7 +230,7 @@
   <BLANKLINE>
   </div>
 
-Pragmas:
+:: Using TAL pragmas "nocall" and "structure"
 
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
@@ -283,6 +244,248 @@
       <built-in function dir>
     </div>
 
+Genshi
+------
+
+py:if
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <div py:if="False">
+  ...     <p>Bar</p>
+  ...   </div>
+  ...   <div py:if="True">
+  ...     <p>Foo</p>
+  ...   </div>
+  ...   <py:if test="False">
+  ...     <b>Bar</b>
+  ...   </py:if>
+  ...   <py:if test="True">
+  ...     <b>Foo</b>
+  ...   </py:if>
+  ... </div>""", translate_xml)
+  <div>
+    <div>
+      <p>Foo</p>
+    </div>
+  <BLANKLINE>
+      <b>Foo</b>
+  <BLANKLINE>
+  </div>
+
+py:choose, py:when, py:otherwise
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <div py:choose="">
+  ...     <span py:when="0 == 1">0</span>
+  ...     <span py:when="1 == 1">1</span>
+  ...     <div>
+  ...       <span py:when="2 == 2">2</span>
+  ...       <span py:when="2 == 3">3</span>
+  ...       <div py:choose="1">
+  ...          <b py:when="1">3</b>
+  ...          <b py:when="2">4</b>
+  ...       </div>
+  ...     </div>
+  ...     <span py:otherwise="">3</span>
+  ...     <div py:choose="1">
+  ...       <span py:when="0">1</span>
+  ...       <span py:otherwise="">1</span>
+  ...     </div>
+  ...     <div py:choose="">
+  ...       <span py:when="0 == 1">1</span>
+  ...       <span py:otherwise="">2</span>
+  ...     </div>
+  ...   </div>
+  ... </div>""", translate_xml)
+  <div>
+    <div>
+      <span>1</span>
+      <div>
+        <span>2</span>
+        <div>
+           <b>3</b>
+           </div>
+      </div>
+      <div>
+        <span>1</span>
+      </div>
+      <div>
+        <span>2</span>
+      </div>
+    </div>
+  </div>
+
+py:for
+
+  >>> print render("""\
+  ... <ul xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <li py:for="item in range(3)">${item}</li>
+  ...  <py:for each="item in range(3, 5)">
+  ...    <li>${item}</li>
+  ...  </py:for>
+  ... </ul>""", translate_xml)
+  <ul>
+    <li>0</li>
+  <li>1</li>
+  <li>2</li>
+  <li>3</li>
+  <li>4</li>
+  </ul>
+
+py:def
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...  <p py:def="greeting(name)" class="greeting">
+  ...    Hello, ${name}!
+  ...  </p>
+  ...  ${greeting('world')}
+  ...  ${greeting('everyone else')}
+  ...  <py:def function="goodbye(name)">
+  ...    <p class="goodbye">Goodbye, ${name}!</p>
+  ...  </py:def>
+  ...  ${goodbye('world')}
+  ...  ${goodbye('everyone')}  
+  ... </div>""", translate_xml)
+  <div>
+   <p class="greeting">
+     Hello, world!
+   </p>
+  <BLANKLINE>
+   <p class="greeting">
+     Hello, everyone else!
+   </p>
+  <BLANKLINE>
+  <BLANKLINE>
+     <p class="goodbye">Goodbye, world!</p>
+  <BLANKLINE>
+  <BLANKLINE>
+  <BLANKLINE>
+     <p class="goodbye">Goodbye, everyone!</p>
+  <BLANKLINE>
+  <BLANKLINE>
+  </div>
+
+py:with
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <span py:with="x=2; y=7; z=x+10">${x} ${y} ${z}</span>
+  ...   <py:with vars="x=4; y=3; z=x+5">${x} ${y} ${z}</py:with>
+  ... </div>""", translate_xml)
+  <div>
+    <span>2 7 12</span>
+    4 3 9
+  </div>
+
+py:attrs
+
+  >>> print render("""\
+  ... <ul xmlns="http://www.w3.org/1999/xhtml"
+  ...     xmlns:py="http://genshi.edgewall.org">
+  ...   <li py:attrs="{'class': 'collapse'}">Bar</li>
+  ... </ul>""", translate_xml)
+  <ul>
+    <li class="collapse">Bar</li>
+  </ul>
+
+py:content, py:replace
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <span py:content="'Hello, world!'" />
+  ...   <span py:replace="'Goodbye, world!'" />
+  ... </div>""", translate_xml)
+  <div>
+    <span>Hello, world!</span>
+    Goodbye, world!
+  </div>
+
+py:strip
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...    <span py:strip="True"><b>foo</b></span>
+  ... </div>""", translate_xml)
+  <div>
+     <b>foo</b>
+  </div>
+
+py:match
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <span py:match="xmlns:greeting">
+  ...     Hello, ${select('@name')[0]}!
+  ...   </span>
+  ...   <py:match path="xmlns:farewell">
+  ...      <span>Goodbye, ${select('@name')[0]}!</span>
+  ...   </py:match>
+  ...   <greeting name="dude" />
+  ...   <farewell name="dude" />
+  ... </div>""", translate_xml)
+  <div>
+    <span>
+      Hello, dude!
+    </span>
+  <BLANKLINE>
+  <BLANKLINE>
+       <span>Goodbye, dude!</span>
+  <BLANKLINE>
+  <BLANKLINE>
+  </div>
+
+:: Genshi variable interpolation (${<exp>} notation)
+
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span>inter${'pol' + 'ati'}on</span>is ${int('test') | 'convenient'}!
+  ...   <span>${'a'}${'b'}${'c'} ${'d'}</span>
+  ...   <span tal:define="hello 'Hello'" class="${hello} World!" />
+  ...   <span class="my-${'class'} item${'Last'}" />
+  ...   <span style="position: ${'abs'}olute"
+  ...         class="my-${int('test') | 'class'} item${'Last'}" />
+  ... </div>""", translate_xml)
+    <div>
+      <span>interpolation</span>is convenient!
+      <span>abc d</span>
+      <span class="Hello World!" />
+      <span class="my-class itemLast" />
+      <span style="position: absolute" class="my-class itemLast" />
+    </div>
+
+:: Genshi variable interpolation and unicode values
+    
+  >>> print render("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <img alt="${'La Peña'}" />
+  ...   <img alt="Hello ${'La Peña'}" />
+  ...   <img alt="La Peña, oh ${'La Peña'}" />
+  ...   ${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}
+  ...   <img alt="${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}" />
+  ...   <img alt="Hello ${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}!" />
+  ... </div>""", translate_xml)
+    <div>
+      <img alt="La Peña" />
+      <img alt="Hello La Peña" />
+      <img alt="La Peña, oh La Peña" />
+      La Peña
+      <img alt="La Peña" />
+      <img alt="Hello La Peña!" />
+    </div>
+  
 Text templates
 --------------
 

Modified: z3c.pt/trunk/src/z3c/pt/types.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/types.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/types.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -28,3 +28,12 @@
 class escape(parts):
     def __repr__(self):
         return 'escape'+tuple.__repr__(self)
+
+class method(object):
+    def __init__(self, name, args):
+        self.name = name
+        self.args = args
+
+    def __repr__(self):
+        return "%s(%s)" % (self.name, ", ".join(arg for arg in self.args))
+        

Modified: z3c.pt/trunk/src/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/utils.py	2008-08-07 00:12:42 UTC (rev 89477)
+++ z3c.pt/trunk/src/z3c/pt/utils.py	2008-08-07 01:09:02 UTC (rev 89478)
@@ -1,5 +1,6 @@
 import sys
 import logging
+import config
 
 # check if we're able to coerce unicode to str
 try:
@@ -31,7 +32,6 @@
         if value is not None:
             if factory is None:
                 return value
-
             f = factory(self.translator)
             return f(value)
         elif default is not None:
@@ -99,3 +99,15 @@
             self[key] = (value, length)
             
         return value
+
+def xml_attr(name):
+    return "{%s}%s" % (config.XML_NS, name)
+
+def tal_attr(name):
+    return "{%s}%s" % (config.TAL_NS, name)
+
+def i18n_attr(name):
+    return "{%s}%s" % (config.I18N_NS, name)
+
+def py_attr(name):
+    return "{%s}%s" % (config.PY_NS, name)



More information about the Checkins mailing list