[Checkins] SVN: z3c.pt/trunk/ Formalized expression type definitions, adding support for nested expressions (needed to properly support string-expressions). Fixed path- and string-expression support. Many added tests and general refactoring.

Malthe Borch mborch at gmail.com
Tue Mar 18 20:42:06 EDT 2008


Log message for revision 84765:
  Formalized expression type definitions, adding support for nested expressions (needed to properly support string-expressions). Fixed path- and string-expression support. Many added tests and general refactoring.

Changed:
  U   z3c.pt/trunk/README.txt
  U   z3c.pt/trunk/docs/HISTORY.txt
  U   z3c.pt/trunk/z3c/pt/clauses.py
  U   z3c.pt/trunk/z3c/pt/configure.zcml
  U   z3c.pt/trunk/z3c/pt/expressions.py
  U   z3c.pt/trunk/z3c/pt/generation.py
  U   z3c.pt/trunk/z3c/pt/testing.py
  U   z3c.pt/trunk/z3c/pt/translation.py
  U   z3c.pt/trunk/z3c/pt/translation.txt
  A   z3c.pt/trunk/z3c/pt/types.py
  U   z3c.pt/trunk/z3c/pt/utils.py

-=-
Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/README.txt	2008-03-19 00:42:03 UTC (rev 84765)
@@ -5,13 +5,13 @@
 template language including i18n. It also provides a simple text
 template class that allows expression interpolation.
 
-Casual benchmarks pegs it 12x more performant than ``zope.pagetemplate``.
+Casual benchmarks pegs it 11x more performant than ``zope.pagetemplate``.
 
 In a nutshell:
 
 * Templates are bytecode-compiled
-* Supports Python and Zope path traversal expressions
-* Introduces expression interpolation using the ${<expression>}-format
+* Pluggable expression implementation
+* Support for expression interpolation using the ${<expression>}-format
 * Non-XML friendly
 
 See the README.txt inside the package for instructions on usage.
@@ -34,22 +34,18 @@
 
       tal:repeat="i <some generator>"
 
-3. Attribute-access to dictionary entries is allowed, e.g.
+3. Attribute-access to dictionary entries is allowed in
+   Python-expressions, e.g.
 
       dictionary.key
 
    can be used instead of ``dictionary['key']``.
 
-4. Expressions that return a callable are called.
+4. Expression interpolation is allowed in attributes and HTML content.
 
-5. Expression interpolation is allowed:
-
        <a href="mailto:${context.email}">${context.email}</a>
 
-6. Attribute-values are always escaped; document expressions are
-   never.
-
-7. Default expression type can be set using ``tal:default-expression``.
+5. Default expression type can be set using ``tal:default-expression``.
    This is an alternative to providing the expression type before each
    expression.
    
@@ -59,7 +55,8 @@
 Development
 -----------
 
-If you want to use the code directly from trunk, provide
-``z3c.pt==dev`` as your dependency.
+If you want to use the code directly from trunk (recommended only for
+development and testing usage), provide ``z3c.pt==dev`` as your
+dependency.
 
 http://svn.zope.org/z3c.pt/trunk#egg=z3c.pt-dev

Modified: z3c.pt/trunk/docs/HISTORY.txt
===================================================================
--- z3c.pt/trunk/docs/HISTORY.txt	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/docs/HISTORY.txt	2008-03-19 00:42:03 UTC (rev 84765)
@@ -4,14 +4,21 @@
 Version 0.8dev
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-- Result of 'replace' and 'content' is now escaped by default.
+- Added support for 'nocall' and 'not' (for path-expressions).
 
-- Added support for 'nocall', 'not' and 'structure'-pragmas.
+- Added support for path- and string-expressions.
 
-- Added support for path-expressions.
+- Abstracted expression translation engine. Expression implementations
+  are now pluggable. Expression name pragmas are supported throughout.
 
-- Abstracted expression translation engine.
+- Formalized expression types
 
+- Added support for 'structure'-keyword for replace and content.
+
+- Result of 'replace' and 'content' is now escaped by default.
+
+- Benchmark is now built as a custom testrunner
+
 Version 0.7 - March 10, 2008
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

Modified: z3c.pt/trunk/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/z3c/pt/clauses.py	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/clauses.py	2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,60 +1,146 @@
+# -*- coding: utf-8 -*-
+
 from utils import unicode_required_flag
 from cgi import escape
 
+import types
+
 class Assign(object):
     """
-      >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
-      >>> from z3c.pt.testing import value
+    >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
+    >>> from z3c.pt.testing import pyexp
 
-    Simple assignment:
+    We'll define some values for use in the tests.
+    
+    >>> one = types.value("1")
+    >>> bad_float = types.value("float('abc')")
+    >>> abc = types.value("'abc'")
+    >>> ghi = types.value("'ghi'")
+    >>> utf8_encoded = types.value("'La Peña'")
+    >>> exclamation = types.value("'!'")
+        
+    Simple value assignment:
+    
+    >>> assign = Assign(one)
+    >>> assign.begin(stream, 'a')
+    >>> exec stream.getvalue()
+    >>> a == 1
+    True
+    >>> assign.end(stream)
+    
+    Try-except parts (bad, good):
+    
+    >>> assign = Assign(types.parts((bad_float, one)))
+    >>> assign.begin(stream, 'b')
+    >>> exec stream.getvalue()
+    >>> b == 1
+    True
+    >>> assign.end(stream)
+    
+    Try-except parts (good, bad):
+    
+    >>> assign = Assign(types.parts((one, bad_float)))
+    >>> assign.begin(stream, 'b')
+    >>> exec stream.getvalue()
+    >>> b == 1
+    True
+    >>> assign.end(stream)
+    
+    Join:
 
-      >>> assign = Assign(value("1"))
-      >>> assign.begin(stream, 'a')
-      >>> exec stream.getvalue()
-      >>> a == 1
-      True
-      >>> assign.end(stream)
+    >>> assign = Assign(types.join((abc, ghi)))
+    >>> assign.begin(stream, 'b')
+    >>> exec stream.getvalue()
+    >>> b == 'abcghi'
+    True
+    >>> assign.end(stream)
 
-    Try-except chain:
+    Join with try-except parts:
+    
+    >>> assign = Assign(types.join((types.parts((bad_float, abc, ghi)), ghi)))
+    >>> assign.begin(stream, 'b')
+    >>> exec stream.getvalue()
+    >>> b == 'abcghi'
+    True
+    >>> assign.end(stream)
 
-      >>> assign = Assign(value("float('abc') | 1"))
-      >>> assign.begin(stream, 'b')
-      >>> exec stream.getvalue()
-      >>> b == 1
-      True
-      >>> assign.end(stream)
+    UTF-8 coercing:
 
-    Try-except chain part 2: 
+    >>> assign = Assign(types.join((utf8_encoded, exclamation)))
+    >>> assign.begin(stream, 'b')
+    >>> exec stream.getvalue()
+    >>> b == 'La Peña!'
+    True
+    >>> assign.end(stream)
 
-      >>> assign = Assign(value("'abc' | 1"))
-      >>> assign.begin(stream, 'b')
-      >>> exec stream.getvalue()
-      >>> b == 'abc'
-      True
-      >>> assign.end(stream)
-     """
+    UTF-8 coercing with unicode:
     
