[Checkins] SVN: z3c.pt/trunk/ Added Zope METAL-support.
Malthe Borch
mborch at gmail.com
Sat Aug 9 12:18:54 EDT 2008
Log message for revision 89579:
Added Zope METAL-support.
Changed:
U z3c.pt/trunk/CHANGES.txt
U z3c.pt/trunk/README.txt
U z3c.pt/trunk/setup.py
U z3c.pt/trunk/src/z3c/pt/config.py
U z3c.pt/trunk/src/z3c/pt/generation.py
A z3c.pt/trunk/src/z3c/pt/macro.py
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/translation.py
U z3c.pt/trunk/src/z3c/pt/translation.txt
U z3c.pt/trunk/src/z3c/pt/utils.py
-=-
Modified: z3c.pt/trunk/CHANGES.txt
===================================================================
--- z3c.pt/trunk/CHANGES.txt 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/CHANGES.txt 2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,9 +1,12 @@
Changelog
---------
-Version 0.9.x
+Version 1.0dev
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+- Added support for METAL.
+ [malthe]
+
- Add a TemplateLoader class to have a convenient method to instantiate
templates. This is similar to the template loaders from other template
toolkits and makes integration with Pylons a lot simpler.
Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/README.txt 2008-08-09 16:18:54 UTC (rev 89579)
@@ -5,6 +5,7 @@
following dialects of the attribute template language:
* Zope TAL
+* Zope METAL
* Zope i18n
* Genshi
@@ -19,9 +20,7 @@
* Templates are serialized and compiled into Python bytecode
* Pluggable expression implementation
-Note: Zope's METAL macro language is not supported.
-
Usage
-----
Modified: z3c.pt/trunk/setup.py
===================================================================
--- z3c.pt/trunk/setup.py 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/setup.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
-version = '0.9dev'
+version = '1.0dev'
setup(name='z3c.pt',
version=version,
Modified: z3c.pt/trunk/src/z3c/pt/config.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/config.py 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/config.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -12,8 +12,9 @@
XML_NS = "http://www.w3.org/1999/xhtml"
TAL_NS = "http://xml.zope.org/namespaces/tal"
+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=PY_NS)
+NS_MAP = dict(py=PY_NS, tal=TAL_NS, metal=METAL_NS)
Modified: z3c.pt/trunk/src/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/generation.py 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/generation.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -9,21 +9,27 @@
import z3c.pt.generation
from z3c.pt.config import DISABLE_I18N
-wrapper = """\
-def render(%starget_language=None):
+template_wrapper = """\
+def render(%(args)s%(extra)starget_language=None):
\tglobal generation
-\t(_out, _write) = generation.initialize_stream()
-\t(_attributes, repeat) = generation.initialize_tal()
-\t(_domain, _negotiate, _translate) = generation.initialize_i18n()
+\t_out, _write = generation.initialize_stream()
+\t_attributes, repeat = generation.initialize_tal()
+\t_domain, _negotiate, _translate = generation.initialize_i18n()
\t_marker = generation.initialize_helpers()
\t_path = generation.initialize_traversal()
\t_target_language = _negotiate(_context, target_language)
-%s
+%(code)s
\treturn _out.getvalue()
"""
+macro_wrapper = """\
+def render(%(kwargs)s%(extra)s):
+\tglobal generation
+%(code)s
+"""
+
def _fake_negotiate(context, target_language):
return target_language
@@ -62,32 +68,42 @@
return expressions.PathTranslation.traverse
class Generator(object):
- def __init__(self, params):
- self.params = tuple(params)
+ def __init__(self, params, wrapper):
+ self.params = list(params)
+ self.wrapper = wrapper
self.stream = CodeIO(indentation=1, indentation_string="\t")
# initialize variable scope
- self.stream.scope.append(set(params + ['_out', '_write']))
+ self.stream.scope.append(set(('_out', '_write') + tuple(params)))
def __call__(self):
- # prepare template arguments
- args = self.params
-
- # we need to ensure we have _context for the i18n handling in
- # the arguments. the default template implementations pass
- # this in explicitly.
- if '_context' not in args:
- args = args + ('_context=None', )
- args = ', '.join(args)
+ params = self.params
+ extra = ''
+
+ # prepare args
+ args = ', '.join(params)
if args:
args += ', '
- # pass selectors
+ # prepare kwargs
+ kwargs = ', '.join("%s=None" % param for param in params)
+ if kwargs:
+ kwargs += ', '
+
+ # prepare selectors
for selector in self.stream.selectors:
- args += '%s=None, ' % selector
+ extra += '%s=None, ' % selector
+ # we need to ensure we have _context for the i18n handling in
+ # the arguments. the default template implementations pass
+ # this in explicitly.
+ if '_context' not in params:
+ extra += '_context=None, '
+
code = self.stream.getvalue()
- return wrapper % (args, code), {'generation': z3c.pt.generation}
+ return self.wrapper % dict(
+ args=args, kwargs=kwargs, extra=extra, code=code), \
+ {'generation': z3c.pt.generation}
class BufferIO(list):
write = list.append
Added: z3c.pt/trunk/src/z3c/pt/macro.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/macro.py (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/macro.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -0,0 +1,8 @@
+class Macros(object):
+ def __init__(self, render):
+ self.render = render
+
+ def __getitem__(self, name):
+ def render(**kwargs):
+ return self.render(macro=name, **kwargs)
+ return render
Modified: z3c.pt/trunk/src/z3c/pt/template.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.py 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/template.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,5 +1,6 @@
import os
import sys
+import macro
import codegen
import traceback
@@ -10,6 +11,7 @@
class BaseTemplate(object):
registry = {}
+ cachedir = None
default_expression = 'python'
def __init__(self, body, default_expression=None):
@@ -24,38 +26,46 @@
def translate(self):
return NotImplementedError("Must be implemented by subclass.")
- def source_write(self):
- # Hook for writing out the source code to the file system
- return
+ @property
+ def macros(self):
+ return macro.Macros(self.render)
- def cook(self, params):
+ def cook(self, params, macro=None):
generator = self.translate(
- self.body, params=params, default_expression=self.default_expression)
+ self.body, macro=macro, params=params,
+ default_expression=self.default_expression)
source, _globals = generator()
suite = codegen.Suite(source)
+ if self.cachedir:
+ self.registry.store(params, suite.code)
+
self.source = source
- self.source_write()
self.selectors = generator.stream.selectors
self.annotations = generator.stream.annotations
_globals.update(suite._globals)
- _locals = {}
+ return self.execute(suite.code, _globals)
- exec suite.code in _globals, _locals
-
- return _locals['render']
-
- def render(self, **kwargs):
- # a ''.join of a dict uses only the keys
- signature = self.signature + hash(''.join(kwargs))
-
+ def cook_check(self, macro, params):
+ signature = self.signature, macro, params
template = self.registry.get(signature, None)
if template is None:
- self.registry[signature] = template = self.cook(kwargs.keys())
+ template = self.cook(params, macro=macro)
+ self.registry[signature] = template
+
+ return template
+
+ def execute(self, code, _globals):
+ _locals = {}
+ exec code in _globals, _locals
+ return _locals['render']
+ def render(self, macro=None, **kwargs):
+ template = self.cook_check(macro, tuple(kwargs))
+
# pass in selectors
kwargs.update(self.selectors)
@@ -101,8 +111,8 @@
class BaseTemplateFile(BaseTemplate):
- def __init__(self, filename, auto_reload=False, cachedir=None):
- BaseTemplate.__init__(self, None)
+ def __init__(self, filename, auto_reload=False, cachedir=None, **kwargs):
+ BaseTemplate.__init__(self, None, **kwargs)
self.auto_reload = auto_reload
self.cachedir = cachedir
@@ -133,6 +143,8 @@
else:
self.registry = {}
+ self.read()
+
def _get_filename(self):
return getattr(self, '_filename', None)
@@ -142,16 +154,21 @@
filename = property(_get_filename, _set_filename)
- @property
- def source_filename(self):
- return "%s.source" % self.filename
+ def _get_source(self):
+ return self._source
- def source_write(self):
- if DEBUG_MODE and self.source_filename:
- fs = open(self.source_filename, 'w')
- fs.write(self.source)
+ def _set_source(self, source):
+ self._source = source
+
+ # write source to disk
+ filename = "%s.source" % self.filename
+ if DEBUG_MODE:
+ fs = open(filename, 'w')
+ fs.write(source)
fs.close()
+ source = property(_get_source, _set_source)
+
def read(self):
fd = open(self.filename, 'r')
self.body = body = fd.read()
@@ -159,60 +176,21 @@
self.signature = hash(body)
self._v_last_read = self.mtime()
- def cook(self, params):
- if self.body is None:
- self.read()
-
- generator = self.translate(
- self.body, params=params, default_expression=self.default_expression)
-
- source, _globals = generator()
-
- suite = codegen.Suite(source)
-
- self.source = source
- self.source_write()
- self.annotations = generator.stream.annotations
-
- _globals.update(suite._globals)
- if self.cachedir:
- self.registry.store(params, suite.code)
-
- return self.execute(suite.code, _globals)
-
def execute(self, code, _globals=None):
# TODO: This is evil. We need a better way to get all the globals
# independent from the generation step
if _globals is None:
_globals = codegen.Lookup.globals()
_globals['generation'] = z3c.pt.generation
- _locals = {}
- exec code in _globals, _locals
- return _locals['render']
- def render(self, **kwargs):
- if self._cook_check():
+ return BaseTemplate.execute(self, code, _globals)
+
+ def cook_check(self, *args):
+ if self.auto_reload and self._v_last_read != self.mtime():
self.read()
- signature = hash(''.join(kwargs))
- template = self.registry.get(signature, None)
- if template is None:
- self.registry[signature] = template = self.cook(kwargs.keys())
+ return BaseTemplate.cook_check(self, *args)
- if PROD_MODE:
- return template(**kwargs)
-
- return self.safe_render(template, **kwargs)
-
- def _cook_check(self):
- if self._v_last_read and not self.auto_reload:
- return False
-
- if self.mtime() == self._v_last_read:
- return False
-
- return True
-
def mtime(self):
try:
return os.path.getmtime(self.filename)
Modified: z3c.pt/trunk/src/z3c/pt/template.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.txt 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/template.txt 2008-08-09 16:18:54 UTC (rev 89579)
@@ -121,6 +121,36 @@
<BLANKLINE>
</div>
+metal:define-macro, metal:use-macro
+
+ >>> template1 = PageTemplate("""\
+ ... <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, ${name}!
+ ... </div>
+ ... </div>""")
+
+ >>> template2 = PageTemplate("""\
+ ... <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 tal:define="name 'world'">
+ ... <div metal:use-macro="template1.macros['greeting']" />
+ ... </div>
+ ... </div>""")
+
+ >>> print template2(template1=template1)
+ <div>
+ <div>
+ <div>
+ Hello, world!
+ </div>
+ <BLANKLINE>
+ </div>
+ </div>
+
Error handling
--------------
Modified: z3c.pt/trunk/src/z3c/pt/testing.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/testing.py 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/testing.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,4 +1,33 @@
import expressions
+import macro
def pyexp(string):
return expressions.PythonTranslation.expression(string)
+
+def cook(generator, **kwargs):
+ source, _globals = generator()
+ _locals = {}
+ 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)
+
+class MockTemplate(object):
+ def __init__(self, body, translator):
+ self.body = body
+ self.translator = translator
+
+ @property
+ def macros(self):
+ def render(macro=None, **kwargs):
+ generator = self.translator(
+ self.body, macro=macro, params=kwargs.keys())
+ return _render(generator, **kwargs)
+ return macro.Macros(render)
Modified: z3c.pt/trunk/src/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.py 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/translation.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -7,6 +7,7 @@
import clauses
import interfaces
import expressions
+import itertools
import types
import utils
import config
@@ -18,9 +19,9 @@
compilation at this node, use the ``start`` method, providing a
code stream object.
"""
+
+ metal_slot_prefix = '_fill'
- _stream = None
-
def start(self, stream):
self._stream = stream
self.visit()
@@ -38,6 +39,7 @@
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:
@@ -49,8 +51,9 @@
def visit(self, skip_macro=True):
assert self.stream is not None, "Must use ``start`` method."
-
- if skip_macro and self.py_def is not None:
+
+ macro = self.py_def or self.metal_define
+ if skip_macro and macro is not None:
return
for element in self:
@@ -64,9 +67,14 @@
@property
def stream(self):
- root = self.getroottree().getroot()
- return root._stream
-
+ while self is not None:
+ try:
+ return self._stream
+ except AttributeError:
+ self = self.getparent()
+
+ raise ValueError("Can't locate stream object.")
+
@property
def translator(self):
while self.tal_default_expression is None:
@@ -209,28 +217,29 @@
_.append(clauses.Define(
"_domain", types.value(repr(self.i18n_domain))))
- # defines
+ # variable definitions
if self._define is not None:
for variables, expression in self._define:
_.append(clauses.Define(variables, expression))
- # macro
+ # genshi macro
for element in tuple(self):
if not isinstance(element, Element):
continue
-
- py_def = element.py_def
- if py_def is not None:
+
+ macro = element.py_def
+ if macro is not None:
# define macro
subclauses = []
- subclauses.append(clauses.Method("_macro", py_def.args))
+ subclauses.append(clauses.Method(
+ "_macro", macro.args))
subclauses.append(clauses.Visit(element))
_.append(clauses.Group(subclauses))
# assign to variable
_.append(clauses.Define(
- py_def.name, types.parts((types.value("_macro"),))))
-
+ macro.name, types.parts((types.value("_macro"),))))
+
# condition
if self._condition is not None:
_.append(clauses.Condition(self._condition))
@@ -245,16 +254,24 @@
_.append(clauses.Repeat(variables[0], expression))
# tag tail (deferred)
- if self.tail:
+ if self.tail and not self.metal_fillslot:
_.append(clauses.Out(self.tail.encode('utf-8'), defer=True))
+ # dynamic content and content translation
+ replace = self._replace
+ content = self._content
+
+ if self.metal_defineslot:
+ # check if slot has been filled
+ variable = self.metal_slot_prefix+self.metal_defineslot
+ if variable in itertools.chain(*self.stream.scope):
+ content = types.value(variable)
+
# compute dynamic flag
- dynamic = (self._replace or
- self._content or
- self.i18n_translate is not None)
-
+ dynamic = replace or content or self.i18n_translate is not None
+
# tag
- if self._replace is None:
+ if replace is None:
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)
@@ -262,7 +279,8 @@
if self._omit:
_.append(clauses.Condition(_not(self._omit), [tag],
finalize=False))
- elif self._omit is not None:
+ elif self._omit is not None or \
+ self.metal_use or self.metal_fillslot:
pass
else:
_.append(tag)
@@ -271,23 +289,47 @@
if self.text and not dynamic:
_.append(clauses.Out(self.text.encode('utf-8')))
- # dynamic content and content translation
- replace = self._replace
- content = self._content
-
if replace and content:
raise ValueError("Can't use replace clause together with "
"content clause.")
- expression = replace or content
- if expression:
+ if 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.")
_.append(clauses.Translate())
- _.append(clauses.Write(expression))
+ _.append(clauses.Write(replace or content))
+ elif self.metal_use:
+ # 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
+ kwargs.append((variable, variable))
+
+ 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()'), variable))
+ _.append(clauses.Group(subclauses))
+
+ _.append(clauses.Assign(self.metal_use, '_metal'))
+
+ # compute macro function arguments and create argument string
+ arguments = ", ".join(
+ tuple("%s=%s" % (arg, arg) for arg in \
+ 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
@@ -493,6 +535,14 @@
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(
@@ -591,19 +641,36 @@
def translate_xml(body, *args, **kwargs):
tree = lxml.etree.parse(StringIO(body), parser)
root = tree.getroot()
+
return translate_etree(root, *args, **kwargs)
-def translate_etree(root, params=[], default_expression='python'):
+def translate_etree(root, macro=None ,params=[], default_expression='python'):
if None not in root.nsmap:
raise ValueError, "Must set default namespace."
+ # skip to macro
+ if macro is not None:
+ elements = root.xpath(
+ './/*[@metal:define-macro="%s"]' % macro,
+ namespaces={'metal': config.METAL_NS})
+
+ if not elements:
+ raise ValueError("Macro not found: %s." % macro)
+
+ root = elements[0]
+ del root.attrib[utils.metal_attr('define-macro')]
+
# set default expression name
key = utils.tal_attr('default-expression')
if key not in root.attrib:
root.attrib[key] = default_expression
# set up code generation stream
- generator = generation.Generator(params)
+ if macro is not None:
+ wrapper = generation.macro_wrapper
+ else:
+ wrapper = generation.template_wrapper
+ generator = generation.Generator(params, wrapper)
stream = generator.stream
# output doctype if any
Modified: z3c.pt/trunk/src/z3c/pt/translation.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.txt 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/translation.txt 2008-08-09 16:18:54 UTC (rev 89579)
@@ -3,16 +3,7 @@
This document contains functional template tests.
-A generic render-method is used for convenience.
-
- >>> def render(body, translator, **kwargs):
- ... generator = translator(body)
- ... source, _globals = generator()
- ... _locals = {}
- ... _globals.update(kwargs)
- ... exec source in _globals, _locals
- ... return _locals['render'](**generator.stream.selectors)
-
+ >>> from z3c.pt.testing import render
>>> from z3c.pt.translation import translate_xml
XHTML
@@ -104,11 +95,13 @@
... 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>
@@ -283,6 +276,75 @@
<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
------
Modified: z3c.pt/trunk/src/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/utils.py 2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/utils.py 2008-08-09 16:18:54 UTC (rev 89579)
@@ -106,6 +106,9 @@
def tal_attr(name):
return "{%s}%s" % (config.TAL_NS, name)
+def metal_attr(name):
+ return "{%s}%s" % (config.METAL_NS, name)
+
def i18n_attr(name):
return "{%s}%s" % (config.I18N_NS, name)
More information about the Checkins
mailing list