[Checkins] SVN: z3c.pt/trunk/ Formalized expression type
definitions,
adding support for nested expressions (needed to properly support
string-expressions). Fixed path- and string-expression
support. Many added tests and general refactoring.
Malthe Borch
mborch at gmail.com
Tue Mar 18 20:42:06 EDT 2008
Log message for revision 84765:
Formalized expression type definitions, adding support for nested expressions (needed to properly support string-expressions). Fixed path- and string-expression support. Many added tests and general refactoring.
Changed:
U z3c.pt/trunk/README.txt
U z3c.pt/trunk/docs/HISTORY.txt
U z3c.pt/trunk/z3c/pt/clauses.py
U z3c.pt/trunk/z3c/pt/configure.zcml
U z3c.pt/trunk/z3c/pt/expressions.py
U z3c.pt/trunk/z3c/pt/generation.py
U z3c.pt/trunk/z3c/pt/testing.py
U z3c.pt/trunk/z3c/pt/translation.py
U z3c.pt/trunk/z3c/pt/translation.txt
A z3c.pt/trunk/z3c/pt/types.py
U z3c.pt/trunk/z3c/pt/utils.py
-=-
Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/README.txt 2008-03-19 00:42:03 UTC (rev 84765)
@@ -5,13 +5,13 @@
template language including i18n. It also provides a simple text
template class that allows expression interpolation.
-Casual benchmarks pegs it 12x more performant than ``zope.pagetemplate``.
+Casual benchmarks pegs it 11x more performant than ``zope.pagetemplate``.
In a nutshell:
* Templates are bytecode-compiled
-* Supports Python and Zope path traversal expressions
-* Introduces expression interpolation using the ${<expression>}-format
+* Pluggable expression implementation
+* Support for expression interpolation using the ${<expression>}-format
* Non-XML friendly
See the README.txt inside the package for instructions on usage.
@@ -34,22 +34,18 @@
tal:repeat="i <some generator>"
-3. Attribute-access to dictionary entries is allowed, e.g.
+3. Attribute-access to dictionary entries is allowed in
+ Python-expressions, e.g.
dictionary.key
can be used instead of ``dictionary['key']``.
-4. Expressions that return a callable are called.
+4. Expression interpolation is allowed in attributes and HTML content.
-5. Expression interpolation is allowed:
-
<a href="mailto:${context.email}">${context.email}</a>
-6. Attribute-values are always escaped; document expressions are
- never.
-
-7. Default expression type can be set using ``tal:default-expression``.
+5. Default expression type can be set using ``tal:default-expression``.
This is an alternative to providing the expression type before each
expression.
@@ -59,7 +55,8 @@
Development
-----------
-If you want to use the code directly from trunk, provide
-``z3c.pt==dev`` as your dependency.
+If you want to use the code directly from trunk (recommended only for
+development and testing usage), provide ``z3c.pt==dev`` as your
+dependency.
http://svn.zope.org/z3c.pt/trunk#egg=z3c.pt-dev
Modified: z3c.pt/trunk/docs/HISTORY.txt
===================================================================
--- z3c.pt/trunk/docs/HISTORY.txt 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/docs/HISTORY.txt 2008-03-19 00:42:03 UTC (rev 84765)
@@ -4,14 +4,21 @@
Version 0.8dev
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Result of 'replace' and 'content' is now escaped by default.
+- Added support for 'nocall' and 'not' (for path-expressions).
-- Added support for 'nocall', 'not' and 'structure'-pragmas.
+- Added support for path- and string-expressions.
-- Added support for path-expressions.
+- Abstracted expression translation engine. Expression implementations
+ are now pluggable. Expression name pragmas are supported throughout.
-- Abstracted expression translation engine.
+- Formalized expression types
+- Added support for 'structure'-keyword for replace and content.
+
+- Result of 'replace' and 'content' is now escaped by default.
+
+- Benchmark is now built as a custom testrunner
+
Version 0.7 - March 10, 2008
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Modified: z3c.pt/trunk/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/z3c/pt/clauses.py 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/clauses.py 2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,60 +1,146 @@
+# -*- coding: utf-8 -*-
+
from utils import unicode_required_flag
from cgi import escape
+import types
+
class Assign(object):
"""
- >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
- >>> from z3c.pt.testing import value
+ >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
+ >>> from z3c.pt.testing import pyexp
- Simple assignment:
+ We'll define some values for use in the tests.
+
+ >>> one = types.value("1")
+ >>> bad_float = types.value("float('abc')")
+ >>> abc = types.value("'abc'")
+ >>> ghi = types.value("'ghi'")
+ >>> utf8_encoded = types.value("'La Peña'")
+ >>> exclamation = types.value("'!'")
+
+ Simple value assignment:
+
+ >>> assign = Assign(one)
+ >>> assign.begin(stream, 'a')
+ >>> exec stream.getvalue()
+ >>> a == 1
+ True
+ >>> assign.end(stream)
+
+ Try-except parts (bad, good):
+
+ >>> assign = Assign(types.parts((bad_float, one)))
+ >>> assign.begin(stream, 'b')
+ >>> exec stream.getvalue()
+ >>> b == 1
+ True
+ >>> assign.end(stream)
+
+ Try-except parts (good, bad):
+
+ >>> assign = Assign(types.parts((one, bad_float)))
+ >>> assign.begin(stream, 'b')
+ >>> exec stream.getvalue()
+ >>> b == 1
+ True
+ >>> assign.end(stream)
+
+ Join:
- >>> assign = Assign(value("1"))
- >>> assign.begin(stream, 'a')
- >>> exec stream.getvalue()
- >>> a == 1
- True
- >>> assign.end(stream)
+ >>> assign = Assign(types.join((abc, ghi)))
+ >>> assign.begin(stream, 'b')
+ >>> exec stream.getvalue()
+ >>> b == 'abcghi'
+ True
+ >>> assign.end(stream)
- Try-except chain:
+ Join with try-except parts:
+
+ >>> assign = Assign(types.join((types.parts((bad_float, abc, ghi)), ghi)))
+ >>> assign.begin(stream, 'b')
+ >>> exec stream.getvalue()
+ >>> b == 'abcghi'
+ True
+ >>> assign.end(stream)
- >>> assign = Assign(value("float('abc') | 1"))
- >>> assign.begin(stream, 'b')
- >>> exec stream.getvalue()
- >>> b == 1
- True
- >>> assign.end(stream)
+ UTF-8 coercing:
- Try-except chain part 2:
+ >>> assign = Assign(types.join((utf8_encoded, exclamation)))
+ >>> assign.begin(stream, 'b')
+ >>> exec stream.getvalue()
+ >>> b == 'La Peña!'
+ True
+ >>> assign.end(stream)
- >>> assign = Assign(value("'abc' | 1"))
- >>> assign.begin(stream, 'b')
- >>> exec stream.getvalue()
- >>> b == 'abc'
- True
- >>> assign.end(stream)
- """
+ UTF-8 coercing with unicode:
- def __init__(self, expressions, variable=None):
- self.expressions = expressions
+ >>> assign = Assign(types.join((utf8_encoded, u"!")))
+ >>> assign.begin(stream, 'b')
+ >>> exec stream.getvalue()
+ >>> b == 'La Peña!'
+ True
+ >>> assign.end(stream)
+
+ """
+
+ def __init__(self, parts, variable=None):
+ if not isinstance(parts, types.parts):
+ parts = types.parts((parts,))
+
+ self.parts = parts
self.variable = variable
-
+
def begin(self, stream, variable=None):
"""First n - 1 expressions must be try-except wrapped."""
variable = variable or self.variable
-
- for expression in self.expressions[:-1]:
+
+ for value in self.parts[:-1]:
stream.write("try:")
stream.indent()
- stream.write("%s = %s" % (variable, expression))
+
+ self._assign(variable, value, stream)
+
stream.outdent()
stream.write("except Exception, e:")
stream.indent()
- expression = self.expressions[-1]
- stream.write("%s = %s" % (variable, expression))
+ value = self.parts[-1]
+ self._assign(variable, value, stream)
+
+ stream.outdent(len(self.parts)-1)
- stream.outdent(len(self.expressions)-1)
+ def _assign(self, variable, value, stream):
+ if isinstance(value, types.value):
+ stream.write("%s = %s" % (variable, value))
+ elif isinstance(value, types.join):
+ parts = []
+ _v_count = 0
+
+ for part in value:
+ if isinstance(part, (types.parts, types.join)):
+ _v = stream.save()
+ assign = Assign(part, _v)
+ assign.begin(stream)
+ assign.end(stream)
+ _v_count +=1
+ parts.append(_v)
+ elif isinstance(part, types.value):
+ parts.append(part)
+ elif isinstance(part, unicode):
+ parts.append(repr(part.encode('utf-8')))
+ elif isinstance(part, str):
+ parts.append(repr(part))
+ else:
+ raise ValueError("Not able to handle %s" % type(part))
+
+ format = "%s"*len(parts)
+
+ stream.write("%s = '%s' %% (%s)" % (variable, format, ",".join(parts)))
+
+ for i in range(_v_count):
+ stream.restore()
def end(self, stream):
pass
@@ -62,11 +148,11 @@
class Define(object):
"""
>>> from z3c.pt.generation import CodeIO; stream = CodeIO()
- >>> from z3c.pt.testing import value
+ >>> from z3c.pt.testing import pyexp
Variable scope:
- >>> define = Define("a", value("b"))
+ >>> define = Define("a", pyexp("b"))
>>> b = object()
>>> define.begin(stream)
>>> exec stream.getvalue()
@@ -85,8 +171,8 @@
Multiple defines:
>>> stream = CodeIO()
- >>> define1 = Define("a", value("b"))
- >>> define2 = Define("c", value("d"))
+ >>> define1 = Define("a", pyexp("b"))
+ >>> define2 = Define("c", pyexp("d"))
>>> d = object()
>>> define1.begin(stream)
>>> define2.begin(stream)
@@ -112,7 +198,7 @@
Tuple assignments:
>>> stream = CodeIO()
- >>> define = Define(['e', 'f'], value("[1, 2]"))
+ >>> define = Define(['e', 'f'], pyexp("[1, 2]"))
>>> define.begin(stream)
>>> exec stream.getvalue()
>>> e == 1 and f == 2
@@ -134,7 +220,7 @@
Using semicolons in expressions within a define:
>>> stream = CodeIO()
- >>> define = Define("a", value("';'"))
+ >>> define = Define("a", pyexp("';'"))
>>> define.begin(stream)
>>> exec stream.getvalue()
>>> a
@@ -147,7 +233,7 @@
>>> a = 1
>>> stream.scope[-1].add('a')
>>> stream.scope.append(set())
- >>> define = Define("a", value("2"))
+ >>> define = Define("a", pyexp("2"))
>>> define.begin(stream)
>>> define.end(stream)
>>> exec stream.getvalue()
@@ -209,14 +295,14 @@
class Condition(object):
"""
>>> from z3c.pt.generation import CodeIO
- >>> from z3c.pt.testing import value
+ >>> from z3c.pt.testing import pyexp
>>> from cgi import escape as _escape
Unlimited scope:
>>> stream = CodeIO()
- >>> true = Condition(value("True"))
- >>> false = Condition(value("False"))
+ >>> true = Condition(pyexp("True"))
+ >>> false = Condition(pyexp("False"))
>>> true.begin(stream)
>>> stream.write("print 'Hello'")
>>> true.end(stream)
@@ -233,8 +319,8 @@
>>> stream = CodeIO()
>>> from StringIO import StringIO
>>> _out = StringIO()
- >>> true = Condition(value("True"), [Write(value("'Hello'"))])
- >>> false = Condition(value("False"), [Write(value("'Hallo'"))])
+ >>> true = Condition(pyexp("True"), [Write(pyexp("'Hello'"))])
+ >>> false = Condition(pyexp("False"), [Write(pyexp("'Hallo'"))])
>>> true.begin(stream)
>>> true.end(stream)
>>> false.begin(stream)
@@ -248,8 +334,8 @@
>>> stream = CodeIO()
>>> from StringIO import StringIO
>>> _out = StringIO()
- >>> true = Condition(value("True"), [Tag('div')], finalize=False)
- >>> false = Condition(value("False"), [Tag('span')], finalize=False)
+ >>> true = Condition(pyexp("True"), [Tag('div')], finalize=False)
+ >>> false = Condition(pyexp("False"), [Tag('span')], finalize=False)
>>> true.begin(stream)
>>> stream.out("Hello World!")
>>> true.end(stream)
@@ -263,21 +349,13 @@
def __init__(self, value, clauses=None, finalize=True):
self.assign = Assign(value)
- try:
- self.inverse = value.options.get('not')
- except:
- import pdb; pdb.set_trace()
-
self.clauses = clauses
self.finalize = finalize
def begin(self, stream):
temp = stream.save()
self.assign.begin(stream, temp)
- if self.inverse:
- stream.write("if not(%s):" % temp)
- else:
- stream.write("if %s:" % temp)
+ stream.write("if %s:" % temp)
stream.indent()
if self.clauses:
for clause in self.clauses:
@@ -335,12 +413,14 @@
class Tag(object):
"""
>>> from z3c.pt.generation import CodeIO
- >>> from z3c.pt.testing import value
+ >>> from z3c.pt.testing import pyexp
>>> from StringIO import StringIO
>>> from cgi import escape as _escape
+
+ Dynamic attribute:
>>> _out = StringIO(); stream = CodeIO()
- >>> tag = Tag('div', dict(alt=value(repr('Hello World!'))))
+ >>> tag = Tag('div', dict(alt=pyexp(repr('Hello World!'))))
>>> tag.begin(stream)
>>> stream.out('Hello Universe!')
>>> tag.end(stream)
@@ -348,6 +428,8 @@
>>> _out.getvalue()
'<div alt="Hello World!">Hello Universe!</div>'
+ Self-closing tag:
+
>>> _out = StringIO(); stream = CodeIO()
>>> tag = Tag('br', {}, True)
>>> tag.begin(stream)
@@ -355,7 +437,18 @@
>>> exec stream.getvalue()
>>> _out.getvalue()
'<br />'
+
+ Unicode:
+ >>> _out = StringIO(); stream = CodeIO()
+ >>> tag = Tag('div', dict(alt=pyexp(repr('La Peña'))))
+ >>> tag.begin(stream)
+ >>> stream.out('Hello Universe!')
+ >>> tag.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue() == '<div alt="La Peña">Hello Universe!</div>'
+ True
+
"""
def __init__(self, tag, attributes={}, selfclosing=False):
@@ -374,13 +467,13 @@
# static attributes
static = filter(
- lambda (attribute, expression): \
- not isinstance(expression, (tuple, list)),
+ lambda (attribute, value): \
+ not isinstance(value, types.expression),
self.attributes.items())
dynamic = filter(
- lambda (attribute, expression): \
- isinstance(expression, (tuple, list)),
+ lambda (attribute, value): \
+ isinstance(value, types.expression),
self.attributes.items())
for attribute, expression in static:
@@ -389,7 +482,7 @@
escape(expression, '"')))
temp = stream.save()
-
+
for attribute, value in dynamic:
assign = Assign(value)
assign.begin(stream, temp)
@@ -398,9 +491,9 @@
stream.write("if %s is not None:" % temp)
stream.indent()
- if not value.options.get('nocall'):
- # if callable, evaluate method
- stream.write("if callable(%s): %s = %s()" % (temp, temp, temp))
+ #if not value.options.get('nocall'):
+ # # if callable, evaluate method
+ # stream.write("if callable(%s): %s = %s()" % (temp, temp, temp))
if unicode_required_flag:
stream.write("if isinstance(%s, unicode):" % temp)
@@ -435,7 +528,7 @@
class Repeat(object):
"""
>>> from z3c.pt.generation import CodeIO; stream = CodeIO()
- >>> from z3c.pt.testing import value
+ >>> from z3c.pt.testing import pyexp
We need to set up the repeat object.
@@ -444,7 +537,7 @@
Simple repeat loop and repeat data structure:
- >>> _repeat = Repeat("i", value("range(5)"))
+ >>> _repeat = Repeat("i", pyexp("range(5)"))
>>> _repeat.begin(stream)
>>> stream.write("r = repeat['i']")
>>> stream.write("print (i, r.index, r.start, r.end, r.number(), r.odd(), r.even())")
@@ -492,46 +585,65 @@
class Write(object):
"""
- >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
- >>> from z3c.pt.testing import value
- >>> from StringIO import StringIO
- >>> from cgi import escape as _escape
-
- >>> _out = StringIO()
- >>> write = Write(value("'New York'"))
- >>> write.begin(stream)
- >>> write.end(stream)
- >>> exec stream.getvalue()
- >>> _out.getvalue()
- 'New York'
- >>> _out = StringIO()
- >>> write = Write(value("undefined | ', New York!'"))
- >>> write.begin(stream)
- >>> write.end(stream)
- >>> exec stream.getvalue()
- >>> _out.getvalue()
- 'New York, New York!'
+ >>> from z3c.pt.generation import CodeIO; stream = CodeIO()
+ >>> from z3c.pt.testing import pyexp
+ >>> from StringIO import StringIO
+ >>> from cgi import escape as _escape
+
+ Basic write:
+
+ >>> _out = StringIO()
+ >>> write = Write(pyexp("'New York'"))
+ >>> write.begin(stream)
+ >>> write.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue()
+ 'New York'
+
+ Try-except parts:
+
+ >>> stream = CodeIO()
+ >>> _out = StringIO()
+ >>> write = Write(pyexp("undefined | 'New Delhi'"))
+ >>> write.begin(stream)
+ >>> write.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue()
+ 'New Delhi'
+
+ Unicode:
+
+ >>> stream = CodeIO()
+ >>> _out = StringIO()
+ >>> write = Write(types.value("unicode('La Pe\xc3\xb1a', 'utf-8')"))
+ >>> write.begin(stream)
+ >>> write.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue() == 'La Pe\xc3\xb1a'
+ True
+
"""
+
+ value = assign = None
def __init__(self, value):
- self.assign = Assign(value)
- self.value = value
- self.count = len(value)
+ if isinstance(value, types.parts):
+ self.assign = Assign(value)
+ else:
+ self.value = value
+
+ self.structure = not isinstance(value, types.escape)
def begin(self, stream):
temp = stream.save()
- if self.count == 1:
- expr = self.value[0]
+ if self.value:
+ expr = self.value
else:
self.assign.begin(stream, temp)
expr = temp
stream.write("_urf = %s" % expr)
-
- if not self.value.options.get('nocall'):
- stream.write("if callable(_urf): _urf = _urf()")
-
stream.write("if _urf is None: _urf = ''")
if unicode_required_flag:
@@ -541,26 +653,26 @@
stream.outdent()
stream.write("else:")
stream.indent()
- if self.value.options.get('structure'):
+ if self.structure:
stream.write("_out.write(str(_urf))")
else:
stream.write("_out.write(_escape(str(_urf)))")
stream.outdent()
else:
- if self.value.options.get('structure'):
+ if self.structure:
stream.write("_out.write(str(_urf))")
else:
stream.write("_out.write(_escape(str(_urf)))")
def end(self, stream):
- if self.count != 1:
+ if self.assign:
self.assign.end(stream)
stream.restore()
class Out(object):
"""
>>> from z3c.pt.generation import CodeIO; stream = CodeIO()
- >>> from z3c.pt.testing import value
+ >>> from z3c.pt.testing import pyexp
>>> from StringIO import StringIO
>>> _out = StringIO()
Modified: z3c.pt/trunk/z3c/pt/configure.zcml
===================================================================
--- z3c.pt/trunk/z3c/pt/configure.zcml 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/configure.zcml 2008-03-19 00:42:03 UTC (rev 84765)
@@ -10,5 +10,9 @@
name="path"
factory=".expressions.PathTranslation" />
+ <adapter
+ name="string"
+ factory=".expressions.StringTranslation" />
+
</configure>
Modified: z3c.pt/trunk/z3c/pt/expressions.py
===================================================================
--- z3c.pt/trunk/z3c/pt/expressions.py 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/expressions.py 2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
import zope.interface
import zope.component
import zope.traversing.adapters
@@ -7,27 +9,26 @@
import re
from interfaces import IExpressionTranslation
-from utils import value
+import types
+
class ExpressionTranslation(object):
zope.interface.implements(IExpressionTranslation)
- pragma = re.compile(r'^(?P<pragma>[a-z]+):\s*')
-
+ re_pragma = re.compile(r'^\s*(?P<pragma>[a-z]+):\s*')
+ re_interpolation = re.compile(r'(?P<prefix>[^\\]\$|^\$){((?P<expression>.*)})?')
+
def name(self, string):
return string
- def value(self, string):
- return NotImplementedError("Must be implemented by subclass.")
-
def search(self, string):
"""
- We need to implement a ``value``-method. Let's define that an
+ We need to implement a ``validate``-method. Let's define that an
expression is valid if it contains an odd number of
characters.
>>> class MockExpressionTranslation(ExpressionTranslation):
- ... def value(self, string):
+ ... def validate(self, string):
... if len(string) % 2 == 0: raise SyntaxError()
>>> search = MockExpressionTranslation().search
@@ -49,7 +50,7 @@
expression = string[left:right]
try:
- e = self.value(expression)
+ e = self.validate(expression)
current = expression
except SyntaxError, e:
if right == len(string):
@@ -62,19 +63,19 @@
return current
- def variable(self, string):
+ def declaration(self, string):
"""
- >>> variable = ExpressionTranslation().variable
+ >>> declaration = ExpressionTranslation().declaration
Single variable:
- >>> variable("variable")
- ('variable',)
+ >>> declaration("variable")
+ declaration('variable',)
Multiple variables:
- >>> variable("variable1, variable2")
- ('variable1', 'variable2')
+ >>> declaration("variable1, variable2")
+ declaration('variable1', 'variable2')
"""
variables = []
@@ -90,20 +91,20 @@
variables.append(var)
- return tuple(variables)
+ return types.declaration(variables)
def mapping(self, string):
"""
>>> mapping = ExpressionTranslation().mapping
>>> mapping("abc def")
- [('abc', 'def')]
+ mapping(('abc', 'def'),)
>>> mapping("abc")
- [('abc', None)]
+ mapping(('abc', None),)
>>> mapping("abc; def ghi")
- [('abc', None), ('def', 'ghi')]
+ mapping(('abc', None), ('def', 'ghi'))
"""
@@ -122,49 +123,50 @@
else:
raise ValueError, "Invalid mapping (%s)." % string
- return mappings
+ return types.mapping(mappings)
def definitions(self, string):
"""
- We need to subclass the parser and implement a simple
- ``value``-method.
-
- >>> class MockExpressionTranslation(ExpressionTranslation):
- ... def value(self, string):
- ... return (string.strip(),)
-
- >>> definitions = MockExpressionTranslation().definitions
-
+
+ >>> class MockExpressionTranslation(ExpressionTranslation):
+ ... def expression(self, string):
+ ... return types.value(string.strip())
+
+ >>> definitions = MockExpressionTranslation().definitions
+
Single define:
-
- >>> definitions("variable expression")
- ((['variable'], ('expression',)),)
-
+
+ >>> definitions("variable expression")
+ definitions((declaration('variable',), value('expression')),)
+
Multiple defines:
-
- >>> definitions("variable1 expression1; variable2 expression2")
- ((['variable1'], ('expression1',)), (['variable2'], ('expression2',)))
-
+
+ >>> definitions("variable1 expression1; variable2 expression2")
+ definitions((declaration('variable1',), value('expression1')),
+ (declaration('variable2',), value('expression2')))
+
Tuple define:
-
- >>> definitions("(variable1, variable2) (expression1, expression2)")
- ((['variable1', 'variable2'], ('(expression1, expression2)',)),)
-
+
+ >>> definitions("(variable1, variable2) (expression1, expression2)")
+ definitions((declaration('variable1', 'variable2'),
+ value('(expression1, expression2)')),)
+
A define clause that ends in a semicolon:
-
- >>> definitions("variable expression;")
- ((['variable'], ('expression',)),)
-
+
+ >>> definitions("variable expression;")
+ definitions((declaration('variable',), value('expression')),)
+
A define clause with a trivial expression (we do allow this):
-
- >>> definitions("variable")
- ((['variable'], None),)
-
+
+ >>> definitions("variable")
+ definitions((declaration('variable',), None),)
+
A proper define clause following one with a trivial expression:
+
+ >>> definitions("variable1 expression; variable2")
+ definitions((declaration('variable1',), value('expression')),
+ (declaration('variable2',), None))
- >>> definitions("variable1 expression; variable2")
- ((['variable1'], ('expression',)), (['variable2'], None))
-
"""
string = string.replace('\n', '').strip()
@@ -181,15 +183,15 @@
j = string.find(')', i+1)
if j == -1:
raise ValueError, "Invalid variable tuple definition (%s)." % string
- var = self.variable(string[i+1:j])
+ var = self.declaration(string[i+1:j])
j += 1
else:
j = string.find(' ', i + 1)
if j == -1:
- var = self.variable(string[i:])
+ var = self.declaration(string[i:])
j = len(string)
else:
- var = self.variable(string[i:j])
+ var = self.declaration(string[i:j])
# get expression
i = j
@@ -199,20 +201,21 @@
j = len(string)
try:
- expr = self.value(string[i:j])
+ expr = self.expression(string[i:j])
except SyntaxError, e:
if j < len(string):
continue
+
raise e
break
else:
expr = None
- defines.append((list(var), expr))
+ defines.append((var, expr))
i = j + 1
- return tuple(defines)
+ return types.definitions(defines)
def definition(self, string):
defs = self.definitions(string)
@@ -221,63 +224,90 @@
return defs[0]
- def value(self, string):
+ def output(self, string):
"""
- We need to implement a ``value``-method. Let's define that an
- expression is valid if it contains an odd number of
- characters.
-
>>> class MockExpressionTranslation(ExpressionTranslation):
... def validate(self, string):
... return True
...
... def translate(self, string):
- ... return '<translated %s>' % string
+ ... return types.value(string)
+
+ >>> output = MockExpressionTranslation().output
+
+ >>> output("context/title")
+ escape(value('context/title'),)
+
+ >>> output("context/pretty_title_or_id|context/title")
+ escape(value('context/pretty_title_or_id'), value('context/title'))
+
+ >>> output("structure context/title")
+ value('context/title')
- >>> value = MockExpressionTranslation().value
+ """
+
+ if string.startswith('structure '):
+ return self.expression(string[len('structure'):])
+
+ expression = self.expression(string)
- >>> value('a')
- ('<translated a>',)
+ if isinstance(expression, types.parts):
+ return types.escape(expression)
- >>> value('a|b')
- ('<translated a>', '<translated b>')
+ return types.escape((expression,))
+
+ def expression(self, string):
+ """We need to implement the ``validate`` and
+ ``translate``-methods. Let's define that an expression is
+ valid if it contains an odd number of characters.
+
+ >>> class MockExpressionTranslation(ExpressionTranslation):
+ ... def validate(self, string):
+ ... return True
+ ...
+ ... def translate(self, string):
+ ... return types.value(string)
+
+ >>> expression = MockExpressionTranslation().expression
+
+ >>> expression('a')
+ value('a')
+
+ >>> expression('a|b')
+ parts(value('a'), value('b'))
"""
+ if string is None:
+ import pdb; pdb.set_trace()
+
string = string.replace('\n', '').strip()
if not string:
- return []
+ return types.parts()
- expressions = []
+ parts = []
- # reset pragmas
+ # default translator is ``self``
translator = self
- options = {'nocall': False,
- 'structure': False,
- 'not': False}
-
i = j = 0
while i < len(string):
- match = self.pragma.match(string[i:])
- if match is not None:
- pragma = match.group('pragma').lower()
+ if translator is self:
+ match = self.re_pragma.match(string[i:])
+ if match is not None:
+ pragma = match.group('pragma')
- utility = zope.component.queryUtility(
- IExpressionTranslation, name=pragma)
-
- if utility is not None:
- translator = utility
- pragma = None
+ translator = \
+ zope.component.queryUtility(
+ IExpressionTranslation, name=pragma) or \
+ zope.component.queryAdapter(
+ self, IExpressionTranslation, name=pragma) or \
+ self
- if pragma in options:
- options[pragma] = True
- pragma = None
-
- if pragma is None:
- i += match.end()
- continue
+ if translator is not self:
+ i += match.end()
+ continue
j = string.find('|', j + 1)
if j == -1:
@@ -293,13 +323,64 @@
raise e
- expressions.append(translator.translate(expr))
+ value = translator.translate(expr)
+ parts.append(value)
translator = self
i = j + 1
- return value(options, expressions)
+ if len(parts) == 1:
+ return parts[0]
+ return types.parts(parts)
+
+ def interpolate(self, string):
+ """Search for an interpolation and return a match.
+
+ >>> class MockExpressionTranslation(ExpressionTranslation):
+ ... def validate(self, string):
+ ... if '}' in string: raise SyntaxError
+ ...
+ ... def translate(self, string):
+ ... return types.value(string)
+
+ >>> interpolate = MockExpressionTranslation().interpolate
+
+ >>> interpolate('${abc}').group('expression')
+ 'abc'
+
+ >>> interpolate('abc${def}').group('expression')
+ 'def'
+
+ >>> interpolate('abc${def}ghi${jkl}').group('expression')
+ 'def'
+
+ >>> interpolate('${abc')
+ Traceback (most recent call last):
+ ...
+ SyntaxError: Interpolation expressions must of the form ${<expression>} (${abc)
+
+ """
+
+ m = self.re_interpolation.search(string)
+ if m is None:
+ return None
+
+ expression = m.group('expression')
+
+ if expression:
+ left = m.start()+len(m.group('prefix'))
+ exp = self.search(string[left+1:])
+ right = left+2+len(exp)
+ m = self.re_interpolation.search(string[:right])
+
+ if expression is None or m is None:
+ raise SyntaxError(
+ "Interpolation expressions must of the "
+ "form ${<expression>} (%s)" % string)
+
+ return m
+
class PythonTranslation(ExpressionTranslation):
def validate(self, string):
"""We use the ``parser`` module to determine if
@@ -308,13 +389,87 @@
parser.expr(string.encode('utf-8'))
def translate(self, string):
- return string
+ if isinstance(string, unicode):
+ string = string.encode('utf-8')
+ return types.value(string)
+
+class StringTranslation(ExpressionTranslation):
+ zope.component.adapts(IExpressionTranslation)
+
+ def __init__(self, translator):
+ self.translator = translator
+
+ def validate(self, string):
+ self.interpolate(string)
+
+ def translate(self, string):
+ return types.join(self.split(string))
+
+ def split(self, string):
+ """Split up an interpolation string expression into parts that
+ are either unicode strings or ``value``-tuples.
+
+ >>> class MockTranslation(ExpressionTranslation):
+ ... def validate(self, string):
+ ... if '}' in string: raise SyntaxError
+ ...
+ ... def translate(self, string):
+ ... return types.value(string)
+
+ >>> class MockStringTranslation(StringTranslation):
+ ... pass
+
+ >>> split = MockStringTranslation(MockTranslation()).split
+
+ >>> split("${abc}")
+ (value('abc'),)
+
+ >>> split("abc${def}")
+ ('abc', value('def'))
+
+ >>> split("${def}abc")
+ (value('def'), 'abc')
+
+ >>> split("abc${def}ghi")
+ ('abc', value('def'), 'ghi')
+
+ >>> split("abc${def}ghi${jkl}")
+ ('abc', value('def'), 'ghi', value('jkl'))
+
+ >>> split("abc${def | ghi}")
+ ('abc', parts(value('def '), value('ghi')))
+
+ >>> print split("abc${La Peña}")
+ ('abc', value('La Pe\\xc3\\xb1a'))
+
+ """
+
+ m = self.translator.interpolate(string)
+ if m is None:
+ return (string,)
+
+ parts = []
+
+ start = m.start()
+ if start > 0:
+ text = string[:m.start()+1]
+ parts.append(text)
+
+ expression = m.group('expression')
+ parts.append(self.translator.expression(expression))
+
+ rest = string[m.end():]
+ if len(rest):
+ parts.extend(self.split(rest))
+
+ return tuple(parts)
+
class PathTranslation(ExpressionTranslation):
- path_regex = re.compile(r'^([A-Za-z_]+)(/[A-Za-z_ at -]+)+$')
+ path_regex = re.compile(r'^((nocall|not):\s*)*([A-Za-z_]+)(/[A-Za-z_ at -]+)*$')
@classmethod
- def traverse(cls, base, request, *path_items):
+ def traverse(cls, base, request, call, *path_items):
"""See ``zope.app.pagetemplate.engine``."""
for i in range(len(path_items)):
@@ -326,8 +481,11 @@
else:
base = zope.traversing.adapters.traversePathElement(
base, name, path_items[i+1:], request=request)
+
+ base = zope.security.proxy.ProxyFactory(base)
- base = zope.security.proxy.ProxyFactory(base)
+ if call and callable(base):
+ base = base()
return base
@@ -339,15 +497,52 @@
"""
>>> translate = PathTranslation().translate
>>> translate("a/b")
- "_path(a, request, 'b')"
+ value("_path(a, request, True, 'b')")
>>> translate("context/@@view")
- "_path(context, request, '@@view')"
+ value("_path(context, request, True, '@@view')")
+
+ >>> translate("nocall: context/@@view")
+ value("_path(context, request, False, '@@view')")
+
+ >>> translate("not: context/@@view")
+ value("not(_path(context, request, True, '@@view'))")
+
"""
+ nocall = False
+ negate = False
+
+ while string:
+ m = self.re_pragma.match(string)
+ if m is None:
+ break
+
+ string = string[m.end():]
+ pragma = m.group('pragma').lower()
+
+ if pragma == 'nocall':
+ nocall = True
+ elif pragma == 'not':
+ negate = True
+ else:
+ raise ValueError("Invalid pragma: %s" % pragma)
+
parts = string.split('/')
+ # map 'nothing' to 'None'
+ parts = map(lambda part: part == 'nothing' and 'None' or part, parts)
+
base = parts[0]
components = [repr(part) for part in parts[1:]]
-
- return '_path(%s, request, %s)' % (base, ', '.join(components))
+
+ if not components:
+ components = ()
+
+ value = types.value(
+ '_path(%s, request, %s, %s)' % (base, not nocall, ', '.join(components)))
+
+ if negate:
+ value = types.value('not(%s)' % value)
+
+ return value
Modified: z3c.pt/trunk/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/generation.py 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/generation.py 2008-03-19 00:42:03 UTC (rev 84765)
@@ -86,7 +86,7 @@
def out(self, string):
self.queue += string
-
+
def cook(self):
if self.queue:
queue = self.queue
@@ -95,14 +95,22 @@
queue.replace('\n', '\\n').replace("'", "\\'"))
def write(self, string):
+ if isinstance(string, str):
+ string = string.decode('utf-8')
+
self.cook()
StringIO.StringIO.write(
self, self.indentation_string * self.indentation + string + '\n')
+ try:
+ self.getvalue()
+ except UnicodeDecodeError:
+ import pdb; pdb.set_trace()
+
def getvalue(self):
self.cook()
return StringIO.StringIO.getvalue(self)
-
+
def begin(self, clauses):
if isinstance(clauses, (list, tuple)):
for clause in clauses:
Modified: z3c.pt/trunk/z3c/pt/testing.py
===================================================================
--- z3c.pt/trunk/z3c/pt/testing.py 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/testing.py 2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,5 +1,5 @@
import expressions
-def value(string):
+def pyexp(string):
translator = expressions.PythonTranslation()
- return translator.value(string)
+ return translator.expression(string)
Modified: z3c.pt/trunk/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.py 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/translation.py 2008-03-19 00:42:03 UTC (rev 84765)
@@ -2,16 +2,14 @@
from StringIO import StringIO
import lxml.etree
-import re
import generation
import expressions
import clauses
import interfaces
import utils
+import types
-interpolation_regex = re.compile(r'([^\\]\$|^\$){(?P<expression>.*)}')
-
def attribute(ns, factory=None, default=None):
def get(self):
value = self.attrib.get(ns)
@@ -29,25 +27,6 @@
return property(get, set)
-def interpolate(string, translator):
- m = interpolation_regex.search(string)
- if m is None:
- return None
-
- left = m.start()
- exp = translator.search(string[left+3:])
- right = left+4+len(exp)
-
- m = interpolation_regex.search(string[:right])
-
- if m is None:
- interpolation = string[left:right]
- raise SyntaxError(
- "Interpolation expressions must of the "
- "form ${<expression>} (%s)" % interpolation)
-
- return m
-
class Element(lxml.etree.ElementBase):
def begin(self, stream):
stream.scope.append(set())
@@ -60,23 +39,8 @@
def body(self, stream):
skip = self.replace or self.content or self.i18n_translate is not None
if not skip:
- for element in list(self):
- if isinstance(element, lxml.etree._Comment):
- index = self.index(element)
-
- t = parser.makeelement(
- '{http://xml.zope.org/namespaces/tal}comment')
-
- t.attrib['omit-tag'] = ''
- t.tail = element.tail
- t.text = '<!--' + element.text + '-->'
-
- for child in element.getchildren():
- t.append(child)
-
- self.remove(element)
- self.insert(index, t)
-
+ for element in [e for e in self if isinstance(e, lxml.etree._Comment)]:
+ self._wrap_comment(element)
for element in self:
element.interpolate(stream)
for element in self:
@@ -88,11 +52,13 @@
self.end(stream)
def interpolate(self, stream):
- # interpolate text
+ """The current interpolation strategy is to translate the
+ interpolation statements into TAL."""
+
translator = self._translator()
if self.text is not None:
while self.text:
- m = interpolate(self.text, translator)
+ m = translator.interpolate(self.text)
if m is None:
break
@@ -106,7 +72,7 @@
# interpolate tail
if self.tail is not None:
while self.tail:
- m = interpolate(self.tail, translator)
+ m = translator.interpolate(self.tail)
if m is None:
break
@@ -122,58 +88,14 @@
for name in self._static_attributes():
value = self.attrib[name]
- i = 0
- format = ''
- terms = []
+ if translator.interpolate(value):
+ del self.attrib[name]
- defines = []
-
- while value:
- string = value[i:]
- m = interpolate(string, translator)
- if m is None:
- break
-
- start = m.start()
- if start > 0:
- text = string[:m.start()+1]
+ attributes = '{http://xml.zope.org/namespaces/tal}attributes'
+ if attributes in self.attrib:
+ self.attrib[attributes] += '; %s string: %s' % (name, value)
else:
- text = ''
- i += m.end()
-
- format += '%s%s'
- exp = m.group('expression')
-
- if len(translator.value(exp)) == 1:
- terms.extend(("'%s'" % text.replace("'", "\\'"), exp))
- else:
- var = stream.save()
- defines.append((var, m.group('expression')))
- terms.extend(("'%s'" % text.replace("'", "\\'"), var))
-
- if not terms:
- continue
-
- if i < len(value):
- format += '%s'
- terms.append("'%s'" % value[i:].replace("'", "\\'"))
-
- value = "'%s'" % format + '%%(%s,)' % ",".join(terms)
-
- del self.attrib[name]
-
- attributes = '{http://xml.zope.org/namespaces/tal}attributes'
- if attributes in self.attrib:
- self.attrib[attributes] += '; %s %s' % (name, value)
- else:
- self.attrib[attributes] = '%s %s' % (name, value)
-
- define = '{http://xml.zope.org/namespaces/tal}define'
- for name, expression in defines:
- if define in self.attrib:
- self.attrib[define] += '; %s %s' % (name, expression)
- else:
- self.attrib[define] = '%s %s' % (name, expression)
+ self.attrib[attributes] = '%s string: %s' % (name, value)
def _clauses(self):
_ = []
@@ -181,7 +103,7 @@
# i18n domain
if self.i18n_domain is not None:
_.append(clauses.Define(
- "_domain", utils.value({}, (repr(self.i18n_domain),))))
+ "_domain", types.value(repr(self.i18n_domain))))
# defines
if self.define is not None:
@@ -236,7 +158,6 @@
raise ValueError, "Can't use message id with dynamic content translation."
_.append(clauses.Translate())
- # TODO: structure
_.append(clauses.Write(expression))
else:
if self.i18n_translate is not None:
@@ -251,7 +172,7 @@
if elements:
mapping = '_mapping'
- _.append(clauses.Assign(['{}'], mapping))
+ _.append(clauses.Assign(types.value('{}'), mapping))
else:
mapping = 'None'
@@ -260,21 +181,21 @@
subclauses = []
subclauses.append(clauses.Define(
- '_out', utils.value({}, ('generation.initialize_stream()',))))
+ '_out', types.value('generation.initialize_stream()')))
subclauses.append(clauses.Group(element._clauses()))
subclauses.append(clauses.Assign(
- utils.value({}, ('_out.getvalue()',)), "%s['%s']" % (mapping, name)))
+ types.value('_out.getvalue()'), "%s['%s']" % (mapping, name)))
_.append(clauses.Group(subclauses))
_.append(clauses.Assign(
- _translate([repr(msgid)], mapping=mapping, default='_marker'),
+ _translate(types.value(repr(msgid)), mapping=mapping, default='_marker'),
'_result'))
# write translation to output if successful, otherwise
# fallback to default rendition;
- result = utils.value({}, ('_result',))
- condition = utils.value({}, ('_result is not _marker',))
+ result = types.value('_result')
+ condition = types.value('_result is not _marker')
_.append(clauses.Condition(condition, [clauses.Write(result)]))
subclauses = []
@@ -283,8 +204,7 @@
for element in self:
name = element.i18n_name
if name:
- value = utils.value(
- {'structure': True}, ("%s['%s']" % (mapping, name),))
+ value = types.value("%s['%s']" % (mapping, name))
subclauses.append(clauses.Write(value))
else:
subclauses.append(clauses.Out(lxml.etree.tostring(element)))
@@ -293,6 +213,22 @@
_.append(clauses.Else(subclauses))
return _
+
+ def _wrap_comment(self, element):
+ index = self.index(element)
+
+ t = parser.makeelement(
+ '{http://xml.zope.org/namespaces/tal}comment')
+
+ t.attrib['omit-tag'] = ''
+ t.tail = element.tail
+ t.text = '<!--' + element.text + '-->'
+
+ for child in element.getchildren():
+ t.append(child)
+
+ self.remove(element)
+ self.insert(index, t)
def _msgid(self):
"""Create an i18n msgid from the tag contents."""
@@ -343,25 +279,20 @@
# translated attributes
if self.i18n_attributes:
for variable, msgid in self.i18n_attributes:
- if variable in static:
- static_expression = repr(attributes[variable])
-
if msgid:
if variable in dynamic:
raise ValueError, "Message id not allowed in conjunction with " + \
"a dynamic attribute."
- expression = [repr(msgid)]
+ value = types.value(msgid)
if variable in static:
- expression = _translate(expression, default=static_expression)
+ expression = _translate(value, default=attributes[variable])
else:
- expression = _translate(expression)
+ expression = _translate(value)
else:
- if variable in dynamic:
+ if variable in dynamic or variable in static:
expression = _translate(attributes[variable])
- elif variable in static:
- expression = _translate(static_expression)
else:
raise ValueError, "Must be either static or dynamic attribute " + \
"when no message id is supplied."
@@ -382,17 +313,17 @@
define = attribute(
"{http://xml.zope.org/namespaces/tal}define", lambda p: p.definitions)
condition = attribute(
- "{http://xml.zope.org/namespaces/tal}condition", lambda p: p.value)
+ "{http://xml.zope.org/namespaces/tal}condition", lambda p: p.expression)
repeat = attribute(
"{http://xml.zope.org/namespaces/tal}repeat", lambda p: p.definition)
attributes = attribute(
"{http://xml.zope.org/namespaces/tal}attributes", lambda p: p.definitions)
content = attribute(
- "{http://xml.zope.org/namespaces/tal}content", lambda p: p.value)
+ "{http://xml.zope.org/namespaces/tal}content", lambda p: p.output)
replace = attribute(
- "{http://xml.zope.org/namespaces/tal}replace", lambda p: p.value)
+ "{http://xml.zope.org/namespaces/tal}replace", lambda p: p.output)
omit = attribute(
- "{http://xml.zope.org/namespaces/tal}omit-tag", lambda p: p.value)
+ "{http://xml.zope.org/namespaces/tal}omit-tag", lambda p: p.expression)
i18n_translate = attribute(
"{http://xml.zope.org/namespaces/i18n}translate")
i18n_attributes = attribute(
@@ -406,11 +337,11 @@
class TALElement(Element):
define = attribute("define", lambda p: p.definitions)
- replace = attribute("replace", lambda p: p.value)
+ replace = attribute("replace", lambda p: p.output)
repeat = attribute("repeat", lambda p: p.definition)
- attributes = attribute("attributes", lambda p: p.value)
- content = attribute("content", lambda p: p.value)
- omit = attribute("omit-tag", lambda p: p.value, u"")
+ attributes = attribute("attributes", lambda p: p.expression)
+ content = attribute("content", lambda p: p.output)
+ omit = attribute("omit-tag", lambda p: p.expression, u"")
default_expression = attribute("default-expression", lambda p: p.name)
def _static_attributes(self):
@@ -475,12 +406,11 @@
xml.attrib['{http://xml.zope.org/namespaces/tal}omit-tag'] = ''
return translate_etree(xml, *args, **kwargs)
-def _translate(expressions, mapping=None, default=None):
+def _translate(value, mapping=None, default=None):
format = "_translate(%s, domain=_domain, mapping=%s, "\
"target_language=_target_language, default=%s)"
- return utils.value(
- {}, tuple(format % (exp, mapping, default) for exp in expressions))
+ return types.value(format % (value, mapping, default))
-def _not(expressions):
- return utils.value({}, tuple("not (%s)" % exp for exp in expressions))
+def _not(value):
+ return types.value("not (%s)" % value)
Modified: z3c.pt/trunk/z3c/pt/translation.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.txt 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/translation.txt 2008-03-19 00:42:03 UTC (rev 84765)
@@ -8,8 +8,9 @@
>>> def render(body, translator, **kwargs):
... source, _globals = translator(body)
... _locals = {}
+ ... _globals.update(kwargs)
... exec source in _globals, _locals
- ... return _locals['render'](**kwargs)
+ ... return _locals['render']()
TAL
---
@@ -41,14 +42,11 @@
... tal:content="a + 'ghi'" />
... <span tal:replace="'Hello World!'">Hello Universe!</span>
... <span tal:content="None" />
- ... <span tal:attributes="class lambda: 'Hello'"
- ... tal:content="lambda: 'World'" />
... </div>""", translate_xml)
<div>
<span id="test" style="hij" class="defabc">abcghi</span>
Hello World!
<span></span>
- <span class="Hello">World</span>
</div>
Repeats:
@@ -124,8 +122,17 @@
>>> print render("""\
... <div xmlns="http://www.w3.org/1999/xhtml"
... xmlns:tal="http://xml.zope.org/namespaces/tal">
+ ... <img alt="La Peña" />
+ ... </div>""", translate_xml)
+ <div>
+ <img alt="La Peña" />
+ </div>
+
+ >>> print render("""\
+ ... <div xmlns="http://www.w3.org/1999/xhtml"
+ ... xmlns:tal="http://xml.zope.org/namespaces/tal">
... <img tal:attributes="title '%sHello%s' % (chr(60), chr(62))" />
- ... <span tal:replace="structure: '%sbr /%s' % (chr(60), chr(62))" />
+ ... <span tal:replace="structure '%sbr /%s' % (chr(60), chr(62))" />
... <span tal:replace="'%sbr /%s' % (chr(60), chr(62))" />
... <span tal:content="unicode('La Pe\xc3\xb1a', 'utf-8')" />
... </div>""", translate_xml)
@@ -146,22 +153,33 @@
... <span class="my-${'class'} item${'Last'}" />
... <span style="position: ${'abs'}olute"
... class="my-${int('test') | 'class'} item${'Last'}" />
+ ... </div>""", translate_xml)
+ <div>
+ <span>interpolation</span>is convenient!
+ <span class="Hello World!" />
+ <span class="my-class itemLast" />
+ <span style="position: absolute" class="my-class itemLast" />
+ </div>
+
+ >>> print render("""\
+ ... <div xmlns="http://www.w3.org/1999/xhtml"
+ ... xmlns:tal="http://xml.zope.org/namespaces/tal">
+ ... <img alt="${'La Peña'}" />
+ ... <img alt="Hello ${'La Peña'}" />
... <img alt="La Peña, oh ${'La Peña'}" />
... ${unicode('La Pe\xc3\xb1a', 'utf-8')}
... <img alt="${unicode('La Pe\xc3\xb1a', 'utf-8')}" />
... <img alt="Hello ${unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-8')}!" />
... </div>""", translate_xml)
<div>
- <span>interpolation</span>is convenient!
- <span class="Hello World!" />
- <span class="my-class itemLast" />
- <span style="position: absolute" class="my-class itemLast" />
+ <img alt="La Peña" />
+ <img alt="Hello La Peña" />
<img alt="La Peña, oh La Peña" />
La Peña
<img alt="La Peña" />
<img alt="Hello La Peña!" />
</div>
-
+
Changing default expression:
>>> print render("""\
@@ -200,14 +218,13 @@
>>> print render("""\
... <div xmlns="http://www.w3.org/1999/xhtml"
... xmlns:tal="http://xml.zope.org/namespaces/tal">
- ... <span tal:replace="nocall: dir" />
- ... <span tal:replace="nocall: structure: dir" />
- ... <span tal:condition="not: False">Hello World</span>
- ... </div>""", translate_xml)
+ ... <span tal:default-expression="path"
+ ... tal:replace="structure nocall: dir" />
+ ... <span tal:replace="structure dir" />
+ ... </div>""", translate_xml, request=object())
<div>
- <built-in function dir>
<built-in function dir>
- <span>Hello World</span>
+ <built-in function dir>
</div>
Text templates
Added: z3c.pt/trunk/z3c/pt/types.py
===================================================================
--- z3c.pt/trunk/z3c/pt/types.py (rev 0)
+++ z3c.pt/trunk/z3c/pt/types.py 2008-03-19 00:42:03 UTC (rev 84765)
@@ -0,0 +1,30 @@
+class expression:
+ pass
+
+class parts(tuple, expression):
+ def __repr__(self):
+ return 'parts'+tuple.__repr__(self)
+
+class value(str, expression):
+ def __repr__(self):
+ return 'value(%s)' % str.__repr__(self)
+
+class join(tuple, expression):
+ def __repr__(self):
+ return 'join'+tuple.__repr__(self)
+
+class declaration(tuple):
+ def __repr__(self):
+ return 'declaration'+tuple.__repr__(self)
+
+class mapping(tuple):
+ def __repr__(self):
+ return 'mapping'+tuple.__repr__(self)
+
+class definitions(tuple):
+ def __repr__(self):
+ return 'definitions'+tuple.__repr__(self)
+
+class escape(parts):
+ def __repr__(self):
+ return 'escape'+tuple.__repr__(self)
Modified: z3c.pt/trunk/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/z3c/pt/utils.py 2008-03-18 22:54:00 UTC (rev 84764)
+++ z3c.pt/trunk/z3c/pt/utils.py 2008-03-19 00:42:03 UTC (rev 84765)
@@ -1,4 +1,5 @@
import sys
+import re
import logging
# check if we're able to coerce unicode to str
@@ -34,12 +35,6 @@
def __hash__(self):
return self.hash
-class value(tuple):
- def __new__(cls, options, *args):
- inst = tuple.__new__(cls, *args)
- inst.options = options
- return inst
-
class repeatitem(object):
def __init__(self, iterator, length):
self.length = length
More information about the Checkins
mailing list