-    def __init__(self, expressions, variable=None):
-        self.expressions = expressions
+    >>> assign = Assign(types.join((utf8_encoded, u"!")))
+    >>> assign.begin(stream, 'b')
+    >>> exec stream.getvalue()
+    >>> b == 'La Peña!'
+    True
+    >>> assign.end(stream)
+
+    """
+
+    def __init__(self, parts, variable=None):
+        if not isinstance(parts, types.parts):
+            parts = types.parts((parts,))
+        
+        self.parts = parts
         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]:
+
+        for value in self.parts[:-1]:
             stream.write("try:")
             stream.indent()
-            stream.write("%s = %s" % (variable, expression))
+
+            self._assign(variable, value, stream)
+            
             stream.outdent()
             stream.write("except Exception, e:")
             stream.indent()
 
-        expression = self.expressions[-1]
-        stream.write("%s = %s" % (variable, expression))
+        value = self.parts[-1]
+        self._assign(variable, value, stream)
+        
+        stream.outdent(len(self.parts)-1)
 
-        stream.outdent(len(self.expressions)-1)
+    def _assign(self, variable, value, stream):
+        if isinstance(value, types.value):
+            stream.write("%s = %s" % (variable, value))
+        elif isinstance(value, types.join):
+            parts = []
+            _v_count = 0
+            
+            for part in value:
+                if isinstance(part, (types.parts, types.join)):
+                    _v = stream.save()
+                    assign = Assign(part, _v)
+                    assign.begin(stream)
+                    assign.end(stream)
+                    _v_count +=1
+                    parts.append(_v)
+                elif isinstance(part, types.value):
+                    parts.append(part)
+                elif isinstance(part, unicode):
+                    parts.append(repr(part.encode('utf-8')))
+                elif isinstance(part, str):
+                    parts.append(repr(part))
+                else:
+                    raise ValueError("Not able to handle %s" % type(part))
+                    
+            format = "%s"*len(parts)
+
+            stream.write("%s = '%s' %% (%s)" % (variable, format, ",".join(parts)))
+            
+            for i in range(_v_count):
+                stream.restore()
         
     def end(self, stream):
         pass
@@ -62,11 +148,11 @@
 class Define(object):
     """
       >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
-      >>> from z3c.pt.testing import value
+      >>> from z3c.pt.testing import pyexp
       
     Variable scope:
 
-      >>> define = Define("a", value("b"))
+      >>> define = Define("a", pyexp("b"))
       >>> b = object()
       >>> define.begin(stream)
       >>> exec stream.getvalue()
@@ -85,8 +171,8 @@
     Multiple defines:
 
       >>> stream = CodeIO()
-      >>> define1 = Define("a", value("b"))
-      >>> define2 = Define("c", value("d"))
+      >>> define1 = Define("a", pyexp("b"))
+      >>> define2 = Define("c", pyexp("d"))
       >>> d = object()
       >>> define1.begin(stream)
       >>> define2.begin(stream)
@@ -112,7 +198,7 @@
     Tuple assignments:
 
       >>> stream = CodeIO()
-      >>> define = Define(['e', 'f'], value("[1, 2]"))
+      >>> define = Define(['e', 'f'], pyexp("[1, 2]"))
       >>> define.begin(stream)
       >>> exec stream.getvalue()
       >>> e == 1 and f == 2
@@ -134,7 +220,7 @@
     Using semicolons in expressions within a define:
 
       >>> stream = CodeIO()
-      >>> define = Define("a", value("';'"))
+      >>> define = Define("a", pyexp("';'"))
       >>> define.begin(stream)
       >>> exec stream.getvalue()
       >>> a
@@ -147,7 +233,7 @@
       >>> a = 1
       >>> stream.scope[-1].add('a')
       >>> stream.scope.append(set())
-      >>> define = Define("a", value("2"))
+      >>> define = Define("a", pyexp("2"))
       >>> define.begin(stream)
       >>> define.end(stream)
       >>> exec stream.getvalue()
@@ -209,14 +295,14 @@
 class Condition(object):
     """
       >>> from z3c.pt.generation import CodeIO
-      >>> from z3c.pt.testing import value
+      >>> from z3c.pt.testing import pyexp
       >>> from cgi import escape as _escape
       
     Unlimited scope:
     
       >>> stream = CodeIO()
-      >>> true = Condition(value("True"))
-      >>> false = Condition(value("False"))
+      >>> true = Condition(pyexp("True"))
+      >>> false = Condition(pyexp("False"))
       >>> true.begin(stream)
       >>> stream.write("print 'Hello'")
       >>> true.end(stream)
@@ -233,8 +319,8 @@
       >>> stream = CodeIO()
       >>> from StringIO import StringIO
       >>> _out = StringIO()
-      >>> true = Condition(value("True"), [Write(value("'Hello'"))])
-      >>> false = Condition(value("False"), [Write(value("'Hallo'"))])
+      >>> true = Condition(pyexp("True"), [Write(pyexp("'Hello'"))])
+      >>> false = Condition(pyexp("False"), [Write(pyexp("'Hallo'"))])
       >>> true.begin(stream)
       >>> true.end(stream)
       >>> false.begin(stream)
@@ -248,8 +334,8 @@
       >>> stream = CodeIO()
       >>> from StringIO import StringIO
       >>> _out = StringIO()
-      >>> true = Condition(value("True"), [Tag('div')], finalize=False)
-      >>> false = Condition(value("False"), [Tag('span')], finalize=False)
+      >>> true = Condition(pyexp("True"), [Tag('div')], finalize=False)
+      >>> false = Condition(pyexp("False"), [Tag('span')], finalize=False)
       >>> true.begin(stream)
       >>> stream.out("Hello World!")
       >>> true.end(stream)
@@ -263,21 +349,13 @@
       
     def __init__(self, value, clauses=None, finalize=True):
         self.assign = Assign(value)
-        try:
-            self.inverse = value.options.get('not')
-        except:
-            import pdb; pdb.set_trace()
-            
         self.clauses = clauses
         self.finalize = finalize
         
     def begin(self, stream):
         temp = stream.save()
         self.assign.begin(stream, temp)
-        if self.inverse:
-            stream.write("if not(%s):" % temp)
-        else:
-            stream.write("if %s:" % temp)
+        stream.write("if %s:" % temp)
         stream.indent()
         if self.clauses:
             for clause in self.clauses:
@@ -335,12 +413,14 @@
 class Tag(object):
     """
       >>> from z3c.pt.generation import CodeIO
