[Checkins] SVN: z3c.pt/trunk/ Split out element compiler classes; refactored large parts of the codebase.

Malthe Borch mborch at gmail.com
Fri Aug 15 19:50:06 EDT 2008


Log message for revision 89902:
  Split out element compiler classes; refactored large parts of the codebase.

Changed:
  U   z3c.pt/trunk/CHANGES.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/etree.py
  U   z3c.pt/trunk/src/z3c/pt/generation.py
  A   z3c.pt/trunk/src/z3c/pt/genshi.py
  A   z3c.pt/trunk/src/z3c/pt/genshi.txt
  U   z3c.pt/trunk/src/z3c/pt/template.py
  U   z3c.pt/trunk/src/z3c/pt/template.txt
  U   z3c.pt/trunk/src/z3c/pt/testing.py
  U   z3c.pt/trunk/src/z3c/pt/tests/test_doctests.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/utils.py
  A   z3c.pt/trunk/src/z3c/pt/zpt.py
  A   z3c.pt/trunk/src/z3c/pt/zpt.txt

-=-
Modified: z3c.pt/trunk/CHANGES.txt
===================================================================
--- z3c.pt/trunk/CHANGES.txt	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/CHANGES.txt	2008-08-15 23:50:05 UTC (rev 89902)
@@ -4,6 +4,12 @@
 Version 1.0dev
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+- Substantially reduced compiler overhead for lxml CDATA
+  workaround. [malthe]
+
+- Split out element compiler classes for Genshi and Zope language
+  dialects. [malthe]
+
 - Make lxml a setuptools "extra".  To install with lxml support
   (currently required by Genshi), specify "z3c.pt [lxml]" in
   any references you need to make to the package in buildout or

