[Checkins] SVN: z3c.pt/trunk/ Major refactoring;
added attribute-access fallback to dictionary lookup.
Bumped version number.
Malthe Borch
mborch at gmail.com
Wed Dec 5 11:24:00 EST 2007
Log message for revision 82141:
Major refactoring; added attribute-access fallback to dictionary lookup. Bumped version number.
Changed:
U z3c.pt/trunk/README.txt
U z3c.pt/trunk/setup.py
U z3c.pt/trunk/z3c/pt/README.txt
U z3c.pt/trunk/z3c/pt/VERSION.txt
A z3c.pt/trunk/z3c/pt/codegen.py
A z3c.pt/trunk/z3c/pt/codegen.txt
U z3c.pt/trunk/z3c/pt/io.py
U z3c.pt/trunk/z3c/pt/pagetemplate.py
U z3c.pt/trunk/z3c/pt/tal.py
U z3c.pt/trunk/z3c/pt/tal.txt
U z3c.pt/trunk/z3c/pt/tests/test_doctests.py
A z3c.pt/trunk/z3c/pt/transformer.py
D z3c.pt/trunk/z3c/pt/translate.txt
A z3c.pt/trunk/z3c/pt/translation.py
A z3c.pt/trunk/z3c/pt/translation.txt
-=-
Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/README.txt 2007-12-05 16:23:59 UTC (rev 82141)
@@ -12,14 +12,12 @@
The METAL macro language is not supported; i18n is on the to-do.
-Template language
------------------
+Template and expression language
+--------------------------------
-The template language is based loosely on the TAL 1.4 specification
-found here:
+The template and expression language is based loosely on the TAL 1.4
+specification*. Some notable changes:
- * http://wiki.zope.org/ZPT/TALSpecification14
-
1. Only Python-expressions are allowed. Expressions can have
try-except fallbacks using the vertical bar syntax:
@@ -33,3 +31,12 @@
repeat variable is not available in this case.
tal:repeat="i <some generator>"
+
+4. Attribute-access to dictionary entries is allowed, e.g.
+
+ dictionary.key
+
+ can be used instead of ``dictionary['key']``.
+
+*) http://wiki.zope.org/ZPT/TALSpecification14
+
Modified: z3c.pt/trunk/setup.py
===================================================================
--- z3c.pt/trunk/setup.py 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/setup.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -24,6 +24,7 @@
zip_safe=False,
install_requires=[
'setuptools',
+ 'lxml',
# -*- Extra requirements: -*-
],
entry_points="""
Modified: z3c.pt/trunk/z3c/pt/README.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/README.txt 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/README.txt 2007-12-05 16:23:59 UTC (rev 82141)
@@ -9,7 +9,7 @@
>>> from z3c.pt import PageTemplate
>>> template = PageTemplate("""\
... <div xmlns="http://www.w3.org/1999/xhtml"
- ... xmlns:tal="http://xml.zope.org/namespaces/tal">
+ ... xmlns:tal="http://xml.zope.org/namespaces/tal/python">
... Hello World!
... </div>
... """)
Modified: z3c.pt/trunk/z3c/pt/VERSION.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/VERSION.txt 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/VERSION.txt 2007-12-05 16:23:59 UTC (rev 82141)
@@ -1 +1 @@
-0.1
+0.2
Added: z3c.pt/trunk/z3c/pt/codegen.py
===================================================================
--- z3c.pt/trunk/z3c/pt/codegen.py (rev 0)
+++ z3c.pt/trunk/z3c/pt/codegen.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,63 @@
+from compiler import ast, parse
+from compiler.pycodegen import ModuleCodeGenerator
+
+from transformer import ASTTransformer
+
+marker = object()
+
+CONSTANTS = frozenset(['False', 'True', 'None', 'NotImplemented', 'Ellipsis'])
+
+class TemplateASTTransformer(ASTTransformer):
+ def __init__(self):
+ self.locals = [CONSTANTS]
+
+ def visitGetattr(self, node):
+ """
+ Allow fallback to dictionary lookup if attribute does not exist.
+
+ Variables starting with an underscore are exempt.
+
+ """
+
+ if hasattr(node.expr, 'name') and node.expr.name.startswith('_'):
+ return ast.Getattr(node.expr, node.attrname)
+
+ expr = self.visit(node.expr)
+ name = ast.Const(node.attrname)
+
+ # hasattr(obj, key) and getattr(obj, key) or not
+ # hasattr(obj, key) and obj[key]
+ return ast.Or([ast.And(
+ [ast.CallFunc(ast.Name('hasattr'), [expr, name], None, None),
+ ast.CallFunc(ast.Name('getattr'), [expr, name], None, None)]),
+ ast.And([
+ ast.Not(ast.CallFunc(ast.Name('hasattr'), [expr, name], None, None)),
+ ast.Subscript(expr, 'OP_APPLY', [name])])])
+
+class Suite(object):
+ __slots__ = ['code']
+
+ xform = TemplateASTTransformer
+ mode = 'exec'
+
+ def __init__(self, source):
+ """Create the code object from a string."""
+
+ node = parse(source, self.mode)
+
+ # build tree
+ transform = self.xform()
+ tree = transform.visit(node)
+ filename = tree.filename = '<script>'
+
+ # generate code
+ gen = ModuleCodeGenerator(tree)
+ gen.optimized = True
+
+ self.code = gen.getCode()
+
+ def __hash__(self):
+ return hash(self.code)
+
+ def __repr__(self):
+ return '%s(%r)' % (self.__class__.__name__, self.source)
Added: z3c.pt/trunk/z3c/pt/codegen.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/codegen.txt (rev 0)
+++ z3c.pt/trunk/z3c/pt/codegen.txt 2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,31 @@
+Codegen
+=======
+
+The ``Codegen`` module is responsible for the low-level compilation
+of the page template.
+
+Suite
+-----
+
+The ``Suite`` class compiles a source code suite and makes a code
+object available.
+
+ >>> from z3c.pt.codegen import Suite
+ >>> suite = Suite("""\
+ ... print 'Hello World!'
+ ... """)
+ >>> exec suite.code
+ Hello World!
+
+AST transformations
+-------------------
+
+We allow attribute access to dictionary entries to minimize verbosity
+in templates. It works by wrapping the get attribute nodes in a method
+that tries a dictionary lookup if attribute lookup failed.
+
+ >>> suite = Suite("""\
+ ... a = {'b': 1}
+ ... assert a['b'] == a.b
+ ... """)
+ >>> exec suite.code
Modified: z3c.pt/trunk/z3c/pt/io.py
===================================================================
--- z3c.pt/trunk/z3c/pt/io.py 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/io.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -12,9 +12,9 @@
variable_prefix = '_saved'
- def __init__(self, indentation_string="\t"):
+ def __init__(self, indentation=0, indentation_string="\t"):
StringIO.__init__(self)
- self.indentation = 0
+ self.indentation = indentation
self.indentation_string = indentation_string
self.counter = 0
self.queue = u''
Modified: z3c.pt/trunk/z3c/pt/pagetemplate.py
===================================================================
--- z3c.pt/trunk/z3c/pt/pagetemplate.py 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/pagetemplate.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -1,67 +1,21 @@
-import lxml.etree
-import StringIO
-
-import tal
-import io
-
-ns_lookup_table = [(f.__ns__, f) for f in (tal.define,
- tal.condition,
- tal.omit,
- tal.repeat,
- tal.attribute,
- tal.replace,
- tal.tag,
- tal.content)]
+import translation
+import codegen
class PageTemplate(object):
def __init__(self, body):
self.body = body
self.render = None
- def translate(self):
- """Translates page template body to Python-code."""
+ def cook(self):
+ source, _globals = translation.translate(self.body)
+ suite = codegen.Suite(source)
- tree = lxml.etree.parse(StringIO.StringIO(self.body))
- root = tree.getroot()
+ _locals = {}
- stream = io.CodeIO(indentation_string=' ')
+ exec suite.code in _globals, _locals
- # imports and symbols
- stream.write("from cgi import escape as _escape")
- stream.write("from z3c.pt.tal import repeatdict as _repeatdict")
- stream.write("from StringIO import StringIO as _StringIO")
-
- stream.write("def render(**_kwargs):")
- stream.indent()
+ self.render = _locals['render']
- # globalize imports and set up process variables
- stream.write("global _StringIO, _repeatdict, _escape")
- stream.write("repeat = _repeatdict()")
- stream.write("_attrs = {}")
- stream.write("_scope = _kwargs.keys()")
-
- # output
- stream.write("_out = _StringIO()")
-
- # set up keyword args
- stream.write("for _variable, _value in _kwargs.items():")
- stream.indent()
- stream.write("globals()[_variable] = _value")
- stream.outdent()
-
- # translate tree
- translate(root, stream)
-
- # output
- stream.write("return _out.getvalue()")
- stream.outdent()
-
- return stream.getvalue()
-
- def cook(self):
- exec self.translate()
- self.render = render
-
@property
def template(self):
if self.render is None:
@@ -80,31 +34,3 @@
@property
def body(self):
return open(self.filename, 'r').read()
-
-def translate(node, stream):
- keys = node.keys() + [None]
-
- handlers = [handler(node) for key, handler in ns_lookup_table \
- if key in keys]
-
- # remove namespace attributes
- for key, handler in ns_lookup_table:
- if key is not None and key in node.attrib:
- del node.attrib[key]
-
- # update node
- for handler in handlers:
- node = handler.update(node)
-
- # begin tag
- for handler in handlers:
- handler.begin(stream)
-
- # process children
- if node:
- for element in node:
- translate(element, stream)
-
- # end tag
- for handler in reversed(handlers):
- handler.end(stream)
Modified: z3c.pt/trunk/z3c/pt/tal.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.py 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/tal.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -170,7 +170,7 @@
expression = self.expressions[-1]
stream.write("%s = %s" % (variable, expression))
-
+
def end(self, stream):
stream.outdent(len(self.expressions)-1)
@@ -212,7 +212,7 @@
stream.write("del %s" % variable)
stream.write("_scope.remove('%s')" % variable)
stream.outdent()
-
+
class Condition(object):
def __init__(self, value):
self.assign = Assign(expression(value))
Modified: z3c.pt/trunk/z3c/pt/tal.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.txt 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/tal.txt 2007-12-05 16:23:59 UTC (rev 82141)
@@ -27,16 +27,31 @@
>>> tal.expression("a.non_defined_method() | '|'")
['a.non_defined_method() ', "'|'"]
-
-Define
+
+Assign
------
+Simple assignment.
+
>>> from z3c.pt.io import CodeIO
>>> stream = CodeIO()
>>> _scope = []
+
+ >>> assign = tal.Assign(tal.expression("1"))
+ >>> assign.begin(stream, 'a')
+ >>> exec stream.getvalue()
+ >>> a == 1
+ True
+ >>> del a
+ >>> assign.end(stream)
+ >>> exec stream.getvalue()
+Define
+------
+
Variable scope:
+ >>> stream = CodeIO()
>>> define = tal.Define("a b")
>>> b = object()
>>> define.begin(stream)
@@ -56,6 +71,7 @@
Multiple defines:
+ >>> stream = CodeIO()
>>> define = tal.Define("a b; c d")
>>> d = object()
>>> define.begin(stream)
@@ -79,6 +95,7 @@
Tuple assignments:
+ >>> stream = CodeIO()
>>> define = tal.Define("(e, f) [1, 2]")
>>> define.begin(stream)
>>> exec stream.getvalue()
@@ -96,6 +113,7 @@
Using semicolons in expressions within a define:
+ >>> stream = CodeIO()
>>> define = tal.Define("a ';'")
>>> define.begin(stream)
>>> exec stream.getvalue()
@@ -105,16 +123,22 @@
Ending an define clause with a semicolon.
+ >>> stream = CodeIO()
>>> define = tal.Define("a 4 + 5;")
>>> define.begin(stream)
>>> exec stream.getvalue()
>>> a
9
>>> define.end(stream)
-
+ >>> del a
+ >>> _scope.remove('a')
+ >>> exec stream.getvalue()
+
Scope:
+ >>> stream = CodeIO()
>>> a = 1
+ >>> _scope.append('a')
>>> define = tal.Define("a 2")
>>> define.begin(stream)
>>> define.end(stream)
Modified: z3c.pt/trunk/z3c/pt/tests/test_doctests.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tests/test_doctests.py 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/tests/test_doctests.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -3,21 +3,20 @@
OPTIONFLAGS = (zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
zope.testing.doctest.ELLIPSIS |
-# zope.testing.doctest.REPORT_NDIFF |
zope.testing.doctest.NORMALIZE_WHITESPACE)
import zope.component.testing
def test_suite():
- doctests = ['README.txt', 'BENCHMARKS.txt', 'tal.txt', 'translate.txt']
-
- return unittest.TestSuite((
- zope.testing.doctest.DocFileSuite(doctest,
+ filesuites = ['README.txt', 'BENCHMARKS.txt', 'tal.txt', 'translation.txt', 'codegen.txt']
+
+ return unittest.TestSuite(
+ [zope.testing.doctest.DocFileSuite(doctest,
optionflags=OPTIONFLAGS,
setUp=zope.component.testing.setUp,
tearDown=zope.component.testing.tearDown,
- package="z3c.pt") for doctest in doctests
- ))
+ package="z3c.pt") for doctest in filesuites]
+ )
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Added: z3c.pt/trunk/z3c/pt/transformer.py
===================================================================
--- z3c.pt/trunk/z3c/pt/transformer.py (rev 0)
+++ z3c.pt/trunk/z3c/pt/transformer.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,235 @@
+from compiler import ast, parse
+
+class ASTTransformer(object):
+ """General purpose base class for AST transformations.
+
+ Every visitor method can be overridden to return an AST node that has been
+ altered or replaced in some way.
+ """
+
+ def visit(self, node):
+ if node is None:
+ return None
+ if type(node) is tuple:
+ return tuple([self.visit(n) for n in node])
+ visitor = getattr(self, 'visit%s' % node.__class__.__name__,
+ self._visitDefault)
+ return visitor(node)
+
+ def _clone(self, node, *args):
+ lineno = getattr(node, 'lineno', None)
+ node = node.__class__(*args)
+ if lineno is not None:
+ node.lineno = lineno
+ if isinstance(node, (ast.Class, ast.Function, ast.GenExpr, ast.Lambda)):
+ node.filename = '<string>' # workaround for bug in pycodegen
+ return node
+
+ def _visitDefault(self, node):
+ return node
+
+ def visitExpression(self, node):
+ return self._clone(node, self.visit(node.node))
+
+ def visitModule(self, node):
+ return self._clone(node, node.doc, self.visit(node.node))
+
+ def visitStmt(self, node):
+ return self._clone(node, [self.visit(x) for x in node.nodes])
+
+ # Classes, Functions & Accessors
+
+ def visitCallFunc(self, node):
+ return self._clone(node, self.visit(node.node),
+ [self.visit(x) for x in node.args],
+ node.star_args and self.visit(node.star_args) or None,
+ node.dstar_args and self.visit(node.dstar_args) or None
+ )
+
+ def visitClass(self, node):
+ return self._clone(node, node.name, [self.visit(x) for x in node.bases],
+ node.doc, self.visit(node.code)
+ )
+
+ def visitFunction(self, node):
+ args = []
+ if hasattr(node, 'decorators'):
+ args.append(self.visit(node.decorators))
+ return self._clone(node, *args + [
+ node.name,
+ node.argnames,
+ [self.visit(x) for x in node.defaults],
+ node.flags,
+ node.doc,
+ self.visit(node.code)
+ ])
+
+ def visitGetattr(self, node):
+ return self._clone(node, self.visit(node.expr), node.attrname)
+
+ def visitLambda(self, node):
+ node = self._clone(node, node.argnames,
+ [self.visit(x) for x in node.defaults], node.flags,
+ self.visit(node.code)
+ )
+ return node
+
+ def visitSubscript(self, node):
+ return self._clone(node, self.visit(node.expr), node.flags,
+ [self.visit(x) for x in node.subs]
+ )
+
+ # Statements
+
+ def visitAssert(self, node):
+ return self._clone(node, self.visit(node.test), self.visit(node.fail))
+
+ def visitAssign(self, node):
+ return self._clone(node, [self.visit(x) for x in node.nodes],
+ self.visit(node.expr)
+ )
+
+ def visitAssAttr(self, node):
+ return self._clone(node, self.visit(node.expr), node.attrname,
+ node.flags
+ )
+
+ def visitAugAssign(self, node):
+ return self._clone(node, self.visit(node.node), node.op,
+ self.visit(node.expr)
+ )
+
+ def visitDecorators(self, node):
+ return self._clone(node, [self.visit(x) for x in node.nodes])
+
+ def visitExec(self, node):
+ return self._clone(node, self.visit(node.expr), self.visit(node.locals),
+ self.visit(node.globals)
+ )
+
+ def visitFor(self, node):
+ return self._clone(node, self.visit(node.assign), self.visit(node.list),
+ self.visit(node.body), self.visit(node.else_)
+ )
+
+ def visitIf(self, node):
+ return self._clone(node, [self.visit(x) for x in node.tests],
+ self.visit(node.else_)
+ )
+
+ def _visitPrint(self, node):
+ return self._clone(node, [self.visit(x) for x in node.nodes],
+ self.visit(node.dest)
+ )
+ visitPrint = visitPrintnl = _visitPrint
+
+ def visitRaise(self, node):
+ return self._clone(node, self.visit(node.expr1), self.visit(node.expr2),
+ self.visit(node.expr3)
+ )
+
+ def visitReturn(self, node):
+ return self._clone(node, self.visit(node.value))
+
+ def visitTryExcept(self, node):
+ return self._clone(node, self.visit(node.body), self.visit(node.handlers),
+ self.visit(node.else_)
+ )
+
+ def visitTryFinally(self, node):
+ return self._clone(node, self.visit(node.body), self.visit(node.final))
+
+ def visitWhile(self, node):
+ return self._clone(node, self.visit(node.test), self.visit(node.body),
+ self.visit(node.else_)
+ )
+
+ def visitWith(self, node):
+ return self._clone(node, self.visit(node.expr),
+ [self.visit(x) for x in node.vars], self.visit(node.body)
+ )
+
+ def visitYield(self, node):
+ return self._clone(node, self.visit(node.value))
+
+ # Operators
+
+ def _visitBoolOp(self, node):
+ return self._clone(node, [self.visit(x) for x in node.nodes])
+ visitAnd = visitOr = visitBitand = visitBitor = visitBitxor = _visitBoolOp
+ visitAssTuple = visitAssList = _visitBoolOp
+
+ def _visitBinOp(self, node):
+ return self._clone(node,
+ (self.visit(node.left), self.visit(node.right))
+ )
+ visitAdd = visitSub = _visitBinOp
+ visitDiv = visitFloorDiv = visitMod = visitMul = visitPower = _visitBinOp
+ visitLeftShift = visitRightShift = _visitBinOp
+
+ def visitCompare(self, node):
+ return self._clone(node, self.visit(node.expr),
+ [(op, self.visit(n)) for op, n in node.ops]
+ )
+
+ def _visitUnaryOp(self, node):
+ return self._clone(node, self.visit(node.expr))
+ visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp
+ visitBackquote = visitDiscard = _visitUnaryOp
+
+ def visitIfExp(self, node):
+ return self._clone(node, self.visit(node.test), self.visit(node.then),
+ self.visit(node.else_)
+ )
+
+ # Identifiers, Literals and Comprehensions
+
+ def visitDict(self, node):
+ return self._clone(node,
+ [(self.visit(k), self.visit(v)) for k, v in node.items]
+ )
+
+ def visitGenExpr(self, node):
+ return self._clone(node, self.visit(node.code))
+
+ def visitGenExprFor(self, node):
+ return self._clone(node, self.visit(node.assign), self.visit(node.iter),
+ [self.visit(x) for x in node.ifs]
+ )
+
+ def visitGenExprIf(self, node):
+ return self._clone(node, self.visit(node.test))
+
+ def visitGenExprInner(self, node):
+ quals = [self.visit(x) for x in node.quals]
+ return self._clone(node, self.visit(node.expr), quals)
+
+ def visitKeyword(self, node):
+ return self._clone(node, node.name, self.visit(node.expr))
+
+ def visitList(self, node):
+ return self._clone(node, [self.visit(n) for n in node.nodes])
+
+ def visitListComp(self, node):
+ quals = [self.visit(x) for x in node.quals]
+ return self._clone(node, self.visit(node.expr), quals)
+
+ def visitListCompFor(self, node):
+ return self._clone(node, self.visit(node.assign), self.visit(node.list),
+ [self.visit(x) for x in node.ifs]
+ )
+
+ def visitListCompIf(self, node):
+ return self._clone(node, self.visit(node.test))
+
+ def visitSlice(self, node):
+ return self._clone(node, self.visit(node.expr), node.flags,
+ node.lower and self.visit(node.lower) or None,
+ node.upper and self.visit(node.upper) or None
+ )
+
+ def visitSliceobj(self, node):
+ return self._clone(node, [self.visit(x) for x in node.nodes])
+
+ def visitTuple(self, node):
+ return self._clone(node, [self.visit(n) for n in node.nodes])
Deleted: z3c.pt/trunk/z3c/pt/translate.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/translate.txt 2007-12-05 14:08:44 UTC (rev 82140)
+++ z3c.pt/trunk/z3c/pt/translate.txt 2007-12-05 16:23:59 UTC (rev 82141)
@@ -1,60 +0,0 @@
-Translation
------------
-
- >>> from z3c.pt.pagetemplate import translate
- >>> from z3c.pt import io
-
-A few imports.
-
- >>> from lxml import etree
- >>> from StringIO import StringIO
-
-The following symbols are expected to be in the local symbol table.
-
- >>> from cgi import escape as _escape
- >>> from z3c.pt.tal import repeatdict as _repeat
- >>> repeat = _repeat()
- >>> _attrs = {}
-
-Example:
-
- >>> tree = etree.fromstring("""\
- ... <div xmlns="http://www.w3.org/1999/xhtml"
- ... xmlns:tal="http://xml.zope.org/namespaces/tal">
- ... <span tal:define="a 'abc'"
- ... tal:attributes="class 'def' + a"
- ... tal:content="a + 'ghi'" />
- ... <ul>
- ... <li tal:repeat="i range(5)">
- ... <span tal:replace="'Item ' + str(i) + ')'" />
- ... </li>
- ... </ul>
- ... </div>
- ... """)
-
- >>> stream = io.CodeIO(indentation_string=" ")
- >>> translate(tree, stream)
- >>> _out = StringIO()
- >>> _scope = []
- >>> exec stream.getvalue()
- >>> print _out.getvalue()
- <div>
- <span class="defabc">abcghi</span>
- <ul>
- <li>
- Item 0)
- </li>
- <li>
- Item 1)
- </li>
- <li>
- Item 2)
- </li>
- <li>
- Item 3)
- </li>
- <li>
- Item 4)
- </li>
- </ul>
- </div>
Added: z3c.pt/trunk/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.py (rev 0)
+++ z3c.pt/trunk/z3c/pt/translation.py 2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,76 @@
+from StringIO import StringIO
+import lxml.etree
+
+import cgi
+import tal
+import io
+
+ns_lookup_table = [(f.__ns__, f) for f in \
+ (tal.define,
+ tal.condition,
+ tal.omit,
+ tal.repeat,
+ tal.attribute,
+ tal.replace,
+ tal.tag,
+ tal.content)]
+
+wrapper = """\
+def render(**_kwargs):
+\trepeat = _repeatdict()
+\t_attrs = {}
+\t_scope = _kwargs.keys()
+\t_out = _StringIO()
+\t_globals = globals()
+\tfor _variable, _value in _kwargs.items():
+\t\t_globals[_variable] = _value
+%s
+\treturn _out.getvalue()
+"""
+
+def translate(body):
+ tree = lxml.etree.parse(StringIO(body))
+ root = tree.getroot()
+
+ stream = io.CodeIO(indentation=1, indentation_string="\t")
+ visit(root, stream)
+
+ source = wrapper % stream.getvalue()
+
+ _globals = dict(_StringIO=StringIO,
+ _repeatdict=tal.repeatdict,
+ _escape=cgi.escape)
+
+ return source, _globals
+
+def visit(node, stream):
+ """Translates a node and outputs to a code stream."""
+
+ keys = node.keys() + [None]
+
+ handlers = [handler(node) for key, handler in ns_lookup_table \
+ if key in keys]
+
+ # remove namespace attributes
+ for key, handler in ns_lookup_table:
+ if key is not None and key in node.attrib:
+ del node.attrib[key]
+
+ # update node
+ for handler in handlers:
+ node = handler.update(node)
+
+ # begin tag
+ for handler in handlers:
+ handler.begin(stream)
+
+ # process children
+ if node:
+ for element in node:
+ visit(element, stream)
+
+ # end tag
+ for handler in reversed(handlers):
+ handler.end(stream)
+
+ return stream
Copied: z3c.pt/trunk/z3c/pt/translation.txt (from rev 82100, z3c.pt/trunk/z3c/pt/translate.txt)
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.txt (rev 0)
+++ z3c.pt/trunk/z3c/pt/translation.txt 2007-12-05 16:23:59 UTC (rev 82141)
@@ -0,0 +1,45 @@
+Translation
+-----------
+
+ >>> from z3c.pt.translation import translate
+
+Example:
+
+ >>> body = """\
+ ... <div xmlns="http://www.w3.org/1999/xhtml"
+ ... xmlns:tal="http://xml.zope.org/namespaces/tal">
+ ... <span tal:define="a 'abc'"
+ ... tal:attributes="class 'def' + a"
+ ... tal:content="a + 'ghi'" />
+ ... <ul>
+ ... <li tal:repeat="i range(5)">
+ ... <span tal:replace="'Item ' + str(i) + ')'" />
+ ... </li>
+ ... </ul>
+ ... </div>
+ ... """
+
+ >>> source, _globals = translate(body)
+ >>> _locals = {}
+ >>> exec source in _globals, _locals
+ >>> print _locals['render']()
+ <div>
+ <span class="defabc">abcghi</span>
+ <ul>
+ <li>
+ Item 0)
+ </li>
+ <li>
+ Item 1)
+ </li>
+ <li>
+ Item 2)
+ </li>
+ <li>
+ Item 3)
+ </li>
+ <li>
+ Item 4)
+ </li>
+ </ul>
+ </div>
More information about the Checkins
mailing list