-      >>> from z3c.pt.testing import value
+      >>> from z3c.pt.testing import pyexp
       >>> from StringIO import StringIO
       >>> from cgi import escape as _escape
+
+      Dynamic attribute:
       
       >>> _out = StringIO(); stream = CodeIO()
-      >>> tag = Tag('div', dict(alt=value(repr('Hello World!'))))
+      >>> tag = Tag('div', dict(alt=pyexp(repr('Hello World!'))))
       >>> tag.begin(stream)
       >>> stream.out('Hello Universe!')
       >>> tag.end(stream)
@@ -348,6 +428,8 @@
       >>> _out.getvalue()
       '<div alt="Hello World!">Hello Universe!</div>'
 
+      Self-closing tag:
+      
       >>> _out = StringIO(); stream = CodeIO()
       >>> tag = Tag('br', {}, True)
       >>> tag.begin(stream)
@@ -355,7 +437,18 @@
       >>> exec stream.getvalue()
       >>> _out.getvalue()
       '<br />'
+
+      Unicode:
       
+      >>> _out = StringIO(); stream = CodeIO()
+      >>> tag = Tag('div', dict(alt=pyexp(repr('La Peña'))))
+      >>> tag.begin(stream)
+      >>> stream.out('Hello Universe!')
+      >>> tag.end(stream)
+      >>> exec stream.getvalue()
+      >>> _out.getvalue() == '<div alt="La Peña">Hello Universe!</div>'
+      True
+            
     """
 
     def __init__(self, tag, attributes={}, selfclosing=False):
@@ -374,13 +467,13 @@
 
         # static attributes
         static = filter(
-            lambda (attribute, expression): \
-            not isinstance(expression, (tuple, list)),
+            lambda (attribute, value): \
+            not isinstance(value, types.expression),
             self.attributes.items())
 
         dynamic = filter(
-            lambda (attribute, expression): \
-            isinstance(expression, (tuple, list)),
+            lambda (attribute, value): \
+            isinstance(value, types.expression),
             self.attributes.items())
 
         for attribute, expression in static:
@@ -389,7 +482,7 @@
                 escape(expression, '"')))
 
         temp = stream.save()
-        
+
         for attribute, value in dynamic:
             assign = Assign(value)
             assign.begin(stream, temp)
@@ -398,9 +491,9 @@
             stream.write("if %s is not None:" % temp)
             stream.indent()
 
-            if not value.options.get('nocall'):
-                # if callable, evaluate method
-                stream.write("if callable(%s): %s = %s()" % (temp, temp, temp))
+            #if not value.options.get('nocall'):
+            #    # if callable, evaluate method
+            #    stream.write("if callable(%s): %s = %s()" % (temp, temp, temp))
 
             if unicode_required_flag:
                 stream.write("if isinstance(%s, unicode):" % temp)
@@ -435,7 +528,7 @@
 class Repeat(object):
     """
       >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
-      >>> from z3c.pt.testing import value
+      >>> from z3c.pt.testing import pyexp
 
     We need to set up the repeat object.
 
@@ -444,7 +537,7 @@
 
     Simple repeat loop and repeat data structure:
 