Modified: z3c.pt/trunk/src/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -458,7 +458,7 @@
             
     """
 
-    def __init__(self, tag, attributes={},
+    def __init__(self, tag, attributes=None,
                  selfclosing=False, expression=None, cdata=False):
         i = tag.find('}')
 
@@ -468,7 +468,7 @@
             self.tag = tag
 
         self.selfclosing = selfclosing
-        self.attributes = attributes
+        self.attributes = attributes or {}
         self.expression = expression and Assign(expression)
         self.cdata = cdata
         

Modified: z3c.pt/trunk/src/z3c/pt/config.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/config.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/config.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -14,9 +14,10 @@
 DISABLE_I18N = os.environ.get(DISABLE_I18N_KEY, 'false')
 DISABLE_I18N = DISABLE_I18N.lower() in truevals
 
-XML_NS = "http://www.w3.org/1999/xhtml"
+XHTML_NS = "http://www.w3.org/1999/xhtml"
 TAL_NS = "http://xml.zope.org/namespaces/tal"
-METAL_NS = "http://xml.zope.org/namespaces/metal" 
+META_NS = "http://xml.zope.org/namespaces/meta"
+METAL_NS = "http://xml.zope.org/namespaces/metal"
 I18N_NS = "http://xml.zope.org/namespaces/i18n"
 PY_NS = "http://genshi.edgewall.org"
 NS_MAP = dict(py=PY_NS, tal=TAL_NS, metal=METAL_NS)

Modified: z3c.pt/trunk/src/z3c/pt/etree.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/etree.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/etree.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -2,6 +2,7 @@
 import config
 import utils
 import cgi
+import copy
 from StringIO import StringIO
 
 def import_elementtree():
@@ -15,23 +16,18 @@
 
     return ET
 
+class Parser(object):
+    element_mapping = {}
+
+    @classmethod
+    def parse(self, body):
+        global parse
+        return parse(body, self.element_mapping)
+        
 try:
     import lxml.etree
 
-    lookup = lxml.etree.ElementNamespaceClassLookup()
-    parser = lxml.etree.XMLParser(resolve_entities=False, strip_cdata=False)
-    parser.setElementClassLookup(lookup)
-
-    # lxml 1.3-compatibility
-    try:
-        ns_lookup = lookup.get_namespace
-    except AttributeError:
-        ns_lookup = lxml.etree.Namespace
-
     class ElementBase(lxml.etree.ElementBase):
-        def _init(self):
-            self._convert_cdata_sections()
-            
         def tostring(self):
             return lxml.etree.tostring(self)
 
@@ -46,27 +42,29 @@
                 before, rest = text.split(start, 1)
                 cdata, after = rest.split(end, 1)
 
-                element = parser.makeelement(
-                    utils.xml_attr('cdata'))
-                element.attrib[utils.tal_attr('cdata')] = ""
+                element = self.makeelement(
+                    utils.xhtml_attr('cdata'))
+                element.attrib[utils.meta_attr('cdata')] = ""
                 element.text = cdata
                 element.tail = after
                 
                 self.text = before
                 self.insert(0, element)
+                element._convert_cdata_sections()
                 
             if start in tail:
                 before, rest = tail.split(start, 1)
                 cdata, after = rest.split(end, 1)
 
-                element = parser.makeelement(
-                    utils.xml_attr('cdata'))
-                element.attrib[utils.tal_attr('cdata')] = ""
+                element = self.makeelement(
+                    utils.xhtml_attr('cdata'))
+                element.attrib[utils.meta_attr('cdata')] = ""
                 self.addnext(element)
 
                 element.text = cdata
                 element.tail = after
                 self.tail = before
+                element._convert_cdata_sections()
                 
         @property
         def _raw_text(self):
@@ -81,12 +79,13 @@
             if self.text in ("", None):
                 return self.text
 
+            
             elements = tuple(self)
             del self[:]
             xml = lxml.etree.tostring(self, encoding='utf-8', with_tail=False)
             self.extend(elements)
-
-            element = parser.makeelement(self.tag, nsmap=self.nsmap)
+                
+            element = self.makeelement(self.tag, nsmap=self.nsmap)
             for attr, value in self.items():
                 element.attrib[attr] = value
 
@@ -119,7 +118,7 @@
             
             # wrap element
             index = parent.index(self)
-            element = parser.makeelement(self.tag, nsmap=self.nsmap)
+            element = self.makeelement(self.tag, nsmap=self.nsmap)
             element.append(self)
             xml = lxml.etree.tostring(element, encoding='utf-8', with_tail=False)
             self.extend(elements)
@@ -140,12 +139,42 @@
             tail = xml[length+tag:-tag-1]
             
             return tail
+
+    def convert_cdata_section(node):
+        parent = node.getparent()
+        if parent is not None:
+            index = parent.index(node)
+            element = node.makeelement(node.tag, nsmap=node.nsmap)
+            element.append(node)
+            xml = lxml.etree.tostring(element, encoding='utf-8', with_tail=False)
+            parent.insert(index, node)
+        else:
+            xml = lxml.etree.tostring(node, encoding='utf-8', with_tail=False)
             
-    element_factory = parser.makeelement
+        if 'CDATA' in xml:
+            node._convert_cdata_sections()
+            for child in tuple(node):
+                convert_cdata_section(child)
+        
+    def parse(body, element_mapping):
+        lookup = lxml.etree.ElementNamespaceClassLookup()
+        parser = lxml.etree.XMLParser(resolve_entities=False, strip_cdata=False)
+        parser.setElementClassLookup(lookup)
 
-    def parse(body):
+        # lxml 1.3-compatibility
+        try:
+            ns_lookup = lookup.get_namespace
+        except AttributeError:
+            ns_lookup = lxml.etree.Namespace
+
+        for key, mapping in element_mapping.items():
+            ns_lookup(key).update(mapping)
+        
         tree = lxml.etree.parse(StringIO(body), parser)
         root = tree.getroot()
+
+        convert_cdata_section(root)
+        
         return root, tree.docinfo.doctype
 
 except ImportError:
@@ -184,7 +213,7 @@
         @property
         def nsmap(self):
             # TODO: Return correct namespace map
-            return {None: config.XML_NS}
+            return {None: config.XHTML_NS}
 
         @property
         def prefix(self):
@@ -209,7 +238,8 @@
                 parent = None
             elem = ET.TreeBuilder.start(self, tag, attrs)
             elem._parent = parent
-
+            elem.makeelement = self._factory
+            
     class XMLParser(ET.XMLParser):
         def __init__(self, **kwargs):
             ET.XMLParser.__init__(self, **kwargs)
@@ -238,33 +268,33 @@
             self._target.end(name)
 
         def handle_cdata_start(self):
-            self._target.start(utils.xml_attr('cdata'), {
+            self._target.start(utils.xhtml_attr('cdata'), {
                 utils.tal_attr('cdata'): ''})
 
         def handle_cdata_end(self):
-            self._target.end(utils.xml_attr('cdata'))
+            self._target.end(utils.xhtml_attr('cdata'))
             
-    def element_factory(tag, attrs=None, nsmap=None):
-        if attrs is None:
-            attrs = {}
+    def parse(body, element_mapping):
+        def element_factory(tag, attrs=None, nsmap=None):
+            if attrs is None:
+                attrs = {}
 
-        if '{' in tag:
-            ns = tag[tag.find('{')+1:tag.find('}')]
-            ns_tag = tag[tag.find('}')+1:]
-        else:
-            ns = None
-            ns_tag = None
+            if '{' in tag:
+                ns = tag[tag.find('{')+1:tag.find('}')]
+                ns_tag = tag[tag.find('}')+1:]
+            else:
+                ns = None
+                ns_tag = None
 
-        namespace = ns_lookup(ns)
-        factory = namespace.get(ns_tag) or namespace.get(None) or ElementBase
-            
-        element = object.__new__(factory)
-        element.__init__(tag, attrs)
-        return element
+            namespace = element_mapping[ns]
+            factory = namespace.get(ns_tag) or namespace.get(None) or ElementBase
 
-    def parse(body):
+            element = object.__new__(factory)
+            element.__init__(tag, attrs)
+
+            return element
+        
         target = TreeBuilder(element_factory=element_factory)
-
         parser = XMLParser(target=target)
         parser.entity = dict([(name, "&%s;" % name) for name in htmlentitydefs.entitydefs])
         parser.feed(body)

Modified: z3c.pt/trunk/src/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -210,7 +210,7 @@
                 self.begin(clause)
         else:
             clauses.begin(self)
-
+                
     def end(self, clauses):
         if isinstance(clauses, (list, tuple)):
             for clause in reversed(clauses):

Added: z3c.pt/trunk/src/z3c/pt/genshi.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/genshi.py	                        (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/genshi.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -0,0 +1,218 @@
+import translation
+import expressions
+import utils
+import config
+import etree
+
+class GenshiElement(translation.Element, translation.VariableInterpolation):
+    """Genshi element.
+
+    Implements the Genshi subset of the attribute template language.
+    """
+
+    translator = expressions.PythonTranslation
+    
+    class node(object):
+        def __init__(self, element):
+            self.element = element
+            
+        @property
+        def omit(self):
+            if self.element.py_strip is not None:
+                return self.element.py_strip or True
+            if self.element.meta_omit is not None:
+                return self.element.meta_omit or True
+            if self.element.py_replace or self.element.meta_replace:
+                return True
+        
+        @property
+        def define(self):
+            return self.element.py_with
+
+        @property
+        def condition(self):
+            return self.element.py_if
+
+        @property
+        def repeat(self):
+            return self.element.py_for
+
+        @property
+        def content(self):
+            return self.element.py_content or self.element.py_replace or \
+                   self.element.meta_replace
+
+        @property
+        def skip(self):
+            return bool(self.content)
+        
+        @property
+        def dict_attributes(self):
+            return self.element.py_attrs
+
+        @property
+        def dynamic_attributes(self):
+            return self.element.meta_attributes
+        
+        translated_attributes = None
+        
+        @property
+        def static_attributes(self):
+            return utils.get_attributes_from_namespace(
+                self.element, config.XHTML_NS)
+
+        translate = None
+        translation_domain = None
+
+        @property
+        def method(self):
+            return self.element.py_def
+
+        use_macro = None
+        define_macro = None
+        define_slot = None
+        fill_slot = None
+
+        @property
+        def cdata(self):
+            return self.element.meta_cdata
+
+    node = property(node)
+
+    def update(self):
+        # Step 1: Convert py:choose, py:when, py:otherwise into
+        # tal:define, tal:condition
+        stream = self.stream
+        choose_expression = self._pull_attribute(utils.py_attr('choose'))
+        if choose_expression is not None:
+            choose_variable = stream.save()
+            
+            if choose_expression:
+                self._add_define(choose_variable, choose_expression)
+                
+            # 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_define(variable, expression)
+                
+                # add condition to element
+                if choose_expression:
+                    expression = "python: %s == %s" % (
+                        choose_variable, variable)
+                else:
+                    expression = "python: %s" % variable
+                    
+                element.attrib[utils.py_attr('if')] = expression
+
+            # 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.py_attr('if')] = 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.py_attr('replace')] = expression
+
+        # Step 3: Variable interpolation
+        translation.VariableInterpolation.update(self)        
+
+    def _add_define(self, variable, expression):
+        name = utils.py_attr('with')
+        define = "%s=%s; " % (variable, expression)
+
+        if name in self.attrib:
+            self.attrib[name] += define
+        else:
+            self.attrib[name] = define
+
+class XHTMLElement(GenshiElement):
+    """XHTML namespace element."""
+
+    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 PyElement(XHTMLElement):
+    py_strip = utils.attribute("strip", 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")
+    
+class GenshiParser(etree.Parser):
+    element_mapping = {
+        config.XHTML_NS: {None: XHTMLElement},
+        config.META_NS: {None: XHTMLElement},
+        config.PY_NS: {'if': PyIfElement,
+                       'for': PyForElement,
+                       'def': PyDefElement,
+                       'with': PyWithElement,
+                       'match': PyMatchElement}}

Added: z3c.pt/trunk/src/z3c/pt/genshi.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/genshi.txt	                        (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/genshi.txt	2008-08-15 23:50:05 UTC (rev 89902)
@@ -0,0 +1,299 @@
+Genshi
+======
+
+  >>> from z3c.pt.testing import render_genshi
+  
+py:if
+
+  >>> print render_genshi("""\
+  ... <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>""")
+  <div>
+    <div>
+      <p>Foo</p>
+    </div>
+  <BLANKLINE>
+      <b>Foo</b>
+  <BLANKLINE>
+  </div>
+
+py:choose, py:when, py:otherwise
+
+  >>> print render_genshi("""\
+  ... <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>""")
+  <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_genshi("""\
+  ... <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>""")
+  <ul>
+    <li>0</li>
+  <li>1</li>
+  <li>2</li>
+  <li>3</li>
+  <li>4</li>
+  </ul>
+
+py:def
+
+  >>> print render_genshi("""\
+  ... <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>""")
+  <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
+
+  >>> def quote():
+  ...     return dict(author=u"Some name", quote=u"Some random quote")
+     
+  >>> print render_genshi("""\
+  ... <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>
+  ...   <blockquote py:with="q=quote()">
+  ...       "${q["quote"]} <em>${q["author"]}</em>
+  ...   </blockquote>
+  ... </div>""", quote=quote)
+  <div>
+    <span>2 7 12</span>
+    4 3 9
+    <blockquote>
+        "Some random quote <em>Some name</em>
+    </blockquote>
+  </div>
+    
+py:attrs
+
+  >>> print render_genshi("""\
+  ... <ul xmlns="http://www.w3.org/1999/xhtml"
+  ...     xmlns:py="http://genshi.edgewall.org">
+  ...   <li class="expand" py:attrs="{'class': 'collapse'}">Bar</li>
+  ... </ul>""")
+  <ul>
+    <li class="collapse">Bar</li>
+  </ul>
+
+py:content, py:replace
+
+  >>> print render_genshi("""\
+  ... <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>""")
+  <div>
+    <span>Hello, world!</span>
+    Goodbye, world!
+  </div>
+
+py:strip
+
+  >>> print render_genshi("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...    <span py:strip="True"><b>foo</b></span>
+  ... </div>""")
+  <div>
+     <b>foo</b>
+  </div>
+
+py:match
+
+  >>> print render_genshi("""\
+  ... <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>""")
+  <div>
+    <span>
+      Hello, dude!
+    </span>
+  <BLANKLINE>
+  <BLANKLINE>
+       <span>Goodbye, dude!</span>
+  <BLANKLINE>
+  <BLANKLINE>
+  </div>
+
+:: Genshi variable interpolation (${<exp>} notation)
+
+  >>> print render_genshi("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:py="http://genshi.edgewall.org">
+  ...   <span>inter${'pol' + 'ati'}on</span>is ${int('test') | 'convenient'}!
+  ...   <span>${'a'}${'b'}${'c'} ${'d'}</span>
+  ...   <span py:with="hello='Hello'" class="${hello} World!" />
+  ...   <span class="my-${'class'} item${'Last'}" />
+  ...   <span style="position: ${'abs'}olute"
+  ...         class="my-${int('test') | 'class'} item${'Last'}" />
+  ... </div>""")
+    <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_genshi("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml">
+  ...   <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>""")
+    <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>
+
+:: Variables containing quotes
+  >> print render_xhtml("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml">
+  ...   <strong>"${quote}"</strong>
+  ... </div>""", quote="Hello, World!")
+    <div>
+      <strong>"Hello, World!"</strong>
+    </div>
+  
+:: Variables containing markup
+
+  >>> print render_genshi("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml">
+  ...   ${message}
+  ... </div>""", message="Hello, <em>World</em>!")
+    <div>
+      Hello, <em>World</em>!
+    </div>
+
+:: Variable expansion must preserve XML validity
+
+  >>> from z3c.pt import config
+  >>> config.VALIDATION = True
+  
+  >>> print render_genshi("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml">
+  ...   ${message}
+  ... </div>""", message="Hello, <em>World!")
+  Traceback (most recent call last):
+   ...
+  ExpatError: ...
+  
+  >>> config.VALIDATION = False
+  
+:: Unless we are in a CDATA block
+
+  >>> print render_genshi("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml">
+  ... /* <![CDATA[ */
+  ...   ${message}
+  ... /* ]]> */
+  ... </div>""", message="Hello, <em>World!")
+    <div>
+      /* <![CDATA[ */
+      Hello, <em>World!
+      /* ]]> */
+    </div>

Modified: z3c.pt/trunk/src/z3c/pt/template.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/template.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -3,6 +3,7 @@
 import macro
 import codegen
 import traceback
+import zpt
 
 from z3c.pt.config import DEBUG_MODE, PROD_MODE
 from z3c.pt import filecache
@@ -17,9 +18,10 @@
 
     registry = {}
     cachedir = None
+    parser = zpt.ZopePageTemplateParser
     default_expression = 'python'
 
-    def __init__(self, body, default_expression=None):
+    def __init__(self, body, parser=None, default_expression=None):
         self.body = body
         self.signature = hash(body)
         self.source = ''
@@ -27,6 +29,9 @@
         if default_expression:
             self.default_expression = default_expression
 
+        if parser:
+            self.parser = parser
+            
     @property
     def translate(self):
         return NotImplementedError("Must be implemented by subclass.")
