Index: template/base.py
===================================================================
--- template/base.py	(Revision 3)
+++ template/base.py	(Arbeitskopie)
@@ -20,6 +20,7 @@
 
 from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure
 from genshi.input import ParseError
+from genshi.template.astutil import ASTTransformer
 
 __all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError',
            'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError']
@@ -370,7 +371,7 @@
     _number_conv = unicode # function used to convert numbers to event data
 
     def __init__(self, source, filepath=None, filename=None, loader=None,
-                 encoding=None, lookup='strict', allow_exec=True):
+                 encoding=None, lookup='strict', allow_exec=True, xform=None):
         """Initialize a template from either a string, a file-like object, or
         an already parsed markup stream.
         
@@ -386,15 +387,36 @@
                        default), "lenient", or a custom lookup class
         :param allow_exec: whether Python code blocks in templates should be
                            allowed
+        :param xform: a dict in the form of 
         
+                      { 'eval' : <class>, 'exec' : <class> }
+                      
+                      where <class> is the respective AST transformer class that
+                      should be applied to the code representing either a python
+                      expression or executable python code; if `None`, or either
+                      of the two is omitted, then the default transformation is
+                      chosen depending on the current execution mode, which is 
+                      either 'eval' or 'exec'.
+
         :note: Changed in 0.5: Added the `allow_exec` argument
+        :note: Changed in 0.7: Added the `xform` argument
         """
         self.filepath = filepath or filename
         self.filename = filename
         self.loader = loader
         self.lookup = lookup
         self.allow_exec = allow_exec
-        self._init_filters()
+
+        if xform is not None :
+            if 'eval' in xform and not ASTTransformer in xform['eval'].__mro__:
+                raise TemplateRuntimeError("xform['eval'] must inherit from "
+                                           "ASTTransformer.")
+            if 'exec' in xform and not ASTTransformer in xform['exec'].__mro__:
+                raise TemplateRuntimeError("xform['exec'] must inherit from "
+                                           "ASTTransformer.")
+
+        self.xform = xform
+        self._init_filters(xform=xform)
         self._prepared = False
 
         if isinstance(source, basestring):
@@ -418,10 +440,10 @@
     def __repr__(self):
         return '<%s "%s">' % (type(self).__name__, self.filename)
 
-    def _init_filters(self):
+    def _init_filters(self, xform=None):
         self.filters = [self._flatten]
         if self.loader:
-            self.filters.append(self._include)
+            self.filters.append(self._include(xform=xform))
 
     @property
     def stream(self):
@@ -476,7 +498,8 @@
                         # the template is inlined into the stream
                         try:
                             tmpl = self.loader.load(href, relative_to=pos[0],
-                                                    cls=cls or self.__class__)
+                                                    cls=cls or self.__class__,
+                                                    xform=self.xform)
                             for event in tmpl.stream:
                                 yield event
                         except TemplateNotFound:
@@ -583,38 +606,40 @@
                     break
                 stream = pop()
 
-    def _include(self, stream, ctxt, **vars):
+    def _include(self, xform=None):
         """Internal stream filter that performs inclusion of external
         template files.
         """
-        from genshi.template.loader import TemplateNotFound
+        def real_include(stream, ctxt, **vars):
+            from genshi.template.loader import TemplateNotFound
+    
+            for event in stream:
+                if event[0] is INCLUDE:
+                    href, cls, fallback = event[1]
+                    if not isinstance(href, basestring):
+                        parts = []
+                        for subkind, subdata, subpos in self._flatten(href, ctxt,
+                                                                      **vars):
+                            if subkind is TEXT:
+                                parts.append(subdata)
+                        href = ''.join([x for x in parts if x is not None])
+                    try:
+                        tmpl = self.loader.load(href, relative_to=event[2][0],
+                                                cls=cls or self.__class__)
+                        for event in tmpl.generate(ctxt, **vars):
+                            yield event
+                    except TemplateNotFound:
+                        if fallback is None:
+                            raise
+                        for filter_ in self.filters:
+                            fallback = filter_(iter(fallback), ctxt, **vars)
+                        for event in fallback:
+                            yield event
+                else:
+                    yield event
 