-      >>> _repeat = Repeat("i", value("range(5)"))
+      >>> _repeat = Repeat("i", pyexp("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())")
@@ -492,46 +585,65 @@
 
 class Write(object):
     """
-      >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
-      >>> from z3c.pt.testing import value
-      >>> from StringIO import StringIO
-      >>> from cgi import escape as _escape
-      
-      >>> _out = StringIO()
-      >>> write = Write(value("'New York'"))
-      >>> write.begin(stream)
-      >>> write.end(stream)
-      >>> exec stream.getvalue()
-      >>> _out.getvalue()
-      'New York'
-      >>> _out = StringIO()
-      >>> write = Write(value("undefined | ', New York!'"))
-      >>> write.begin(stream)
-      >>> write.end(stream)
-      >>> exec stream.getvalue()
-      >>> _out.getvalue()
-      'New York, New York!'
+    >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
+    >>> from z3c.pt.testing import pyexp
+    >>> from StringIO import StringIO
+    >>> from cgi import escape as _escape
+
+    Basic write:
+    
+    >>> _out = StringIO()
+    >>> write = Write(pyexp("'New York'"))
+    >>> write.begin(stream)
+    >>> write.end(stream)
+    >>> exec stream.getvalue()
+    >>> _out.getvalue()
+    'New York'
+
+    Try-except parts:
+
+    >>> stream = CodeIO()
+    >>> _out = StringIO()
+    >>> write = Write(pyexp("undefined | 'New Delhi'"))
+    >>> write.begin(stream)
+    >>> write.end(stream)
+    >>> exec stream.getvalue()
+    >>> _out.getvalue()
+    'New Delhi'
+
+    Unicode:
+
+    >>> stream = CodeIO()
+    >>> _out = StringIO()
+    >>> write = Write(types.value("unicode('La Pe\xc3\xb1a', 'utf-8')"))
+    >>> write.begin(stream)
+    >>> write.end(stream)
+    >>> exec stream.getvalue()
+    >>> _out.getvalue() == 'La Pe\xc3\xb1a'
+    True
+    
     """
+
+    value = assign = None
     
     def __init__(self, value):
-        self.assign = Assign(value)
-        self.value = value
-        self.count = len(value)
+        if isinstance(value, types.parts):
+            self.assign = Assign(value)
+        else:
+            self.value = value
+
+        self.structure = not isinstance(value, types.escape)
         
     def begin(self, stream):
         temp = stream.save()
 
-        if self.count == 1:
-            expr = self.value[0]
+        if self.value:
+            expr = self.value
         else:
             self.assign.begin(stream, temp)
             expr = temp
 
         stream.write("_urf = %s" % expr)
-
-        if not self.value.options.get('nocall'):
-            stream.write("if callable(_urf): _urf = _urf()")
-            
         stream.write("if _urf is None: _urf = ''")
 
         if unicode_required_flag:
@@ -541,26 +653,26 @@
             stream.outdent()
             stream.write("else:")
             stream.indent()
-            if self.value.options.get('structure'):
+            if self.structure:
                 stream.write("_out.write(str(_urf))")
             else:
                 stream.write("_out.write(_escape(str(_urf)))")
             stream.outdent()
         else:
-            if self.value.options.get('structure'):
+            if self.structure:
                 stream.write("_out.write(str(_urf))")
             else:
                 stream.write("_out.write(_escape(str(_urf)))")
             
     def end(self, stream):
-        if self.count != 1:
+        if self.assign:
             self.assign.end(stream)
         stream.restore()
 
 class Out(object):
     """
       >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
-      >>> from z3c.pt.testing import value
+      >>> from z3c.pt.testing import pyexp
       >>> from StringIO import StringIO
       >>> _out = StringIO()
       

Modified: z3c.pt/trunk/z3c/pt/configure.zcml
===================================================================
--- z3c.pt/trunk/z3c/pt/configure.zcml	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/configure.zcml	2008-03-19 00:42:03 UTC (rev 84765)
@@ -10,5 +10,9 @@
      name="path"
      factory=".expressions.PathTranslation" />
 
+  <adapter
+     name="string"
+     factory=".expressions.StringTranslation" />
+  
 </configure>
  

Modified: z3c.pt/trunk/z3c/pt/expressions.py
===================================================================
--- z3c.pt/trunk/z3c/pt/expressions.py	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/expressions.py	2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 import zope.interface
 import zope.component
 import zope.traversing.adapters
@@ -7,27 +9,26 @@
 import re
 
 from interfaces import IExpressionTranslation
-from utils import value
 
+import types
+
 class ExpressionTranslation(object):
     zope.interface.implements(IExpressionTranslation)
 
-    pragma = re.compile(r'^(?P<pragma>[a-z]+):\s*')
-    
+    re_pragma = re.compile(r'^\s*(?P<pragma>[a-z]+):\s*')
+    re_interpolation = re.compile(r'(?P<prefix>[^\\]\$|^\$){((?P<expression>.*)})?')
+
     def name(self, string):
         return string
     
-    def value(self, string):
-        return NotImplementedError("Must be implemented by subclass.")
-
     def search(self, string):
         """
-        We need to implement a ``value``-method. Let's define that an
+        We need to implement a ``validate``-method. Let's define that an
         expression is valid if it contains an odd number of
         characters.
         
           >>> class MockExpressionTranslation(ExpressionTranslation):
-          ...     def value(self, string):
+          ...     def validate(self, string):
           ...         if len(string) % 2 == 0: raise SyntaxError()
 
           >>> search = MockExpressionTranslation().search
@@ -49,7 +50,7 @@
             expression = string[left:right]
 
             try:
-                e = self.value(expression)
+                e = self.validate(expression)
                 current = expression
             except SyntaxError, e:
                 if right == len(string):
@@ -62,19 +63,19 @@
 
         return current
 
-    def variable(self, string):
+    def declaration(self, string):
         """
-          >>> variable = ExpressionTranslation().variable
+          >>> declaration = ExpressionTranslation().declaration
 
         Single variable:
 
-          >>> variable("variable")
-          ('variable',)
+          >>> declaration("variable")
+          declaration('variable',)
 
         Multiple variables:
 
-          >>> variable("variable1, variable2")
-          ('variable1', 'variable2')
+          >>> declaration("variable1, variable2")
+          declaration('variable1', 'variable2')
         """
 
         variables = []
@@ -90,20 +91,20 @@
 
             variables.append(var)
 
-        return tuple(variables)
+        return types.declaration(variables)
 
     def mapping(self, string):
         """
           >>> mapping = ExpressionTranslation().mapping
           
           >>> mapping("abc def")
-          [('abc', 'def')]
+          mapping(('abc', 'def'),)
 
           >>> mapping("abc")
-          [('abc', None)]
+          mapping(('abc', None),)
 
           >>> mapping("abc; def ghi")
-          [('abc', None), ('def', 'ghi')]
+          mapping(('abc', None), ('def', 'ghi'))
 
         """
 
@@ -122,49 +123,50 @@
             else:
                 raise ValueError, "Invalid mapping (%s)." % string
 
-        return mappings
+        return types.mapping(mappings)
 
     def definitions(self, string):
         """
-        We need to subclass the parser and implement a simple
-        ``value``-method.
-
-          >>> class MockExpressionTranslation(ExpressionTranslation):
-          ...     def value(self, string):
-          ...         return (string.strip(),)
-
-          >>> definitions = MockExpressionTranslation().definitions
-
+        
+        >>> class MockExpressionTranslation(ExpressionTranslation):
+        ...     def expression(self, string):
+        ...         return types.value(string.strip())
+        
+        >>> definitions = MockExpressionTranslation().definitions
+        
         Single define:
-
-          >>> definitions("variable expression")
-          ((['variable'], ('expression',)),)
-
+        
+        >>> definitions("variable expression")
+        definitions((declaration('variable',), value('expression')),)
+        
         Multiple defines:
-
-          >>> definitions("variable1 expression1; variable2 expression2")
-          ((['variable1'], ('expression1',)), (['variable2'], ('expression2',)))
-          
+        
+        >>> definitions("variable1 expression1; variable2 expression2")
+        definitions((declaration('variable1',), value('expression1')),
+                    (declaration('variable2',), value('expression2')))
+        
         Tuple define:
-
-          >>> definitions("(variable1, variable2) (expression1, expression2)")
-          ((['variable1', 'variable2'], ('(expression1, expression2)',)),)
-
+        
+        >>> definitions("(variable1, variable2) (expression1, expression2)")
+        definitions((declaration('variable1', 'variable2'),
+                    value('(expression1, expression2)')),)
+        
         A define clause that ends in a semicolon:
-
-          >>> definitions("variable expression;")
-          ((['variable'], ('expression',)),)
-
+        
+        >>> definitions("variable expression;")
+        definitions((declaration('variable',), value('expression')),)
+        
         A define clause with a trivial expression (we do allow this):
-
-          >>> definitions("variable")
-          ((['variable'], None),)
-
+        
+        >>> definitions("variable")
+        definitions((declaration('variable',), None),)
+        
         A proper define clause following one with a trivial expression:
+        
+        >>> definitions("variable1 expression; variable2")
+        definitions((declaration('variable1',), value('expression')),
+                    (declaration('variable2',), None))
 
-          >>> definitions("variable1 expression; variable2")
-          ((['variable1'], ('expression',)), (['variable2'], None))
-
         """
 
         string = string.replace('\n', '').strip()
@@ -181,15 +183,15 @@
                 j = string.find(')', i+1)
                 if j == -1:
                     raise ValueError, "Invalid variable tuple definition (%s)." % string
-                var = self.variable(string[i+1:j])
+                var = self.declaration(string[i+1:j])
                 j += 1
             else:
                 j = string.find(' ', i + 1)
                 if j == -1:
-                    var = self.variable(string[i:])
+                    var = self.declaration(string[i:])
                     j = len(string)
                 else:
-                    var = self.variable(string[i:j])
+                    var = self.declaration(string[i:j])
 
             # get expression
             i = j
@@ -199,20 +201,21 @@
                     j = len(string)
 
                 try:
-                    expr = self.value(string[i:j])
+                    expr = self.expression(string[i:j])
                 except SyntaxError, e:
                     if j < len(string):
                         continue
+                        
                     raise e
                 break
             else:
                 expr = None
 
-            defines.append((list(var), expr))
+            defines.append((var, expr))
 
             i = j + 1
 
-        return tuple(defines)
+        return types.definitions(defines)
 
     def definition(self, string):
         defs = self.definitions(string)
@@ -221,63 +224,90 @@
 
         return defs[0]
 
-    def value(self, string):
+    def output(self, string):
         """
-        We need to implement a ``value``-method. Let's define that an
-        expression is valid if it contains an odd number of
-        characters.
-        
         >>> class MockExpressionTranslation(ExpressionTranslation):
         ...     def validate(self, string):
         ...         return True
         ...
         ...     def translate(self, string):
-        ...         return '<translated %s>' % string
+        ...         return types.value(string)
+
+        >>> output = MockExpressionTranslation().output
+
+        >>> output("context/title")
+        escape(value('context/title'),)
+
+        >>> output("context/pretty_title_or_id|context/title")
+        escape(value('context/pretty_title_or_id'), value('context/title'))
+
+        >>> output("structure context/title")
+        value('context/title')
         
-        >>> value = MockExpressionTranslation().value
+        """
+        
+        if string.startswith('structure '):
+            return self.expression(string[len('structure'):])
+        
+        expression = self.expression(string)
 
-        >>> value('a')
-        ('<translated a>',)
+        if isinstance(expression, types.parts):
+            return types.escape(expression)
 
-        >>> value('a|b')
-        ('<translated a>', '<translated b>')
+        return types.escape((expression,))
+            
+    def expression(self, string):
+        """We need to implement the ``validate`` and
+        ``translate``-methods. Let's define that an expression is
+        valid if it contains an odd number of characters.
+        
+        >>> class MockExpressionTranslation(ExpressionTranslation):
+        ...     def validate(self, string):
+        ...         return True
+        ...
+        ...     def translate(self, string):
+        ...         return types.value(string)
+
+        >>> expression = MockExpressionTranslation().expression
+
+        >>> expression('a')
+        value('a')
+
+        >>> expression('a|b')
+        parts(value('a'), value('b'))
     
         """
 
+        if string is None:
+            import pdb; pdb.set_trace()
+            
         string = string.replace('\n', '').strip()
 
         if not string:
-            return []
+            return types.parts()
 
-        expressions = []
+        parts = []
 
-        # reset pragmas
+        # default translator is ``self``
         translator = self
 
-        options = {'nocall': False,
-                   'structure': False,
-                   'not': False}
-
         i = j = 0
         while i < len(string):
-            match = self.pragma.match(string[i:])
-            if match is not None:
-                pragma = match.group('pragma').lower()
+            if translator is self:
+                match = self.re_pragma.match(string[i:])
+                if match is not None:
+                    pragma = match.group('pragma')
 
-                utility = zope.component.queryUtility(
-                    IExpressionTranslation, name=pragma)
-                
-                if utility is not None:
-                    translator = utility
-                    pragma = None
+                    translator = \
+                        zope.component.queryUtility(
+                            IExpressionTranslation, name=pragma) or \
+                        zope.component.queryAdapter(
+                            self, IExpressionTranslation, name=pragma) or \
+                        self
 
-                if pragma in options:
-                    options[pragma] = True
-                    pragma = None
-                    
-                if pragma is None:
-                    i += match.end()
-                    continue
+                    if translator is not self:
+                        i += match.end()
+                        continue
             
             j = string.find('|', j + 1)
             if j == -1:
@@ -293,13 +323,64 @@
 
                 raise e
 
-            expressions.append(translator.translate(expr))
+            value = translator.translate(expr)
+            parts.append(value)
             translator = self
             
             i = j + 1
 
-        return value(options, expressions)
+        if len(parts) == 1:
+            return parts[0]
 
+        return types.parts(parts)
+
+    def interpolate(self, string):
+        """Search for an interpolation and return a match.
+
+        >>> class MockExpressionTranslation(ExpressionTranslation):
+        ...     def validate(self, string):
+        ...         if '}' in string: raise SyntaxError
+        ...
+        ...     def translate(self, string):
+        ...         return types.value(string)
+
+        >>> interpolate = MockExpressionTranslation().interpolate
+        
+        >>> interpolate('${abc}').group('expression')
+        'abc'
+
+        >>> interpolate('abc${def}').group('expression')
+        'def'
+
+        >>> interpolate('abc${def}ghi${jkl}').group('expression')
+        'def'
+
+        >>> interpolate('${abc')
+        Traceback (most recent call last):
+          ...
+        SyntaxError: Interpolation expressions must of the form ${<expression>} (${abc)
+        
+        """
+
+        m = self.re_interpolation.search(string)
+        if m is None:
+            return None
+
+        expression = m.group('expression')
+
+        if expression:
+            left = m.start()+len(m.group('prefix'))
+            exp = self.search(string[left+1:])
+            right = left+2+len(exp)
+            m = self.re_interpolation.search(string[:right])
+            
+        if expression is None or m is None:
+            raise SyntaxError(
+                "Interpolation expressions must of the "
+                "form ${<expression>} (%s)" % string)
+
+        return m
+
 class PythonTranslation(ExpressionTranslation):
     def validate(self, string):
         """We use the ``parser`` module to determine if
@@ -308,13 +389,87 @@
         parser.expr(string.encode('utf-8'))
 
     def translate(self, string):
-        return string
+        if isinstance(string, unicode):
+            string = string.encode('utf-8')
 
+        return types.value(string)
+            
+class StringTranslation(ExpressionTranslation):
+    zope.component.adapts(IExpressionTranslation)
+
+    def __init__(self, translator):
+        self.translator = translator
+
+    def validate(self, string):
+        self.interpolate(string)
+
+    def translate(self, string):
+        return types.join(self.split(string))
+        
+    def split(self, string):
+        """Split up an interpolation string expression into parts that
+        are either unicode strings or ``value``-tuples.
+
+        >>> class MockTranslation(ExpressionTranslation):
+        ...     def validate(self, string):
+        ...         if '}' in string: raise SyntaxError
+        ...
+        ...     def translate(self, string):
+        ...         return types.value(string)
+        
+        >>> class MockStringTranslation(StringTranslation):
+        ...     pass
+        
+        >>> split = MockStringTranslation(MockTranslation()).split
+
+        >>> split("${abc}")
+        (value('abc'),)
+
+        >>> split("abc${def}")
+        ('abc', value('def'))
+
+        >>> split("${def}abc")
+        (value('def'), 'abc')
+
+        >>> split("abc${def}ghi")
+        ('abc', value('def'), 'ghi')
+
+        >>> split("abc${def}ghi${jkl}")
+        ('abc', value('def'), 'ghi', value('jkl'))
+
+        >>> split("abc${def | ghi}")
+        ('abc', parts(value('def '), value('ghi')))
+
+        >>> print split("abc${La Peña}")
+        ('abc', value('La Pe\\xc3\\xb1a'))
+        
+        """
+
+        m = self.translator.interpolate(string)
+        if m is None:
+            return (string,)
+
+        parts = []
+        
+        start = m.start()
+        if start > 0:
+            text = string[:m.start()+1]
+            parts.append(text)
+
+        expression = m.group('expression')
+        parts.append(self.translator.expression(expression))
+
+        rest = string[m.end():]
+        if len(rest):
+            parts.extend(self.split(rest))
+
+        return tuple(parts)
+
 class PathTranslation(ExpressionTranslation):
-    path_regex = re.compile(r'^([A-Za-z_]+)(/[A-Za-z_ at -]+)+$')
+    path_regex = re.compile(r'^((nocall|not):\s*)*([A-Za-z_]+)(/[A-Za-z_ at -]+)*$')
 
     @classmethod
-    def traverse(cls, base, request, *path_items):
+    def traverse(cls, base, request, call, *path_items):
         """See ``zope.app.pagetemplate.engine``."""
 
         for i in range(len(path_items)):
@@ -326,8 +481,11 @@
             else:
                 base = zope.traversing.adapters.traversePathElement(
                     base, name, path_items[i+1:], request=request)
+                    
+            base = zope.security.proxy.ProxyFactory(base)
 
-            base = zope.security.proxy.ProxyFactory(base)
+        if call and callable(base):
+            base = base()
             
         return base
 
@@ -339,15 +497,52 @@
         """
             >>> translate = PathTranslation().translate
             >>> translate("a/b")
-            "_path(a, request, 'b')"
+            value("_path(a, request, True, 'b')")
 
             >>> translate("context/@@view")
-            "_path(context, request, '@@view')"
+            value("_path(context, request, True, '@@view')")
+
+            >>> translate("nocall: context/@@view")
+            value("_path(context, request, False, '@@view')")
+
+            >>> translate("not: context/@@view")
+            value("not(_path(context, request, True, '@@view'))")
+
         """
 
+        nocall = False
+        negate = False
+
+        while string:
+            m = self.re_pragma.match(string)
+            if m is None:
+                break
+
+            string = string[m.end():]
+            pragma = m.group('pragma').lower()
+
+            if pragma == 'nocall':
+                nocall = True
+            elif pragma == 'not':
+                negate = True
+            else:
+                raise ValueError("Invalid pragma: %s" % pragma)
+
         parts = string.split('/')
 
+        # map 'nothing' to 'None'
+        parts = map(lambda part: part == 'nothing' and 'None' or part, parts)
+        
         base = parts[0]
         components = [repr(part) for part in parts[1:]]
-                
-        return '_path(%s, request, %s)' % (base, ', '.join(components))
+
+        if not components:
+            components = ()
+
+        value = types.value(
+            '_path(%s, request, %s, %s)' % (base, not nocall, ', '.join(components)))
+
+        if negate:
+            value = types.value('not(%s)' % value)
+
+        return value

Modified: z3c.pt/trunk/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/generation.py	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/generation.py	2008-03-19 00:42:03 UTC (rev 84765)
@@ -86,7 +86,7 @@
 
     def out(self, string):
         self.queue += string
-
+            
     def cook(self):
         if self.queue:
             queue = self.queue
@@ -95,14 +95,22 @@
                        queue.replace('\n', '\\n').replace("'", "\\'"))
         
     def write(self, string):
+        if isinstance(string, str):
+            string = string.decode('utf-8')
+        
         self.cook()
         StringIO.StringIO.write(
             self, self.indentation_string * self.indentation + string + '\n')
 
+        try:
+            self.getvalue()
+        except UnicodeDecodeError:
+            import pdb; pdb.set_trace()
+
     def getvalue(self):
         self.cook()
         return StringIO.StringIO.getvalue(self)
-
+            
     def begin(self, clauses):
         if isinstance(clauses, (list, tuple)):
             for clause in clauses:

Modified: z3c.pt/trunk/z3c/pt/testing.py
===================================================================
--- z3c.pt/trunk/z3c/pt/testing.py	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/testing.py	2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,5 +1,5 @@
 import expressions
 
-def value(string):
+def pyexp(string):
     translator = expressions.PythonTranslation()
-    return translator.value(string)
+    return translator.expression(string)

Modified: z3c.pt/trunk/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.py	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/translation.py	2008-03-19 00:42:03 UTC (rev 84765)
@@ -2,16 +2,14 @@
 
 from StringIO import StringIO
 import lxml.etree
-import re
 
 import generation
 import expressions
 import clauses
 import interfaces
 import utils
+import types
 
-interpolation_regex = re.compile(r'([^\\]\$|^\$){(?P<expression>.*)}')
-
 def attribute(ns, factory=None, default=None):
     def get(self):
         value = self.attrib.get(ns)
@@ -29,25 +27,6 @@
 
     return property(get, set)
 
-def interpolate(string, translator):
-    m = interpolation_regex.search(string)
-    if m is None:
-        return None
-
-    left = m.start()
-    exp = translator.search(string[left+3:])
-    right = left+4+len(exp)
-
-    m = interpolation_regex.search(string[:right])
-
-    if m is None:
-        interpolation = string[left:right]
-        raise SyntaxError(
-            "Interpolation expressions must of the "
-            "form ${<expression>} (%s)" % interpolation)
-
-    return m
-
 class Element(lxml.etree.ElementBase):
     def begin(self, stream):
         stream.scope.append(set())
@@ -60,23 +39,8 @@
     def body(self, stream):
         skip = self.replace or self.content or self.i18n_translate is not None
         if not skip:
-            for element in list(self):
-                if isinstance(element, lxml.etree._Comment):
-                    index = self.index(element)
-
-                    t = parser.makeelement(
-                        '{http://xml.zope.org/namespaces/tal}comment')
-
-                    t.attrib['omit-tag'] = ''
-                    t.tail = element.tail
-                    t.text = '<!--' + element.text + '-->'
-
-                    for child in element.getchildren():
-                        t.append(child)
-
-                    self.remove(element)
-                    self.insert(index, t)
-                
+            for element in [e for e in self if isinstance(e, lxml.etree._Comment)]:
+                self._wrap_comment(element)
             for element in self:
                 element.interpolate(stream)
             for element in self:
@@ -88,11 +52,13 @@
         self.end(stream)
 
     def interpolate(self, stream):
-        # interpolate text
+        """The current interpolation strategy is to translate the
+        interpolation statements into TAL."""
+        
         translator = self._translator()
         if self.text is not None:
             while self.text:
-                m = interpolate(self.text, translator)
+                m = translator.interpolate(self.text)
                 if m is None:
                     break
 
@@ -106,7 +72,7 @@
         # interpolate tail
         if self.tail is not None:
             while self.tail:
-                m = interpolate(self.tail, translator)
+                m = translator.interpolate(self.tail)
                 if m is None:
                     break
 
@@ -122,58 +88,14 @@
         for name in self._static_attributes():
             value = self.attrib[name]
 
-            i = 0
-            format = ''
-            terms = []
+            if translator.interpolate(value):
+                del self.attrib[name]
 
-            defines = []
-
-            while value:
-                string = value[i:]
-                m = interpolate(string, translator)
-                if m is None:
-                    break
-
-                start = m.start()
-                if start > 0:
-                    text = string[:m.start()+1]
+                attributes = '{http://xml.zope.org/namespaces/tal}attributes'
+                if attributes in self.attrib:
+                    self.attrib[attributes] += '; %s string: %s' % (name, value)
                 else:
-                    text = ''
-                i += m.end()
-
-                format += '%s%s'
-                exp = m.group('expression')
-
-                if len(translator.value(exp)) == 1:
-                    terms.extend(("'%s'" % text.replace("'", "\\'"), exp))
-                else:
-                    var = stream.save()
-                    defines.append((var, m.group('expression')))
-                    terms.extend(("'%s'" % text.replace("'", "\\'"), var))
-
-            if not terms:
-                continue
-
-            if i < len(value):
-                format += '%s'
-                terms.append("'%s'" % value[i:].replace("'", "\\'"))
-
-            value = "'%s'" % format + '%%(%s,)' % ",".join(terms)
-
-            del self.attrib[name]
-
-            attributes = '{http://xml.zope.org/namespaces/tal}attributes'
-            if attributes in self.attrib:
-                self.attrib[attributes] += '; %s %s' % (name, value)
-            else:
-                self.attrib[attributes] = '%s %s' % (name, value)
-
-            define = '{http://xml.zope.org/namespaces/tal}define'
-            for name, expression in defines:
-                if define in self.attrib:
-                    self.attrib[define] += '; %s %s' % (name, expression)
-                else:
-                    self.attrib[define] = '%s %s' % (name, expression)
+                    self.attrib[attributes] = '%s string: %s' % (name, value)
                 
     def _clauses(self):
         _ = []
@@ -181,7 +103,7 @@
         # i18n domain
         if self.i18n_domain is not None:
             _.append(clauses.Define(
-                "_domain", utils.value({}, (repr(self.i18n_domain),))))
+                "_domain", types.value(repr(self.i18n_domain))))
 
         # defines
         if self.define is not None:
@@ -236,7 +158,6 @@
                     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:
@@ -251,7 +172,7 @@
 
                 if elements:
                     mapping = '_mapping'
-                    _.append(clauses.Assign(['{}'], mapping))
+                    _.append(clauses.Assign(types.value('{}'), mapping))
                 else:
                     mapping = 'None'
                     
@@ -260,21 +181,21 @@
                     
                     subclauses = []
                     subclauses.append(clauses.Define(
-                        '_out', utils.value({}, ('generation.initialize_stream()',))))
+                        '_out', types.value('generation.initialize_stream()')))
                     subclauses.append(clauses.Group(element._clauses()))
                     subclauses.append(clauses.Assign(
-                        utils.value({}, ('_out.getvalue()',)), "%s['%s']" % (mapping, name)))
+                        types.value('_out.getvalue()'), "%s['%s']" % (mapping, name)))
 
                     _.append(clauses.Group(subclauses))
 
                 _.append(clauses.Assign(
-                    _translate([repr(msgid)], mapping=mapping, default='_marker'),
+                    _translate(types.value(repr(msgid)), mapping=mapping, default='_marker'),
                     '_result'))
 
                 # write translation to output if successful, otherwise
                 # fallback to default rendition; 
-                result = utils.value({}, ('_result',))
-                condition = utils.value({}, ('_result is not _marker',))
+                result = types.value('_result')
+                condition = types.value('_result is not _marker')
                 _.append(clauses.Condition(condition, [clauses.Write(result)]))
 
                 subclauses = []
@@ -283,8 +204,7 @@
                 for element in self:
                     name = element.i18n_name
                     if name:
-                        value = utils.value(
-                            {'structure': True}, ("%s['%s']" % (mapping, name),))
+                        value = types.value("%s['%s']" % (mapping, name))
                         subclauses.append(clauses.Write(value))
                     else:
                         subclauses.append(clauses.Out(lxml.etree.tostring(element)))
@@ -293,6 +213,22 @@
                     _.append(clauses.Else(subclauses))
 
         return _
+
+    def _wrap_comment(self, element):
+        index = self.index(element)
+
+        t = parser.makeelement(
+            '{http://xml.zope.org/namespaces/tal}comment')
+
+        t.attrib['omit-tag'] = ''
+        t.tail = element.tail
+        t.text = '<!--' + element.text + '-->'
+
+        for child in element.getchildren():
+            t.append(child)
+
+        self.remove(element)
+        self.insert(index, t)
     
     def _msgid(self):
         """Create an i18n msgid from the tag contents."""
@@ -343,25 +279,20 @@
         # 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)]
+                    value = types.value(msgid)
 
                     if variable in static:
-                        expression = _translate(expression, default=static_expression)            
+                        expression = _translate(value, default=attributes[variable])
                     else:
-                        expression = _translate(expression)
+                        expression = _translate(value)
                 else:
-                    if variable in dynamic:
+                    if variable in dynamic or variable in static:
                         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."
@@ -382,17 +313,17 @@
     define = attribute(
         "{http://xml.zope.org/namespaces/tal}define", lambda p: p.definitions)
     condition = attribute(
-        "{http://xml.zope.org/namespaces/tal}condition", lambda p: p.value)
+        "{http://xml.zope.org/namespaces/tal}condition", lambda p: p.expression)
     repeat = attribute(
         "{http://xml.zope.org/namespaces/tal}repeat", lambda p: p.definition)
     attributes = attribute(
         "{http://xml.zope.org/namespaces/tal}attributes", lambda p: p.definitions)
     content = attribute(
-        "{http://xml.zope.org/namespaces/tal}content", lambda p: p.value)
+        "{http://xml.zope.org/namespaces/tal}content", lambda p: p.output)
     replace = attribute(
-        "{http://xml.zope.org/namespaces/tal}replace", lambda p: p.value)
+        "{http://xml.zope.org/namespaces/tal}replace", lambda p: p.output)
     omit = attribute(
-        "{http://xml.zope.org/namespaces/tal}omit-tag", lambda p: p.value)
+        "{http://xml.zope.org/namespaces/tal}omit-tag", lambda p: p.expression)
     i18n_translate = attribute(
         "{http://xml.zope.org/namespaces/i18n}translate")
     i18n_attributes = attribute(
@@ -406,11 +337,11 @@
     
 class TALElement(Element):
     define = attribute("define", lambda p: p.definitions)
-    replace = attribute("replace", lambda p: p.value)
+    replace = attribute("replace", lambda p: p.output)
     repeat = attribute("repeat", lambda p: p.definition)
-    attributes = attribute("attributes", lambda p: p.value)
-    content = attribute("content", lambda p: p.value)
-    omit = attribute("omit-tag", lambda p: p.value, u"")
+    attributes = attribute("attributes", lambda p: p.expression)
+    content = attribute("content", lambda p: p.output)
+    omit = attribute("omit-tag", lambda p: p.expression, u"")
     default_expression = attribute("default-expression", lambda p: p.name)
     
     def _static_attributes(self):
@@ -475,12 +406,11 @@
     xml.attrib['{http://xml.zope.org/namespaces/tal}omit-tag'] = ''
     return translate_etree(xml, *args, **kwargs)
     
-def _translate(expressions, mapping=None, default=None):
+def _translate(value, mapping=None, default=None):
     format = "_translate(%s, domain=_domain, mapping=%s, "\
              "target_language=_target_language, default=%s)"
     
-    return utils.value(
-        {}, tuple(format % (exp, mapping, default) for exp in expressions))
+    return types.value(format % (value, mapping, default))
 
-def _not(expressions):
-    return utils.value({}, tuple("not (%s)" % exp for exp in expressions))
+def _not(value):
+    return types.value("not (%s)" % value)

Modified: z3c.pt/trunk/z3c/pt/translation.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.txt	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/translation.txt	2008-03-19 00:42:03 UTC (rev 84765)
@@ -8,8 +8,9 @@
   >>> def render(body, translator, **kwargs):
   ...    source, _globals = translator(body)
   ...    _locals = {}
+  ...    _globals.update(kwargs)
   ...    exec source in _globals, _locals
-  ...    return _locals['render'](**kwargs)
+  ...    return _locals['render']()
 
 TAL
 ---
@@ -41,14 +42,11 @@
   ...         tal:content="a + 'ghi'" />
   ...   <span tal:replace="'Hello World!'">Hello Universe!</span>
   ...   <span tal:content="None" />
-  ...   <span tal:attributes="class lambda: 'Hello'"
-  ...         tal:content="lambda: 'World'" />
   ... </div>""", translate_xml)
     <div>
       <span id="test" style="hij" class="defabc">abcghi</span>
       Hello World!
       <span></span>