@@ -37,7 +42,7 @@
 
     def cook(self, params, macro=None):
         generator = self.translate(
-            self.body, macro=macro, params=params,
+            self.body, self.parser, macro=macro, params=params,
             default_expression=self.default_expression)
         
         source, _globals = generator()
@@ -127,8 +132,9 @@
     default if nothing is passed."""
 
     def __init__(self, filename, auto_reload=False, cachedir=None,
-                 default_expression=None):
-        BaseTemplate.__init__(self, None, default_expression=default_expression)
+                 parser=None, default_expression=None):
+        BaseTemplate.__init__(
+            self, None, parser=parser, default_expression=default_expression)
         self.auto_reload = auto_reload
         self.cachedir = cachedir
 

Modified: z3c.pt/trunk/src/z3c/pt/template.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.txt	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/template.txt	2008-08-15 23:50:05 UTC (rev 89902)
@@ -7,7 +7,8 @@
 --------------------------------
 
   >>> from z3c.pt import PageTemplate
-
+  >>> from z3c.pt.testing import MockParser
+  
   >>> print PageTemplate("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml">
   ...   Hello World!
@@ -38,7 +39,8 @@
   >>> from z3c.pt import ViewPageTemplate
   
   >>> class ViewPageTemplateView(object):
-  ...     __call__ = ViewPageTemplate(open(path+'/view.pt').read())
+  ...     __call__ = ViewPageTemplate(
+  ...          open(path+'/view.pt').read())
   ...     request = u'request'
   ...     context = u'context'
 
@@ -114,13 +116,15 @@
 
 py:match integration
 
+  >>> from z3c.pt.genshi import GenshiParser
+  
   >>> print PageTemplate("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
   ...      xmlns:py="http://genshi.edgewall.org">
   ...   <py:match path="xmlns:greeting">Hello ${select('@name')[0]}!</py:match>
   ...   <greeting name="World" />
   ... </div>
-  ... """)()
+  ... """, parser=GenshiParser)()
   <div>
     Hello World!
   <BLANKLINE>

Modified: z3c.pt/trunk/src/z3c/pt/testing.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -1,6 +1,13 @@
+import translation
 import expressions
 import macro
+import etree
+import config
+import utils
 
+import zpt
+import genshi
+
 def pyexp(string):
     return expressions.PythonTranslation.expression(string)
 
@@ -10,24 +17,80 @@
     exec source in _globals, _locals
     return _locals['render']
 
-def render(body, translator, **kwargs):
-    generator = translator(body, params=sorted(kwargs.keys()))
-    return _render(generator, **kwargs)
-
 def _render(generator, **kwargs):
     cooked = cook(generator, **kwargs)
     kwargs.update(generator.stream.selectors)
     return cooked(**kwargs)
 
+def render_xhtml(body, **kwargs):
+    generator = translation.translate_xml(
+        body, MockParser, params=sorted(kwargs.keys()))
+    return _render(generator, **kwargs)
+
+def render_text(body, **kwargs):
+    generator = translation.translate_text(
+        body, MockParser, params=sorted(kwargs.keys()))
+    return _render(generator, **kwargs)
+
+def render_zpt(body, **kwargs):
+    generator = translation.translate_xml(
+        body, zpt.ZopePageTemplateParser, params=sorted(kwargs.keys()))
+    return _render(generator, **kwargs)
+
+def render_genshi(body, **kwargs):
+    generator = translation.translate_xml(
+        body, genshi.GenshiParser, params=sorted(kwargs.keys()))
+    return _render(generator, **kwargs)
+
 class MockTemplate(object):
-    def __init__(self, body, translator):
+    def __init__(self, body, parser):
         self.body = body
-        self.translator = translator
+        self.parser = parser
 
     @property
     def macros(self):
         def render(macro=None, **kwargs):
-            generator = self.translator(
-                self.body, macro=macro, params=kwargs.keys())
+            generator = translation.translate_xml(
+                self.body, self.parser, macro=macro, params=kwargs.keys())
             return _render(generator, **kwargs)
         return macro.Macros(render)
+
+class MockElement(translation.Element, translation.VariableInterpolation):
+    translator = expressions.PythonTranslation
+    
+    def update(self):
+        translation.VariableInterpolation.update(self)
+        
+    class node(object):
+        def __init__(self, element):
+            self.element = element
+
+        def __getattr__(self, name):
+            return None
+
+        @property
+        def static_attributes(self):
+            return utils.get_attributes_from_namespace(
+                self.element, config.XHTML_NS)
+
+        @property
+        def omit(self):
+            if self.element.meta_omit is not None:
+                return self.element.meta_omit or True
+            if self.content:
+                return True
+            
+        @property
+        def content(self):
+            return self.element.meta_replace
+        
+        @property
+        def cdata(self):
+            return self.element.meta_cdata
+
+    node = property(node)
+
+class MockParser(etree.Parser):
+    element_mapping = {
+        config.XHTML_NS: {None: MockElement},
+        config.META_NS: {None: MockElement}}

Modified: z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -14,7 +14,8 @@
     zope.configuration.xmlconfig.XMLConfig('configure.zcml', z3c.pt)()
 
 def test_suite():
-    filesuites = ('template.txt', 'translation.txt', 'i18n.txt', 'codegen.txt')
+    filesuites = ('zpt.txt', 'genshi.txt', 'template.txt',
+                  'i18n.txt', 'codegen.txt', 'translation.txt')
     testsuites = ('z3c.pt.translation', 'z3c.pt.clauses', 'z3c.pt.expressions')
 
     return unittest.TestSuite(

Modified: z3c.pt/trunk/src/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -21,14 +21,16 @@
     """
 
     metal_slot_prefix = '_fill'
-
+    metal_variable = '_metal'
+    macro_variable = '_macro'
+    
     def start(self, stream):
         self._stream = stream
         self.visit()
 
     def update(self):
-        self._preprocess_genshi()
-
+        pass
+    
     def begin(self):
         self.stream.scope.append(set())
         self.stream.begin(self._serialize())
@@ -38,11 +40,7 @@
         self.stream.scope.pop()
 
     def body(self):
-        skip = self._replace or self._content or \
-               self.metal_define or self.metal_use or \
-               self.i18n_translate is not None
-        
-        if not skip:
+        if not self.node.skip:
             for element in self:
                 element.update()
 
@@ -52,8 +50,7 @@
     def visit(self, skip_macro=True):
         assert self.stream is not None, "Must use ``start`` method."
 
-        macro = self.py_def or self.metal_define
-        if skip_macro and macro is not None:
+        if skip_macro and (self.node.method or self.node.define_macro):
             return
 
         for element in self:
@@ -75,179 +72,47 @@
 
         raise ValueError("Can't locate stream object.")
         
-    @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)
-                
-            # 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
                     
-                element.attrib[utils.tal_attr('condition')] = expression
-
-            # 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 getattr(element, 'py_match', None) 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:
-            while self.text:
-                text = self.text
-                m = translator.interpolate(text)
-                if m is None:
-                    break
-
-                t = etree.element_factory(utils.tal_attr('interpolation'))
-                t.attrib['replace'] = "structure "+m.group('expression')
-                t.tail = text[m.end():]
-                self.insert(0, t)
-                t.update()
-
-                if m.start() == 0:
-                    self.text = text[1:m.start()+1]
-                else:
-                    self.text = text[:m.start()+1]
-
-        if self.tail is not None:
-            while self.tail:
-                m = translator.interpolate(self.tail)
-                if m is None:
-                    break
-
-                t = etree.element_factory(utils.tal_attr('interpolation'))
-                t.attrib['replace'] = "structure "+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]
-                
-        for name in self._get_static_attributes():
-            value = self.attrib[name]
-
-            if translator.interpolate(value):
-                del self.attrib[name]
-
-                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
-                    
     def _serialize(self):
         """Serialize element into clause-statements."""
 
         _ = []
 
         # i18n domain
-        if self.i18n_domain is not None:
+        if self.node.translation_domain is not None:
             _.append(clauses.Define(
-                "_domain", types.value(repr(self.i18n_domain))))
+                "_domain", types.value(repr(self.node.translation_domain))))
 
         # variable definitions
-        if self._define is not None:
-            for variables, expression in self._define:
+        if self.node.define is not None:
+            for variables, expression in self.node.define:
                 _.append(clauses.Define(variables, expression))
 
-        # genshi macro
+        # macro method
         for element in tuple(self):
             if not isinstance(element, Element):
                 continue
 
-            macro = element.py_def
+            macro = element.node.method
             if macro is not None:
                 # define macro
                 subclauses = []
                 subclauses.append(clauses.Method(
-                    "_macro", macro.args))
+                    self.macro_variable, macro.args))
                 subclauses.append(clauses.Visit(element))
                 _.append(clauses.Group(subclauses))
                 
                 # assign to variable
                 _.append(clauses.Define(
-                    macro.name, types.parts((types.value("_macro"),))))
+                    macro.name, types.parts((types.value(self.macro_variable),))))
 
         # condition
-        if self._condition is not None:
-            _.append(clauses.Condition(self._condition))
+        if self.node.condition is not None:
+            _.append(clauses.Condition(self.node.condition))
 
         # repeat