-        for event in stream:
-            if event[0] is INCLUDE:
-                href, cls, fallback = event[1]
-                if not isinstance(href, basestring):
-                    parts = []
-                    for subkind, subdata, subpos in self._flatten(href, ctxt,
-                                                                  **vars):
-                        if subkind is TEXT:
-                            parts.append(subdata)
-                    href = ''.join([x for x in parts if x is not None])
-                try:
-                    tmpl = self.loader.load(href, relative_to=event[2][0],
-                                            cls=cls or self.__class__)
-                    for event in tmpl.generate(ctxt, **vars):
-                        yield event
-                except TemplateNotFound:
-                    if fallback is None:
-                        raise
-                    for filter_ in self.filters:
-                        fallback = filter_(iter(fallback), ctxt, **vars)
-                    for event in fallback:
-                        yield event
-            else:
-                yield event
+        return real_include
 
-
 EXEC = Template.EXEC
 EXPR = Template.EXPR
 INCLUDE = Template.INCLUDE
Index: template/markup.py
===================================================================
--- template/markup.py	(Revision 3)
+++ template/markup.py	(Arbeitskopie)
@@ -61,20 +61,20 @@
     _number_conv = Markup
 
     def __init__(self, source, filepath=None, filename=None, loader=None,
-                 encoding=None, lookup='strict', allow_exec=True):
+                 encoding=None, lookup='strict', allow_exec=True, xform=None):
         Template.__init__(self, source, filepath=filepath, filename=filename,
                           loader=loader, encoding=encoding, lookup=lookup,
-                          allow_exec=allow_exec)
+                          allow_exec=allow_exec, xform=xform)
         self.add_directives(self.DIRECTIVE_NAMESPACE, self)
 
     def _init_filters(self):
         Template._init_filters(self)
         # Make sure the include filter comes after the match filter
         if self.loader:
-            self.filters.remove(self._include)
+            self.filters.remove(self._include(xform=xform))
         self.filters += [self._match]
         if self.loader:
-            self.filters.append(self._include)
+            self.filters.append(self._include(xform=xform))
 
     def _parse(self, source, encoding):
         if not isinstance(source, Stream):
@@ -86,7 +86,8 @@
 
             if kind is TEXT:
                 for kind, data, pos in interpolate(data, self.filepath, pos[1],
-                                                   pos[2], lookup=self.lookup):
+                                                   pos[2], lookup=self.lookup,
+                                                   xform=self.xform):
                     stream.append((kind, data, pos))
 
             elif kind is PI and data[0] == 'python':
@@ -95,7 +96,7 @@
                                               self.filepath, *pos[1:])
                 try:
                     suite = Suite(data[1], self.filepath, pos[1],
-                                  lookup=self.lookup)
+                                  lookup=self.lookup, xform=self.xform)
                 except SyntaxError, err:
                     raise TemplateSyntaxError(err, self.filepath,
                                               pos[1] + (err.lineno or 1) - 1,
@@ -278,7 +279,8 @@
                 for name, value in attrs:
                     if value:
                         value = list(interpolate(value, self.filepath, pos[1],
-                                                 pos[2], lookup=self.lookup))
+                                                 pos[2], lookup=self.lookup,
+                                                 xform=self.xform))
                         if len(value) == 1 and value[0][0] is TEXT:
                             value = value[0][1]
                     new_attrs.append((name, value))
Index: template/text.py
===================================================================
--- template/text.py	(Revision 3)
+++ template/text.py	(Arbeitskopie)
@@ -132,10 +132,11 @@
 
     def __init__(self, source, filepath=None, filename=None, loader=None,
                  encoding=None, lookup='strict', allow_exec=False,
-                 delims=('{%', '%}', '{#', '#}')):
+                 delims=('{%', '%}', '{#', '#}'), xform=None):
         self.delimiters = delims
         Template.__init__(self, source, filepath=filepath, filename=filename,
-                          loader=loader, encoding=encoding, lookup=lookup)
+                          loader=loader, encoding=encoding, lookup=lookup,
+                          xform=xform)
 
     def _get_delims(self):
         return self._delims