-      <span class="Hello">World</span>
     </div>
 
 Repeats:
@@ -124,8 +122,17 @@
   >>> 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="structure '%sbr /%s' % (chr(60), chr(62))" />
   ...   <span tal:replace="'%sbr /%s' % (chr(60), chr(62))" />
   ...   <span tal:content="unicode('La Pe\xc3\xb1a', 'utf-8')" />
   ... </div>""", translate_xml)
@@ -146,22 +153,33 @@
   ...   <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 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')}
   ...   <img alt="${unicode('La Pe\xc3\xb1a', 'utf-8')}" />
   ...   <img alt="Hello ${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}!" />
   ... </div>""", translate_xml)
     <div>
-      <span>interpolation</span>is convenient!
-      <span class="Hello World!" />
-      <span class="my-class itemLast" />
-      <span style="position: absolute" class="my-class itemLast" />
+      <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("""\
@@ -200,14 +218,13 @@
   >>> print render("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
   ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <span tal:replace="nocall: dir" />
-  ...   <span tal:replace="nocall: structure: dir" />
-  ...   <span tal:condition="not: False">Hello World</span>
-  ... </div>""", translate_xml)
+  ...   <span tal:default-expression="path"
+  ...         tal:replace="structure nocall: dir" />
+  ...   <span tal:replace="structure dir" />
+  ... </div>""", translate_xml, request=object())
     <div>
-      &lt;built-in function dir&gt;
       <built-in function dir>
-      <span>Hello World</span>
+      <built-in function dir>
     </div>
 
 Text templates

Added: z3c.pt/trunk/z3c/pt/types.py
===================================================================
--- z3c.pt/trunk/z3c/pt/types.py	                        (rev 0)
+++ z3c.pt/trunk/z3c/pt/types.py	2008-03-19 00:42:03 UTC (rev 84765)
@@ -0,0 +1,30 @@
+class expression:
+    pass
+
+class parts(tuple, expression):
+    def __repr__(self):
+        return 'parts'+tuple.__repr__(self)
+
+class value(str, expression):            
+    def __repr__(self):
+        return 'value(%s)' % str.__repr__(self)
+
+class join(tuple, expression):
+    def __repr__(self):
+        return 'join'+tuple.__repr__(self)
+
+class declaration(tuple):
+    def __repr__(self):
+        return 'declaration'+tuple.__repr__(self)
+
+class mapping(tuple):
+    def __repr__(self):
+        return 'mapping'+tuple.__repr__(self)
+
+class definitions(tuple):
+    def __repr__(self):
+        return 'definitions'+tuple.__repr__(self)
+
+class escape(parts):
+    def __repr__(self):
+        return 'escape'+tuple.__repr__(self)

Modified: z3c.pt/trunk/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/z3c/pt/utils.py	2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/utils.py	2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,4 +1,5 @@
 import sys
+import re
 import logging
 
 # check if we're able to coerce unicode to str
@@ -34,12 +35,6 @@
     def __hash__(self):
         return self.hash
 
-class value(tuple):
-    def __new__(cls, options, *args):
-        inst = tuple.__new__(cls, *args)
-        inst.options = options
-        return inst
-    
 class repeatitem(object):
     def __init__(self, iterator, length):
         self.length = length



More information about the Checkins mailing list