-        if self._repeat is not None:
-            variables, expression = self._repeat
+        if self.node.repeat is not None:
+            variables, expression = self.node.repeat
             if len(variables) != 1:
                 raise ValueError(
                     "Cannot unpack more than one variable in a "
@@ -256,37 +121,75 @@
 
         # tag tail (deferred)
         tail = self.tail
-        if tail and not self.metal_fillslot:
+        if tail and not self.node.fill_slot:
             if isinstance(tail, unicode):
                 tail = tail.encode('utf-8')
             _.append(clauses.Out(tail, defer=True))
 
-        # dynamic content and content translation
-        replace = self._replace
-        content = self._content
+        content = self.node.content
 
-        if self.metal_defineslot:
+        # macro slot definition
+        if self.node.define_slot:
             # check if slot has been filled
-            variable = self.metal_slot_prefix+self.metal_defineslot
+            variable = self.metal_slot_prefix + self.node.define_slot
             if variable in itertools.chain(*self.stream.scope):
                 content = types.value(variable)
-            
-        # compute dynamic flag
-        dynamic = replace or content or self.i18n_translate is not None
 
+        # set dynamic content flag
+        dynamic = content or self.node.translate is not None
+
+        # static attributes are at the bottom of the food chain
+        attributes = self.node.static_attributes
+
+        # dynamic attributes
+        attrs = self.node.dynamic_attributes or ()
+        dynamic_attributes = tuple(attrs)
+
+        for variables, expression in attrs:
+            if len(variables) != 1:
+                raise ValueError("Tuple definitions in assignment clause "
+                                     "is not supported.")
+
+            variable = variables[0]
+            attributes[variable] = expression
+
+        # translated attributes
+        translated_attributes = self.node.translated_attributes or ()
+        for variable, msgid in translated_attributes:
+            if msgid:
+                if variable in dynamic_attributes:
+                    raise ValueError(
+                        "Message id not allowed in conjunction with "
+                        "a dynamic attribute.")
+
+                value = types.value('"%s"' % msgid)
+
+                if variable in attributes:
+                    default = '"%s"' % attributes[variable]
+                    expression = _translate(value, default=default)
+                else:
+                    expression = _translate(value)
+            else:
+                if variable in dynamic_attributes or variable in attributes:
+                    text = '"%s"' % attributes[variable]
+                    expression = _translate(text)
+                else:
+                    raise ValueError("Must be either static or dynamic "
+                                     "attribute when no message id "
+                                     "is supplied.")
+
+            attributes[variable] = expression
+
         # tag
-        if replace is None:
+        if self.node.omit is not True:
             selfclosing = self.text is None and not dynamic and len(self) == 0
-            tag = clauses.Tag(self.tag, self._get_attributes(),
-                              expression=self.py_attrs, selfclosing=selfclosing,
-                              cdata=self.tal_cdata is not None)
-
-            if self._omit:
-                _.append(clauses.Condition(_not(self._omit), [tag],
-                                           finalize=False))
-            elif self._omit is not None or \
-                 self.metal_use or self.metal_fillslot:
-                pass
+            tag = clauses.Tag(
+                self.tag, attributes,
+                expression=self.node.dict_attributes, selfclosing=selfclosing,
+                cdata=self.node.cdata is not None)
+            if self.node.omit:
+                _.append(clauses.Condition(
+                    _not(self.node.omit), [tag], finalize=False))
             else:
                 _.append(tag)
 
@@ -297,26 +200,25 @@
                 text = text.encode('utf-8')
             _.append(clauses.Out(text))
 
-        if replace and content:
-            raise ValueError("Can't use replace clause together with "
-                             "content clause.")
-
-        if replace or content:
-            if self.i18n_translate is not None:
-                if self.i18n_translate != "":
-                    raise ValueError("Can't use message id with "
-                                     "dynamic content translation.")
+        # dynamic content
+        if content:
+            msgid = self.node.translate
+            if msgid is not None:
+                if msgid:
+                    raise ValueError(
+                        "Can't use message id with dynamic content translation.")
+                
                 _.append(clauses.Translate())
+            _.append(clauses.Write(content))
 
-            _.append(clauses.Write(replace or content))
-        elif self.metal_use:
+        # use macro
+        elif self.node.use_macro:
             # for each fill-slot element, create a new output stream
             # and save value in a temporary variable
             kwargs = []
-
             for element in self.xpath(
                 './/*[@metal:fill-slot]', namespaces={'metal': config.METAL_NS}):
-                variable = self.metal_slot_prefix+element.metal_fillslot
+                variable = self.metal_slot_prefix+element.node.fill_slot
                 kwargs.append((variable, variable))
                 
                 subclauses = []
@@ -328,7 +230,7 @@
                     types.value('_out.getvalue()'), variable))
                 _.append(clauses.Group(subclauses))
                 
-            _.append(clauses.Assign(self.metal_use, '_metal'))
+            _.append(clauses.Assign(self.node.use_macro, self.metal_variable))
 
             # compute macro function arguments and create argument string
             arguments = ", ".join(
@@ -336,69 +238,69 @@
                       itertools.chain(*self.stream.scope))+
                 tuple("%s=%s" % kwarg for kwarg in kwargs))
                 
-            _.append(clauses.Write(types.value("_metal(%s)" % arguments)))
-            
-        else:
-            if self.i18n_translate is not None:
-                msgid = self.i18n_translate
-                if not msgid:
-                    msgid = self._msgid()
+            _.append(clauses.Write(types.value("%s(%s)" % (self.metal_variable, arguments))))
 
-                # 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]
+        # translate body
+        elif self.node.translate is not None:
+            msgid = self.node.translate
+            if not msgid:
+                msgid = self._msgid()
 
-                if elements:
-                    mapping = '_mapping'
-                    _.append(clauses.Assign(types.value('{}'), mapping))
-                else:
-                    mapping = 'None'
-                    
-                for element in elements:
-                    name = element.i18n_name
-                    
-                    subclauses = []
-                    subclauses.append(clauses.Define(
-                        ('_out', '_write'),
-                        types.value('generation.initialize_stream()')))
-                    subclauses.append(clauses.Visit(element))
-                    subclauses.append(clauses.Assign(
-                        types.value('_out.getvalue()'),
-                        "%s['%s']" % (mapping, name)))
+            # 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]
 
-                    _.append(clauses.Group(subclauses))
+            if elements:
+                mapping = '_mapping'
+                _.append(clauses.Assign(types.value('{}'), mapping))
+            else:
+                mapping = 'None'
 
-                _.append(clauses.Assign(
-                    _translate(types.value(repr(msgid)), mapping=mapping,
-                               default='_marker'), '_result'))
+            for element in elements:
+                name = element.i18n_name
 
-                # write translation to output if successful, otherwise
-                # fallback to default rendition; 
-                result = types.value('_result')
-                condition = types.value('_result is not _marker')
-                _.append(clauses.Condition(condition,
-                            [clauses.UnicodeWrite(result)]))
-
                 subclauses = []
-                if self.text:
-                    subclauses.append(clauses.Out(self.text.encode('utf-8')))
-                for element in self:
-                    name = element.i18n_name
-                    if name:
-                        value = types.value("%s['%s']" % (mapping, name))
-                        subclauses.append(clauses.Write(value))
-                    else:
-                        subclauses.append(clauses.Out(element.tostring()))
-                if subclauses:
-                    _.append(clauses.Else(subclauses))
+                subclauses.append(clauses.Define(
+                    ('_out', '_write'),
+                    types.value('generation.initialize_stream()')))
+                subclauses.append(clauses.Visit(element))
+                subclauses.append(clauses.Assign(
+                    types.value('_out.getvalue()'),
+                    "%s['%s']" % (mapping, name)))
 
+                _.append(clauses.Group(subclauses))
+
+            _.append(clauses.Assign(
+                _translate(types.value(repr(msgid)), mapping=mapping,
+                           default='_marker'), '_result'))
+
+            # write translation to output if successful, otherwise
+            # fallback to default rendition; 
+            result = types.value('_result')
+            condition = types.value('_result is not _marker')
+            _.append(clauses.Condition(condition,
+                        [clauses.UnicodeWrite(result)]))
+
+            subclauses = []
+            if self.text:
+                subclauses.append(clauses.Out(self.text.encode('utf-8')))
+            for element in self:
+                name = element.i18n_name
+                if name:
+                    value = types.value("%s['%s']" % (mapping, name))
+                    subclauses.append(clauses.Write(value))
+                else:
+                    subclauses.append(clauses.Out(element.tostring()))
+            if subclauses:
+                _.append(clauses.Else(subclauses))
+
         return _
 
     def _wrap_literal(self, element):
         index = self.index(element)
 
-        t = etree.element_factory(utils.tal_attr('literal'))
-        t.attrib['omit-tag'] = ''
+        t = self.makeelement(utils.meta_attr('literal'))
+        t.attrib[utils.meta_attr('omit-tag')] = ''
         t.tail = element.tail
         t.text = unicode(element)
         for child in element.getchildren():
@@ -424,65 +326,6 @@
         
         return msgid
 
-    def _get_static_attributes(self):
-        attributes = {}
-
-        for key in self.keys():
-            if not key.startswith('{'):
-                attributes[key] = self.attrib[key]
-
-        return attributes
-        
-    def _get_attributes(self):
-        """Aggregate static, dynamic and translatable attributes."""
-
-        # static attributes are at the bottom of the food chain
-        attributes = self._get_static_attributes()
-        
-        # dynamic attributes
-        attrs = self.tal_attributes
-        if attrs is not None:
-            for variables, expression in attrs:
-                if len(variables) != 1:
-                    raise ValueError("Tuple definitions in assignment clause "
-                                     "is not supported.")
-
-                variable = variables[0]
-                attributes[variable] = expression
-        else:
-            attrs = []
-
-        dynamic = [key for (key, expression) in attrs]
-
-        # translated attributes
-        if self.i18n_attributes:
-            for variable, msgid in self.i18n_attributes:
-                if msgid:
-                    if variable in dynamic:
-                        raise ValueError(
-                            "Message id not allowed in conjunction with "
-                            "a dynamic attribute.")
-
-                    value = types.value('"%s"' % msgid)
-
-                    if variable in attributes:
-                        default = '"%s"' % attributes[variable]
-                        expression = _translate(value, default=default)
-                    else:
-                        expression = _translate(value)
-                else:
-                    if variable in dynamic or variable in attributes:
-                        text = '"%s"' % attributes[variable]
-                        expression = _translate(text)
-                    else:
-                        raise ValueError("Must be either static or dynamic "
-                                         "attribute when no message id "
-                                         "is supplied.")
-
-                attributes[variable] = expression
-
-        return attributes
-
     def _pull_attribute(self, name, default=None):
         if name in self.attrib:
             value = self.attrib[name]