@@ -179,7 +180,8 @@
             if start > offset:
                 text = _escape_sub(_escape_repl, source[offset:start])
                 for kind, data, pos in interpolate(text, self.filepath, lineno,
-                                                   lookup=self.lookup):
+                                                   lookup=self.lookup,
+                                                   xform=self.xform):
                     stream.append((kind, data, pos))
                 lineno += len(text.splitlines())
 
@@ -189,7 +191,7 @@
             if command == 'include':
                 pos = (self.filename, lineno, 0)
                 value = list(interpolate(value, self.filepath, lineno, 0,
-                                         lookup=self.lookup))
+                                         lookup=self.lookup, xform=self.xform))
                 if len(value) == 1 and value[0][0] is TEXT:
                     value = value[0][1]
                 stream.append((INCLUDE, (value, None, []), pos))
@@ -200,7 +202,7 @@
                                               self.filepath, lineno)
                 try:
                     suite = Suite(value, self.filepath, lineno,
-                                  lookup=self.lookup)
+                                  lookup=self.lookup, xform=self.xform)
                 except SyntaxError, err:
                     raise TemplateSyntaxError(err, self.filepath,
                                               lineno + (err.lineno or 1) - 1)
@@ -228,7 +230,8 @@
         if offset < len(source):
             text = _escape_sub(_escape_repl, source[offset:])
             for kind, data, pos in interpolate(text, self.filepath, lineno,
-                                               lookup=self.lookup):
+                                               lookup=self.lookup,
+                                               xform=self.xform):
                 stream.append((kind, data, pos))
 
         return stream
@@ -289,7 +292,8 @@
             if start > offset:
                 text = source[offset:start]
                 for kind, data, pos in interpolate(text, self.filepath, lineno,
-                                                   lookup=self.lookup):
+                                                   lookup=self.lookup,
+                                                   xform=self.xform):
                     stream.append((kind, data, pos))
                 lineno += len(text.splitlines())
 
@@ -324,7 +328,8 @@
         if offset < len(source):
             text = source[offset:].replace('\\#', '#')
             for kind, data, pos in interpolate(text, self.filepath, lineno,
-                                               lookup=self.lookup):
+                                               lookup=self.lookup, 
+                                               xform=self.xform):
                 stream.append((kind, data, pos))
 
         return stream
Index: template/loader.py
===================================================================
--- template/loader.py	(Revision 3)
+++ template/loader.py	(Arbeitskopie)
@@ -19,7 +19,8 @@
 except ImportError:
     import dummy_threading as threading
 
-from genshi.template.base import TemplateError
+from genshi.template.astutil import ASTTransformer
+from genshi.template.base import TemplateError, TemplateRuntimeError
 from genshi.util import LRUCache
 
 __all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package',
@@ -78,7 +79,8 @@
     """
     def __init__(self, search_path=None, auto_reload=False,
                  default_encoding=None, max_cache_size=25, default_class=None,
-                 variable_lookup='strict', allow_exec=True, callback=None):
+                 variable_lookup='strict', allow_exec=True, callback=None,
+                 xform=None):
         """Create the template laoder.
         
         :param search_path: a list of absolute path names that should be
@@ -104,6 +106,17 @@
                          is passed the template object as only argument. This
                          callback can be used for example to add any desired
                          filters to the template
+        :param xform: a dict in the form of 
+        
+                      { 'eval' : <class>, 'exec' : <class> }
+                      
+                      where <class> is the respective AST transformer class that
+                      should be applied to the code representing either a python
+                      expression or executable python code; if `None`, or either
+                      of the two is omitted, then the default transformation is
+                      chosen depending on the current execution mode, which is 
+                      either 'eval' or 'exec'.
+
         :see: `LenientLookup`, `StrictLookup`
         
         :note: Changed in 0.5: Added the `allow_exec` argument
@@ -120,6 +133,15 @@
         """Whether templates should be reloaded when the underlying file is
         changed"""
 
+        if xform is not None :
+            if 'eval' in xform and not ASTTransformer in xform['eval'].__mro__:
+                raise TemplateRuntimeError("xform['eval'] must inherit from "
+                                           "ASTTransformer.")
+            if 'exec' in xform and not ASTTransformer in xform['exec'].__mro__:
+                raise TemplateRuntimeError("xform['exec'] must inherit from "
+                                           "ASTTransformer.")
+
+        self.xform = xform
         self.default_encoding = default_encoding
         self.default_class = default_class or MarkupTemplate
         self.variable_lookup = variable_lookup