@@ -490,166 +333,76 @@
             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
+    def node(self):
+        return NotImplementedError("Must be provided by subclass.")
 
-    @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
+    meta_cdata = utils.attribute(
+        utils.meta_attr('cdata'))
     
-    tal_define = utils.attribute(
-        utils.tal_attr('define'), lambda p: p.definitions)
-    tal_condition = utils.attribute(
-        utils.tal_attr('condition'), lambda p: p.expression)
-    tal_repeat = utils.attribute(
-        utils.tal_attr('repeat'), lambda p: p.definition)
-    tal_attributes = utils.attribute(
-        utils.tal_attr('attributes'), lambda p: p.definitions)
-    tal_content = utils.attribute(
-        utils.tal_attr('content'), lambda p: p.output)
-    tal_replace = utils.attribute(
-        utils.tal_attr('replace'), lambda p: p.output)
-    tal_omit = utils.attribute(
-        utils.tal_attr('omit-tag'), lambda p: p.expression)
-    tal_default_expression = utils.attribute(
-        utils.tal_attr('default-expression'))
-    tal_cdata = utils.attribute(
-        utils.tal_attr('cdata'))
-    metal_define = utils.attribute(
-        utils.metal_attr('define-macro'), lambda p: p.method)
-    metal_use = utils.attribute(
-        utils.metal_attr('use-macro'), lambda p: p.expression)
-    metal_fillslot = utils.attribute(
-        utils.metal_attr('fill-slot'))
-    metal_defineslot = utils.attribute(
-        utils.metal_attr('define-slot'))
-    i18n_translate = utils.attribute(
-        utils.i18n_attr('translate'))
-    i18n_attributes = utils.attribute(
-        utils.i18n_attr('attributes'), lambda p: p.mapping)
-    i18n_domain = utils.attribute(
-        utils.i18n_attr('domain'))
-    i18n_name = utils.attribute(
-        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)
-    tal_condition = utils.attribute("condition", lambda p: p.expression)
-    tal_replace = utils.attribute("replace", lambda p: p.output)
-    tal_repeat = utils.attribute("repeat", lambda p: p.definition)
-    tal_attributes = utils.attribute("attributes", lambda p: p.expression)
-    tal_content = utils.attribute("content", lambda p: p.output)
-    tal_omit = utils.attribute("omit-tag", lambda p: p.expression, u"")
-    tal_default_expression = utils.attribute(
-        'default-expression')
-    tal_cdata = utils.attribute("cdata")
+    meta_omit = utils.attribute(
+        utils.meta_attr('omit-tag'))
 
-    def _get_static_attributes(self):
-        attributes = {}
+    meta_attributes =  utils.attribute(
+        utils.meta_attr('attributes'), lambda p: p.definitions)
 
-        for key in self.keys():
-            if key not in ('define',
-                           'condition',
-                           'replace',
-                           'repeat',
-                           'attributes',
-                           'content',
-                           'omit-tag',
-                           'default-expression',
-                           'cdata'):
-                raise ValueError(
-                    u"Attribute '%s' not allowed in the namespace '%s'" %
-                    (key, self.nsmap[self.prefix]))
+    meta_replace = utils.attribute(
+        utils.meta_attr('replace'), lambda p: p.output)
 
-        return attributes
+class VariableInterpolation:
+    def update(self):
+        translator = self.translator
+        
+        if self.text is not None:
+            while self.text:
+                text = self.text
+                m = translator.interpolate(text)
+                if m is None:
+                    break
 
-class METALElement(Element):
-    metal_define = utils.attribute('define-macro', lambda p: p.method)
-    metal_use = utils.attribute('use-macro', lambda p: p.expression)
-    metal_fillslot = utils.attribute('fill-slot')
-    metal_defineslot = utils.attribute('define-slot')
+                t = self.makeelement(utils.meta_attr('interpolation'))
+                expression = "structure "+m.group('expression')
+                t.attrib[utils.meta_attr('replace')] = expression
+                t.tail = text[m.end():]
+                self.insert(0, t)
+                t.update()
 
-class PyElement(Element):
-    tal_omit = utils.attribute("omit-tag", lambda p: p.expression, u"")
+                if m.start() == 0:
+                    self.text = text[1:m.start()+1]
+                else:
+                    self.text = text[:m.start()+1]
 
-class PyIfElement(PyElement):
-    py_if = utils.attribute("test", lambda p: p.expression)
+        if self.tail is not None:
+            while self.tail:
+                m = translator.interpolate(self.tail)
+                if m is None:
+                    break
 
-class PyForElement(PyElement):
-    py_for = utils.attribute("each", lambda p: p.definition)
+                t = self.makeelement(utils.meta_attr('interpolation'))
+                expression = "structure "+m.group('expression')
+                t.attrib[utils.meta_attr('replace')] = 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]
 
-class PyWithElement(PyElement):
-    py_with = utils.attribute(
-        "vars", lambda p: expressions.PythonTranslation.definitions)
+        for name in utils.get_attributes_from_namespace(self, config.XHTML_NS):
+            value = self.attrib[name]
 
-class PyDefElement(PyElement):
-    py_def = utils.attribute("function", lambda p: p.method)
+            if translator.interpolate(value):
+                del self.attrib[name]
 
-class PyMatchElement(PyElement):
-    py_match = utils.attribute("path")
+                attributes = utils.meta_attr('attributes')
+                expr = '%s string: %s' % (name, value)
+                if attributes in self.attrib:
+                    self.attrib[attributes] += '; %s' % expr
+                else:
+                    self.attrib[attributes] = expr
 
-# set up namespaces for XML parsing
-etree.ns_lookup(config.XML_NS)[None] = Element
-etree.ns_lookup(config.TAL_NS)[None] = TALElement
-etree.ns_lookup(config.METAL_NS)[None] = METALElement
-etree.ns_lookup(config.PY_NS)["if"] = PyIfElement
-etree.ns_lookup(config.PY_NS)["for"] = PyForElement
-etree.ns_lookup(config.PY_NS)["def"] = PyDefElement
-etree.ns_lookup(config.PY_NS)["with"] = PyWithElement
-etree.ns_lookup(config.PY_NS)["match"] = PyMatchElement
-
-def translate_xml(body, *args, **kwargs):
-    root, doctype = etree.parse(body)
+def translate_xml(body, parser, *args, **kwargs):
+    root, doctype = parser.parse(body)
     return translate_etree(root, doctype=doctype, *args, **kwargs)
 
 def translate_etree(root, macro=None, doctype=None,
@@ -670,9 +423,14 @@
         del root.attrib[utils.metal_attr('define-macro')]
         
     # set default expression name
-    if not root.tal_default_expression:
-        root.tal_default_expression = default_expression
+    if utils.get_namespace(root) == config.TAL_NS:
+        tag = 'default-expression'
+    else:
+        tag = utils.tal_attr('default-expression')
 
+    if not root.attrib.get(tag):
+        root.attrib[tag] = default_expression
+
     # set up code generation stream
     if macro is not None:
         wrapper = generation.macro_wrapper
@@ -691,16 +449,13 @@
         stream.scope.pop()
 
     root.start(stream)
-
     return generator
 
-def translate_text(body, *args, **kwargs):
-    root = etree.element_factory(
-        utils.xml_attr('text'), nsmap={None: config.XML_NS})
-    
+def translate_text(body, parser, *args, **kwargs):
+    root, doctype = parser.parse("<html xmlns='%s'></html>" % config.XHTML_NS)
     root.text = body
-    root.attrib[utils.tal_attr('omit-tag')] = ''
-    return translate_etree(root, *args, **kwargs)
+    root.attrib[utils.meta_attr('omit-tag')] = ''
+    return translate_etree(root, doctype=doctype, *args, **kwargs)
     
 def _translate(value, mapping=None, default=None):
     format = ("_translate(%s, domain=_domain, mapping=%s, context=_context, "

Modified: z3c.pt/trunk/src/z3c/pt/translation.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.txt	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/translation.txt	2008-08-15 23:50:05 UTC (rev 89902)
@@ -3,30 +3,29 @@
 
 This document contains functional template tests.
 
-  >>> from z3c.pt.testing import render
-  >>> from z3c.pt.translation import translate_xml
+  >>> from z3c.pt.testing import render_xhtml
 
 XHTML
 -----
 
 :: Plain HTML document
 
-  >>> print render("""\
+  >>> print render_xhtml("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml">
   ...   Hello World!
-  ... </div>""", translate_xml)
+  ... </div>""")
     <div>
       Hello World!
     </div>
 
 :: Setting DOCTYPE
 
-  >>> print render("""\
+  >>> print render_xhtml("""\
   ... <!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">
   ...   Hello World!
-  ... </html>""", translate_xml)
+  ... </html>""")
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     <html>
       Hello World!
@@ -34,24 +33,24 @@
 
 :: Unicode 
 
-  >>> print render("""\
+  >>> print render_xhtml("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml">
   ...   La Peña
   ...   <img alt="La Peña" />
-  ... </div>""", translate_xml)
+  ... </div>""")
     <div>
       La Peña
       <img alt="La Peña" />
     </div>
 
 :: CDATA blocks
-  >>> print render("""\
+  >>> print render_xhtml("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml">
   ...   /* <![CDATA[ */
   ...   This is protected
   ...   /* ]]> */
   ...   <span>Not protected</span> <![CDATA[ This is protected ]]>
-  ... </div>""", translate_xml)
+  ... </div>""")
     <div>
       /* <![CDATA[ */
       This is protected
@@ -59,66 +58,18 @@
       <span>Not protected</span> <![CDATA[ This is protected ]]>
     </div>
 
-:: Variables containing quotes
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml">
-  ...   <strong>"${quote}"</strong>
-  ... </div>""", translate_xml, quote="Hello, World!")
-    <div>
-      <strong>"Hello, World!"</strong>
-    </div>
-  
-:: Variables containing markup
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml">
-  ...   ${message}
-  ... </div>""", translate_xml, message="Hello, <em>World</em>!")
-    <div>
-      Hello, <em>World</em>!
-    </div>
-
-:: Variable expansion must preserve XML validity
-
-  >>> from z3c.pt import config
-  >>> config.VALIDATION = True
-  
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml">
-  ...   ${message}
-  ... </div>""", translate_xml, message="Hello, <em>World!")
-  Traceback (most recent call last):
-   ...
-  ExpatError: ...
-  
-  >>> config.VALIDATION = False
-  
-:: Unless we are in a CDATA block
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml">
-  ... /* <![CDATA[ */
-  ...   ${message}
-  ... /* ]]> */
-  ... </div>""", translate_xml, message="Hello, <em>World!")
-    <div>
-      /* <![CDATA[ */
-      Hello, <em>World!
-      /* ]]> */
-    </div>
-
 Literals
 --------
 
 :: Named entities output literally (note doctype is required to prevent
    lxml from raising a XMLSyntaxError :-( )
 
-  >>> print render("""\
+  >>> print render_xhtml("""\
   ... <!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">
   ...   Hello &nbsp; World!
-  ... </html>""", translate_xml)
+  ... </html>""")
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     <html>
       Hello &nbsp; World!
@@ -126,11 +77,11 @@
 
 :: Processing instructions output literally
 
-  >>> print render("""\
+  >>> print render_xhtml("""\
   ... <html xmlns="http://www.w3.org/1999/xhtml">
   ...   <?xml-stylesheet href="classic.xsl" type="text/xml"?>
   ...   Hello World!
-  ... </html>""", translate_xml)
+  ... </html>""")
     <html>
       <?xml-stylesheet href="classic.xsl" type="text/xml"?>
       Hello World!
@@ -138,589 +89,18 @@
 
 :: Literal comments (without embedded expressions) output literally
 
-  >>> print render("""\
+  >>> print render_xhtml("""\
   ... <html xmlns="http://www.w3.org/1999/xhtml">
   ...   <!-- hello world -->
-  ... </html>""", translate_xml)
+  ... </html>""")
     <html>
       <!-- hello world -->
     </html>
-
-Zope TAL
---------
-
-:: Namespace elements
-
-  >>> print render("""\
-  ... <tal:block xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   Hello, world!
-  ... </tal:block>""", translate_xml)
-  <BLANKLINE>
-    Hello, world!
-  <BLANKLINE>
-
-tal:define, tal:attributes, tal:contents    
   
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <span id="test"
-  ...         class="dummy"
-  ...         tal:define="a 'abc'"
-  ...         tal:attributes="class 'def' + a; style 'hij'"
-  ...         tal:content="a + 'ghi'" />
-  ...   <span tal:replace="'Hello World!'">Hello Universe!</span>
-  ...   <span tal:replace="'Hello World!'"><b>Hello Universe!</b></span>
-  ...   <span tal:content="None" />
-  ... </div>""", translate_xml)
-    <div>
-      <span id="test" style="hij" class="defabc">abcghi</span>
-      Hello World!
-      Hello World!
-      <span></span>
-    </div>
-
-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="'Item ' + str(i) + ')'" /></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>
-      </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(3)"><span tal:replace="str(i) + ' ' + str(repeat['i'].even())" /></li>
-  ...   </ul>
-  ... </div>""", translate_xml)
-    <div>
-      <ul>
-        <li>0 True</li>
-        <li>1 False</li>
-        <li>2 True</li>
-      </ul>
-    </div>
-
-tal:condition
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <div tal:condition="True">
-  ...     Show me!
-  ...   </div>
-  ...   <div tal:condition="False">
-  ...     Do not show me!
-  ...   </div>
-  ... </div>""", translate_xml)
-    <div>
-      <div>
-        Show me!
-      </div>
-    </div>
-
-:: HTML comments
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <!-- a comment -->
-  ...   <!-- a multi-
-  ...        line comment -->
-  ...   <!-- a comment with an ${'expression'} -->
-  ... </div>""", translate_xml)
-    <div>
-      <!-- a comment -->
-      <!-- a multi-
-           line comment -->
-      <!-- a comment with an expression -->
-    </div>
-
-:: TAL elements with namespace prefix
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <tal:example replace="'Hello World!'" />
-  ...   <tal:div content="'Hello World!'" />
-  ...   <tal:multiple repeat="i range(3)" replace="i" />
-  ...   <tal:div condition="True">True</tal:div>
-  ... </div>""", translate_xml)
-    <div>
-      Hello World!
-      Hello World!
-      0
-      1
-      2
-      True
-    </div>
-
-tal:omit-tag
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <p tal:omit-tag="">No paragraph here.</p>
-  ...   <p tal:omit-tag="True">No paragraph here either.</p>
-  ...   <p tal:omit-tag="False">A paragraph here.</p>
-  ... </div>""", translate_xml)
-    <div>
-      No paragraph here.
-      No paragraph here either.
-      <p>A paragraph here.</p>
-    </div>
-
-:: 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 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))" />
-  ...   <span tal:content="unicode('La Pe\xc3\xb1a', 'utf-8')" />
-  ... </div>""", translate_xml)
-    <div>
-      <img title="&lt;Hello&gt;" />
-      <br />
-      &lt;br /&gt;
-      <span>La Peña</span>
-    </div>
-
-:: Using the "path:" expression
-
-  >>> class Test(object):
-  ...     greeting = u'Hello'
-  
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <span tal:replace="path: test1/greeting" />
-  ...   <span tal:replace="path: test2/greeting" />
-  ... </div>""", translate_xml, request=object(),
-  ... test1={'greeting': u'Hello'}, test2=Test())
-  <div>
-    Hello
-    Hello
-  </div>
-
-:: Using the "string:" expression
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <span tal:replace="string:${greeting}, world!" />
-  ...   <img tal:attributes="alt string:Leonardo da Vinci;; Musee du Louvre, 1503;
-  ...                        title string:Mona Lisa" />
-  ... </div>""", translate_xml, request=object(), greeting=u'Hello')
-  <div>
-    Hello, world!
-    <img alt="Leonardo da Vinci; Musee du Louvre, 1503" title="Mona Lisa" />
-  </div>
-
-:: Setting the default expression
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <div tal:default-expression="path">
-  ...     <span tal:replace="test/greeting" />
-  ...   </div>
-  ... </div>""", translate_xml, request=object(), test={'greeting': u'Hello'})
-  <div>
-    <div>
-      Hello
-    </div>
-  </div>
-
-:: Using different expressions with try-except operator (|)
-  
-  >>> 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}}">
-  ...       <div tal:default-expression="path">
-  ...          <span tal:replace="mydict/a" />
-  ...          <span tal:replace="mydict/b|mydict/a" />
-  ...          <span tal:replace="mydict/c/a" />
-  ...          <span tal:replace="python: 5+5" />
-  ...       </div>
-  ...       <span tal:replace="path: mydict/a" />
-  ...       <span tal:replace="python: 1+1" />
-  ...       <span tal:replace="path: mydict/b|True" />
-  ...       <span tal:replace="int('a')|path: mydict/a" />
-  ...   </tal:path-expression-testing>
-  ... </div>""", translate_xml)
-  <div>
-  <BLANKLINE>
-        <div>
-           1
-           1
-           2
-           10
-        </div>
-        1
-        2
-        True
-        1
-  <BLANKLINE>
-  </div>
-
-:: Using TAL pragmas "nocall" and "structure"
-
-  >>> print render("""\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
-  ...   <span tal:default-expression="path"
-  ...         tal:replace="structure nocall: dir" />
-  ...   <span tal:replace="structure dir" />
-  ... </div>""", translate_xml, request=object())
-    <div>
-      <built-in function dir>
-      <built-in function dir>
-    </div>
-
-METAL
------
-
-metal:define-macro, metal:use-macro
-
-  >>> body = """\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
-  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
-  ...   <div class="greeting" metal:define-macro="greeting">
-  ...     Hello, ${name}!
-  ...   </div>
-  ...   <div tal:define="name 'world'">
-  ...     <div metal:use-macro="template.macros['greeting']" />
-  ...   </div>  
-  ... </div>"""
-
-  >>> from z3c.pt.testing import MockTemplate
-  >>> template = MockTemplate(body, translate_xml)
-  >>> print render(body, translate_xml, template=template)
-  <div>
-    <div>
-      <div class="greeting">
-        Hello, world!
-      </div>
-    </div>  
-  </div>
-
-metal:define-slot, metal:fill-slot
-
-  >>> body = """\
-  ... <div xmlns="http://www.w3.org/1999/xhtml"
-  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
-  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
-  ...   <div metal:define-macro="greeting">
-  ...     Hello, <b class="name" metal:define-slot="name">stranger!</b>
-  ...   </div>
-  ...   <div metal:use-macro="template.macros['greeting']">
-  ...     <span metal:fill-slot="name">earth!</span>
-  ...   </div>
-  ...   <div metal:use-macro="template.macros['greeting']">
-  ...     <!-- display fallback greeting -->
-  ...   </div>
-  ...   <div metal:use-macro="template.macros['greeting']">
-  ...     <span metal:fill-slot="dummy">dummy!</span>
-  ...   </div>
-  ... </div>"""
-
-  >>> from z3c.pt.testing import MockTemplate
-  >>> template = MockTemplate(body, translate_xml)
-  >>> print render(body, translate_xml, template=template)
-  <div>
-  <BLANKLINE>
-      <div>
-      Hello, <b class="name">earth!</b>
-    </div>
-  <BLANKLINE>
-  <BLANKLINE>
-      <div>
-      Hello, <b class="name">stranger!</b>
-    </div>
-  <BLANKLINE>
-  <BLANKLINE>
-      <div>
-      Hello, <b class="name">stranger!</b>
-    </div>
-  <BLANKLINE>
-  </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
-
-  >>> def quote():
-  ...     return dict(author=u"Some name", quote=u"Some random quote")
-     
-  >>> 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>
-  ...   <blockquote py:with="q=quote()">
-  ...       "${q["quote"]} <em>${q["author"]}</em>
-  ...   </blockquote>
-  ... </div>""", translate_xml, quote=quote)
-  <div>
-    <span>2 7 12</span>
-    4 3 9
-    <blockquote>
-        "Some random quote <em>Some name</em>
-    </blockquote>
-  </div>
-    
-py:attrs
-
-  >>> print render("""\
-  ... <ul xmlns="http://www.w3.org/1999/xhtml"
-  ...     xmlns:py="http://genshi.edgewall.org">
-  ...   <li class="expand" 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
 --------------
 