@@ -131,7 +153,8 @@
         self._uptodate = {}
         self._lock = threading.RLock()
 
-    def load(self, filename, relative_to=None, cls=None, encoding=None):
+    def load(self, filename, relative_to=None, cls=None, encoding=None,
+             xform=None):
         """Load the template with the given name.
         
         If the `filename` parameter is relative, this method searches the
@@ -158,6 +181,17 @@
         :param cls: the class of the template object to instantiate
         :param encoding: the encoding of the template to load; defaults to the
                          ``default_encoding`` of the loader instance
+        :param xform: a dict in the form of 
+        
+                      { 'eval' : <class>, 'exec' : <class> }
+                      
+                      where <class> is the respective AST transformer class that
+                      should be applied to the code representing either a python
+                      expression or executable python code; if `None`, or either
+                      of the two is omitted, then the default transformation is
+                      chosen depending on the current execution mode, which is 
+                      either 'eval' or 'exec'.
+
         :return: the loaded `Template` instance
         :raises TemplateNotFound: if a template with the given name could not
                                   be found
@@ -166,6 +200,9 @@
             cls = self.default_class
         search_path = self.search_path
 
+        if xform is None:
+            xform = self.xform
+
         # Make the filename relative to the template file its being loaded
         # from, but only if that file is specified as a relative path, or no
         # search path has been set up
@@ -224,7 +261,8 @@
                             # search path
                             filename = filepath
                         tmpl = self._instantiate(cls, fileobj, filepath,
-                                                 filename, encoding=encoding)
+                                                 filename, encoding=encoding,
+                                                 xform=xform)
                         if self.callback:
                             self.callback(tmpl)
                         self._cache[cachekey] = tmpl
@@ -239,7 +277,8 @@
         finally:
             self._lock.release()
 
-    def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
+    def _instantiate(self, cls, fileobj, filepath, filename, encoding=None,
+                     xform=None):
         """Instantiate and return the `Template` object based on the given
         class and parameters.
         
@@ -255,6 +294,17 @@
                          path
         :param encoding: the encoding of the template to load; defaults to the
                          ``default_encoding`` of the loader instance
+        :param xform: a dict in the form of 
+        
+                      { 'eval' : <class>, 'exec' : <class> }
+                      
+                      where <class> is the respective AST transformer class that
+                      should be applied to the code representing either a python
+                      expression or executable python code; if `None`, or either
+                      of the two is omitted, then the default transformation is
+                      chosen depending on the current execution mode, which is 
+                      either 'eval' or 'exec'.
+
         :return: the loaded `Template` instance
         :rtype: `Template`
         """
@@ -262,7 +312,7 @@
             encoding = self.default_encoding
         return cls(fileobj, filepath=filepath, filename=filename, loader=self,
                    encoding=encoding, lookup=self.variable_lookup,
-                   allow_exec=self.allow_exec)
+                   allow_exec=self.allow_exec, xform=xform)
 
     @staticmethod
     def directory(path):
Index: template/interpolation.py
===================================================================
--- template/interpolation.py	(Revision 3)
+++ template/interpolation.py	(Arbeitskopie)
@@ -37,7 +37,8 @@
 ))
 
 
-def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict'):
+def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict',
+                xform=None):
     """Parse the given string and extract expressions.
     
     This function is a generator that yields `TEXT` events for literal strings,
@@ -58,6 +59,8 @@
                    (optional)
     :param lookup: the variable lookup mechanism; either "lenient" (the
                    default), "strict", or a custom lookup class
+    :param xform:  a dict containing class references to AST transformers being
+                   applied on evaluation of expressions.
     :return: a list of `TEXT` and `EXPR` events
     :raise TemplateSyntaxError: when a syntax error in an expression is
                                 encountered
@@ -75,7 +78,7 @@
             if chunk:
                 try:
                     expr = Expression(chunk.strip(), pos[0], pos[1],
-                                      lookup=lookup)
+                                      lookup=lookup, xform=xform)
                     yield EXPR, expr, tuple(pos)
                 except SyntaxError, err:
                     raise TemplateSyntaxError(err, filepath, pos[1],