-  >>> from z3c.pt.translation import translate_text
+  >>> from z3c.pt.testing import render_text
 
 An example with a CSS stylesheet document:
   
@@ -729,7 +109,7 @@
   ...    background: url(${'http://nohost/plone'}/logo.gif) no-repeat;
   ... }"""
 
-  >>> print render(css, translate_text)
+  >>> print render_text(css)
   #some-region {
      background: url(http://nohost/plone/logo.gif) no-repeat;
   }
@@ -739,7 +119,7 @@
   >>> js = """\
   ... print '<div class="description">Hello ${'World!'}</div>';"""
 
-  >>> print render(js, translate_text)
+  >>> print render_text(js)
   print '<div class="description">Hello World!</div>';
 
 Error handling
@@ -752,7 +132,7 @@
   A default namespace must be explicitly declared for the parser to work.
   
   >>> body = '<br />'
-  >>> render(body, translate_xml)
+  >>> render_xhtml(body)
   Traceback (most recent call last):
     ...
   ValueError: Must define valid namespace for tag: 'br.'
@@ -761,7 +141,7 @@
   We expect the xml-parser to raise an exception.
   
   >>> body = '<div xmlns="http://www.w3.org/1999/xhtml"'
-  >>> render(body, translate_xml)
+  >>> render_xhtml(body)
   Traceback (most recent call last):
     ...
   XMLSyntaxError: Couldn't find end of Start Tag div line 1, line 1, column 11
@@ -775,7 +155,7 @@
   ... <div xmlns="http://www.w3.org/1999/xhtml" tal:content="'Hello World'" />
   ... """
 
-  >>> print render(body, translate_xml)
+  >>> print render_xhtml(body)
   Traceback (most recent call last):
     ...
   XMLSyntaxError: Namespace prefix tal for content on div is not defined, line 1, column 23

Modified: z3c.pt/trunk/src/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/utils.py	2008-08-15 22:59:32 UTC (rev 89901)
+++ z3c.pt/trunk/src/z3c/pt/utils.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -110,12 +110,30 @@
             
         return value
 
-def xml_attr(name):
-    return "{%s}%s" % (config.XML_NS, name)
+def get_attributes_from_namespace(element, namespace):
+    if element.nsmap.get(element.prefix) == namespace:
+        return dict([
+            (name, value) for (name, value) in element.attrib.items() \
+            if '{' not in name])
 
+    return dict([
+        (name, value) for (name, value) in element.attrib.items() \
+        if name.startswith('{%s}' % namespace)])
+
+def get_namespace(element):
+    if '}' in element.tag:
+        return element.tag.split('}')[0][1:]
+    return element.nsmap[None]
+
+def xhtml_attr(name):
+    return "{%s}%s" % (config.XHTML_NS, name)
+
 def tal_attr(name):
     return "{%s}%s" % (config.TAL_NS, name)
 
+def meta_attr(name):
+    return "{%s}%s" % (config.META_NS, name)
+
 def metal_attr(name):
     return "{%s}%s" % (config.METAL_NS, name)
 

Added: z3c.pt/trunk/src/z3c/pt/zpt.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/zpt.py	                        (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/zpt.py	2008-08-15 23:50:05 UTC (rev 89902)
@@ -0,0 +1,189 @@
+from zope import component
+
+import translation
+import interfaces
+import utils
+import config
+import etree
+
+class ZopePageTemplateElement(
+    translation.Element, translation.VariableInterpolation):
+    """Zope Page Template element.
+
+    Implements the ZPT subset of the attribute template language.
+    """
+
+    class node(object):
+        def __init__(self, element):
+            self.element = element
+            
+        @property
+        def omit(self):
+            if self.element.tal_omit is not None:
+                return self.element.tal_omit or True
+            if self.element.meta_omit is not None:
+                return self.element.meta_omit or True
+            if self.element.tal_replace or self.element.meta_replace:
+                return True
+            if self.element.metal_use or self.element.metal_fillslot:
+                return True
+            
+        @property
+        def define(self):
+            return self.element.tal_define
+
+        @property
+        def condition(self):
+            return self.element.tal_condition
+
+        @property
+        def repeat(self):
+            return self.element.tal_repeat
+
+        @property
+        def content(self):
+            return self.element.tal_content or self.element.tal_replace or \
+                   self.element.meta_replace
+
+        @property
+        def skip(self):
+            return self.content or self.define_macro or \
+                   self.use_macro or self.translate is not None
+
+        dict_attributes = None
+
+        @property
+        def dynamic_attributes(self):
+            return (self.element.tal_attributes or ()) + \
+                   (self.element.meta_attributes or ())
+
+        @property
+        def translated_attributes(self):
+            return self.element.i18n_attributes
+        
+        @property
+        def static_attributes(self):
+            return utils.get_attributes_from_namespace(
+                self.element, config.XHTML_NS)
+            
+        @property
+        def translate(self):
+            return self.element.i18n_translate
+
+        @property
+        def translation_domain(self):
+            return self.element.i18n_domain
+
+        method = None
+        
+        @property
+        def use_macro(self):
+            return self.element.metal_use
+        
+        @property
+        def define_macro(self):
+            return self.element.metal_define
+
+        @property
+        def define_slot(self):
+            return self.element.metal_defineslot
+
+        @property
+        def fill_slot(self):
+            return self.element.metal_fillslot        
+
+        @property
+        def cdata(self):
+            return self.element.meta_cdata
+
+        @property
+        def default_expression(self):
+            return self.element.tal_default_expression
+        
+    node = property(node)
+
+    @property
+    def translator(self):
+        while self.node.default_expression is None:
+            self = self.getparent()
+            if self is None:
+                raise ValueError("Default expression not set.")
+            
+        return component.getUtility(
+            interfaces.IExpressionTranslation, name=self.node.default_expression)
+
+    def update(self):
+        translation.VariableInterpolation.update(self)        
+
+class XHTMLElement(ZopePageTemplateElement):
+    """XHTML namespace element."""
+
+    tal_define = utils.attribute(
+        utils.tal_attr('define'), lambda p: p.definitions)
+    tal_condition = utils.attribute(
+        utils.tal_attr('condition'), lambda p: p.expression)
+    tal_repeat = utils.attribute(
+        utils.tal_attr('repeat'), lambda p: p.definition)
+    tal_attributes = utils.attribute(
+        utils.tal_attr('attributes'), lambda p: p.definitions)
+    tal_content = utils.attribute(
+        utils.tal_attr('content'), lambda p: p.output)
+    tal_replace = utils.attribute(
+        utils.tal_attr('replace'), lambda p: p.output)
+    tal_omit = utils.attribute(
+        utils.tal_attr('omit-tag'), lambda p: p.expression)
+    tal_default_expression = utils.attribute(
+        utils.tal_attr('default-expression'))
+    metal_define = utils.attribute(
+        utils.metal_attr('define-macro'), lambda p: p.method)
+    metal_use = utils.attribute(
+        utils.metal_attr('use-macro'), lambda p: p.expression)
+    metal_fillslot = utils.attribute(
+        utils.metal_attr('fill-slot'))
+    metal_defineslot = utils.attribute(
+        utils.metal_attr('define-slot'))
+    i18n_translate = utils.attribute(
+        utils.i18n_attr('translate'))
+    i18n_attributes = utils.attribute(
+        utils.i18n_attr('attributes'), lambda p: p.mapping)
+    i18n_domain = utils.attribute(
+        utils.i18n_attr('domain'))
+    i18n_name = utils.attribute(
+        utils.i18n_attr('name'))
+
+class TALElement(ZopePageTemplateElement):
+    """TAL namespace element."""
+    
+    tal_define = utils.attribute("define", lambda p: p.definitions)
+    tal_condition = utils.attribute("condition", lambda p: p.expression)
+    tal_replace = utils.attribute("replace", lambda p: p.output)
+    tal_repeat = utils.attribute("repeat", lambda p: p.definition)
+    tal_attributes = utils.attribute("attributes", lambda p: p.expression)
+    tal_content = utils.attribute("content", lambda p: p.output)
+    tal_omit = utils.attribute("omit-tag", lambda p: p.expression, u"")
+    tal_default_expression = utils.attribute(
+        'default-expression')
+    tal_cdata = utils.attribute("cdata")
+
+    metal_define = None
+    metal_use = None
+    metal_fillslot = None
+    metal_defineslot = None
+    i18n_domain = None
+    i18n_translate = None
+    i18n_attributes = None
+    
+class METALElement(ZopePageTemplateElement):
+    """METAL namespace element."""
+    
+    metal_define = utils.attribute('define-macro', lambda p: p.method)
+    metal_use = utils.attribute('use-macro', lambda p: p.expression)
+    metal_fillslot = utils.attribute('fill-slot')
+    metal_defineslot = utils.attribute('define-slot')
+
+class ZopePageTemplateParser(etree.Parser):
+    element_mapping = {
+        config.XHTML_NS: {None: XHTMLElement},
+        config.META_NS: {None: XHTMLElement},
+        config.TAL_NS: {None: TALElement},
+        config.METAL_NS: {None: METALElement}}

Added: z3c.pt/trunk/src/z3c/pt/zpt.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/zpt.txt	                        (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/zpt.txt	2008-08-15 23:50:05 UTC (rev 89902)
@@ -0,0 +1,329 @@
+Zope Page Templates (ZPT)
+=========================
+
+  >>> from z3c.pt.testing import render_zpt
+
+TAL
+---
+  
+:: Namespace elements
+
+  >>> print render_zpt("""\
+  ... <tal:block xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   Hello, world!
+  ... </tal:block>""")
+  <BLANKLINE>
+    Hello, world!
+  <BLANKLINE>
+
+tal:define, tal:attributes, tal:contents    
+  
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span id="test"
+  ...         class="dummy"
+  ...         tal:define="a 'abc'"
+  ...         tal:attributes="class 'def' + a; style 'hij'"
+  ...         tal:content="a + 'ghi'" />
+  ...   <span tal:replace="'Hello World!'">Hello Universe!</span>
+  ...   <span tal:replace="'Hello World!'"><b>Hello Universe!</b></span>
+  ...   <span tal:content="None" />
+  ... </div>""")
+    <div>
+      <span id="test" style="hij" class="defabc">abcghi</span>
+      Hello World!
+      Hello World!
+      <span></span>
+    </div>
+
+tal:repeat
+    
+  >>> print render_zpt("""\
+  ... <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>
+  ...   </ul>
+  ... </div>""")
+    <div>
+      <ul>
+        <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_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <ul>
+  ...     <li tal:repeat="i range(3)"><span tal:replace="str(i) + ' ' + str(repeat['i'].even())" /></li>
+  ...   </ul>
+  ... </div>""")
+    <div>
+      <ul>
+        <li>0 True</li>
+        <li>1 False</li>
+        <li>2 True</li>
+      </ul>
+    </div>
+
+tal:condition
+
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <div tal:condition="True">
+  ...     Show me!
+  ...   </div>
+  ...   <div tal:condition="False">
+  ...     Do not show me!
+  ...   </div>
+  ... </div>""")
+    <div>
+      <div>
+        Show me!
+      </div>
+    </div>
+
+:: HTML comments
+
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <!-- a comment -->
+  ...   <!-- a multi-
+  ...        line comment -->
+  ...   <!-- a comment with an ${'expression'} -->
+  ... </div>""")
+    <div>
+      <!-- a comment -->
+      <!-- a multi-
+           line comment -->
+      <!-- a comment with an expression -->
+    </div>
+
+:: TAL elements with namespace prefix
+
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <tal:example replace="'Hello World!'" />
+  ...   <tal:div content="'Hello World!'" />
+  ...   <tal:multiple repeat="i range(3)" replace="i" />
+  ...   <tal:div condition="True">True</tal:div>
+  ... </div>""")
+    <div>
+      Hello World!
+      Hello World!
+      0
+      1
+      2
+      True
+    </div>
+
+tal:omit-tag
+
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <p tal:omit-tag="">No paragraph here.</p>
+  ...   <p tal:omit-tag="True">No paragraph here either.</p>
+  ...   <p tal:omit-tag="False">A paragraph here.</p>
+  ... </div>""")
+    <div>
+      No paragraph here.
+      No paragraph here either.
+      <p>A paragraph here.</p>
+    </div>
+
+:: Unicode with dynamic attributes and content
+    
+  >>> print render_zpt("""\
+  ... <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))" />
+  ...   <span tal:content="unicode('La Pe\xc3\xb1a', 'utf-8')" />
+  ... </div>""")
+    <div>
+      <img title="&lt;Hello&gt;" />
+      <br />
+      &lt;br /&gt;
+      <span>La Peña</span>
+    </div>
+
+:: Using the "path:" expression
+
+  >>> class Test(object):
+  ...     greeting = u'Hello'
+  
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span tal:replace="path: test1/greeting" />
+  ...   <span tal:replace="path: test2/greeting" />
+  ... </div>""", request=object(),
+  ... test1={'greeting': u'Hello'}, test2=Test())
+  <div>
+    Hello
+    Hello
+  </div>
+
+:: Using the "string:" expression
+
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span tal:replace="string:${greeting}, world!" />
+  ...   <img tal:attributes="alt string:Leonardo da Vinci;; Musee du Louvre, 1503;
+  ...                        title string:Mona Lisa" />
+  ... </div>""", request=object(), greeting=u'Hello')
+  <div>
+    Hello, world!
+    <img alt="Leonardo da Vinci; Musee du Louvre, 1503" title="Mona Lisa" />
+  </div>
+
+:: Setting the default expression
+
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <div tal:default-expression="path">
+  ...     <span tal:replace="test/greeting" />
+  ...   </div>
+  ... </div>""", request=object(), test={'greeting': u'Hello'})
+  <div>
+    <div>
+      Hello
+    </div>
+  </div>
+
+:: Using different expressions with try-except operator (|)
+  
+  >>> print render_zpt("""\
+  ... <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}}">
+  ...       <div tal:default-expression="path">
+  ...          <span tal:replace="mydict/a" />
+  ...          <span tal:replace="mydict/b|mydict/a" />
+  ...          <span tal:replace="mydict/c/a" />
+  ...          <span tal:replace="python: 5+5" />
+  ...       </div>
+  ...       <span tal:replace="path: mydict/a" />
+  ...       <span tal:replace="python: 1+1" />
+  ...       <span tal:replace="path: mydict/b|True" />
+  ...       <span tal:replace="int('a')|path: mydict/a" />
+  ...   </tal:path-expression-testing>
+  ... </div>""")
+  <div>
+  <BLANKLINE>
+        <div>
+           1
+           1
+           2
+           10
+        </div>
+        1
+        2
+        True
+        1
+  <BLANKLINE>
+  </div>
+
+:: Using TAL pragmas "nocall" and "structure"
+
+  >>> print render_zpt("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal">
+  ...   <span tal:default-expression="path"
+  ...         tal:replace="structure nocall: dir" />
+  ...   <span tal:replace="structure dir" />
+  ... </div>""", request=object())
+    <div>
+      <built-in function dir>
+      <built-in function dir>
+    </div>
+
+METAL
+-----
+
+metal:define-macro, metal:use-macro
+
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
+  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
+  ...   <div class="greeting" metal:define-macro="greeting">
+  ...     Hello, ${name}!
+  ...   </div>
+  ...   <div tal:define="name 'world'">
+  ...     <div metal:use-macro="template.macros['greeting']" />
+  ...   </div>  
+  ... </div>"""
+
+  >>> from z3c.pt.testing import MockTemplate
+  >>> from z3c.pt.zpt import ZopePageTemplateParser
+
+  >>> template = MockTemplate(body, ZopePageTemplateParser)
+  >>> print render_zpt(body, template=template)
+  <div>
+    <div>
+      <div class="greeting">
+        Hello, world!
+      </div>
+    </div>  
+  </div>
+
+metal:define-slot, metal:fill-slot
+
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
+  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
+  ...   <div metal:define-macro="greeting">
+  ...     Hello, <b class="name" metal:define-slot="name">stranger!</b>
+  ...   </div>
+  ...   <div metal:use-macro="template.macros['greeting']">
+  ...     <span metal:fill-slot="name">earth!</span>
+  ...   </div>
+  ...   <div metal:use-macro="template.macros['greeting']">
+  ...     <!-- display fallback greeting -->
+  ...   </div>
+  ...   <div metal:use-macro="template.macros['greeting']">
+  ...     <span metal:fill-slot="dummy">dummy!</span>
+  ...   </div>
+  ... </div>"""
+
+  >>> from z3c.pt.testing import MockTemplate
+  
+  >>> template = MockTemplate(body, ZopePageTemplateParser)
+  >>> print render_zpt(body, template=template)
+  <div>
+  <BLANKLINE>
+      <div>
+      Hello, <b class="name">earth!</b>
+    </div>
+  <BLANKLINE>
+  <BLANKLINE>
+      <div>
+      Hello, <b class="name">stranger!</b>
+    </div>
+  <BLANKLINE>
+  <BLANKLINE>
+      <div>
+      Hello, <b class="name">stranger!</b>
+    </div>
+  <BLANKLINE>
+  </div>
+
+



More information about the Checkins mailing list