# HG changeset patch
# User cmlenz
# Date 1164209918 0
# Node ID 37e4b4bb0b53047c7f6db83ed1e91d2326574e24
# Parent fe40d34fb71d07c8bacf0860c35b0fb5dc2852f7
Parse template includes at parse time to avoid some runtime overhead.
diff --git a/genshi/filters.py b/genshi/filters.py
--- a/genshi/filters.py
+++ b/genshi/filters.py
@@ -19,10 +19,10 @@
from sets import ImmutableSet as frozenset
import re
-from genshi.core import Attrs, Namespace, stripentities
-from genshi.core import END, END_NS, START, START_NS, TEXT
+from genshi.core import Attrs, stripentities
+from genshi.core import END, START, TEXT
-__all__ = ['HTMLFormFiller', 'HTMLSanitizer', 'IncludeFilter']
+__all__ = ['HTMLFormFiller', 'HTMLSanitizer']
class HTMLFormFiller(object):
@@ -284,78 +284,3 @@
else:
if not waiting_for:
yield kind, data, pos
-
-
-class IncludeFilter(object):
- """Template filter providing (very) basic XInclude support
- (see http://www.w3.org/TR/xinclude/) in templates.
- """
-
- NAMESPACE = Namespace('http://www.w3.org/2001/XInclude')
-
- def __init__(self, loader):
- """Initialize the filter.
-
- @param loader: the `TemplateLoader` to use for resolving references to
- external template files
- """
- self.loader = loader
-
- def __call__(self, stream, ctxt=None):
- """Filter the stream, processing any XInclude directives it may
- contain.
-
- @param stream: the markup event stream to filter
- @param ctxt: the template context
- """
- from genshi.template import TemplateError, TemplateNotFound
-
- namespace = self.NAMESPACE
- ns_prefixes = []
- in_fallback = False
- include_href = fallback_stream = None
-
- for kind, data, pos in stream:
-
- if kind is START and not in_fallback and data[0] in namespace:
- tag, attrs = data
- if tag.localname == 'include':
- include_href = attrs.get('href')
- elif tag.localname == 'fallback':
- in_fallback = True
- fallback_stream = []
-
- elif kind is END and data in namespace:
- if data.localname == 'include':
- try:
- if not include_href:
- raise TemplateError('Include misses required '
- 'attribute "href"')
- template = self.loader.load(include_href,
- relative_to=pos[0])
- for event in template.generate(ctxt):
- yield event
-
- except TemplateNotFound:
- if fallback_stream is None:
- raise
- for event in fallback_stream:
- yield event
-
- include_href = None
- fallback_stream = None
-
- elif data.localname == 'fallback':
- in_fallback = False
-
- elif in_fallback:
- fallback_stream.append((kind, data, pos))
-
- elif kind is START_NS and data[1] == namespace:
- ns_prefixes.append(data[0])
-
- elif kind is END_NS and data in ns_prefixes:
- ns_prefixes.pop()
-
- else:
- yield kind, data, pos
diff --git a/genshi/path.py b/genshi/path.py
--- a/genshi/path.py
+++ b/genshi/path.py
@@ -237,9 +237,9 @@
elif steps[cursor][0] is ATTRIBUTE:
# If the axis of the next location step is the
- # attribute axis, we need to move on to
- # processing that step without waiting for the
- # next markup event
+ # attribute axis, we need to move on to processing
+ # that step without waiting for the next markup
+ # event
continue
# We're done with this step if it's the last step or the
diff --git a/genshi/template/core.py b/genshi/template/core.py
--- a/genshi/template/core.py
+++ b/genshi/template/core.py
@@ -180,6 +180,7 @@
self.filepath = os.path.join(basedir, filename)
else:
self.filepath = filename
+ self.loader = loader
self.filters = [self._flatten, self._eval]
self.stream = list(self._prepare(self._parse(encoding)))
diff --git a/genshi/template/loader.py b/genshi/template/loader.py
--- a/genshi/template/loader.py
+++ b/genshi/template/loader.py
@@ -20,7 +20,6 @@
import dummy_threading as threading
from genshi.template.core import TemplateError
-from genshi.template.markup import MarkupTemplate
from genshi.util import LRUCache
__all__ = ['TemplateLoader', 'TemplateNotFound']
@@ -53,6 +52,7 @@
template has already been loaded. If not, it attempts to locate the
template file, and returns the corresponding `Template` object:
+ >>> from genshi.template import MarkupTemplate
>>> template = loader.load(os.path.basename(path))
>>> isinstance(template, MarkupTemplate)
True
@@ -66,7 +66,7 @@
>>> os.remove(path)
"""
def __init__(self, search_path=None, auto_reload=False,
- default_encoding=None, max_cache_size=25):
+ default_encoding=None, max_cache_size=25, default_class=None):
"""Create the template laoder.
@param search_path: a list of absolute path names that should be
@@ -78,7 +78,11 @@
templates; defaults to UTF-8
@param max_cache_size: the maximum number of templates to keep in the
cache
+ @param default_class: the default `Template` subclass to use when
+ instantiating templates
"""
+ from genshi.template.markup import MarkupTemplate
+
self.search_path = search_path
if self.search_path is None:
self.search_path = []
@@ -86,12 +90,12 @@
self.search_path = [self.search_path]
self.auto_reload = auto_reload
self.default_encoding = default_encoding
+ self.default_class = default_class or MarkupTemplate
self._cache = LRUCache(max_cache_size)
self._mtime = {}
self._lock = threading.Lock()
- def load(self, filename, relative_to=None, cls=MarkupTemplate,
- encoding=None):
+ def load(self, filename, relative_to=None, cls=None, encoding=None):
"""Load the template with the given name.
If the `filename` parameter is relative, this method searches the search
@@ -119,6 +123,8 @@
@param encoding: the encoding of the template to load; defaults to the
`default_encoding` of the loader instance
"""
+ if cls is None:
+ cls = self.default_class
if encoding is None:
encoding = self.default_encoding
if relative_to and not os.path.isabs(relative_to):
diff --git a/genshi/template/markup.py b/genshi/template/markup.py
--- a/genshi/template/markup.py
+++ b/genshi/template/markup.py
@@ -15,12 +15,12 @@
from itertools import chain
-from genshi.core import Attrs, Namespace, Stream
+from genshi.core import Attrs, Namespace, Stream, StreamEventKind
from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT
-from genshi.filters import IncludeFilter
from genshi.input import XMLParser
-from genshi.template.core import BadDirectiveError, Template, _apply_directives
-from genshi.template.core import SUB
+from genshi.template.core import BadDirectiveError, Template, \
+ _apply_directives, SUB
+from genshi.template.loader import TemplateNotFound
from genshi.template.directives import *
@@ -35,7 +35,10 @@
123
"""
- NAMESPACE = Namespace('http://genshi.edgewall.org/')
+ INCLUDE = StreamEventKind('INCLUDE')
+
+ DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/')
+ XINCLUDE_NAMESPACE = Namespace('http://www.w3.org/2001/XInclude')
directives = [('def', DefDirective),
('match', MatchDirective),
@@ -58,7 +61,7 @@
self.filters.append(self._match)
if loader:
- self.filters.append(IncludeFilter(loader))
+ self.filters.append(self._include)
def _parse(self, encoding):
"""Parse the template from an XML document."""
@@ -66,6 +69,9 @@
dirmap = {} # temporary mapping of directives to elements
ns_prefix = {}
depth = 0
+ in_fallback = False
+ fallback_stream = None
+ include_href = None
for kind, data, pos in XMLParser(self.source, filename=self.filename,
encoding=encoding):
@@ -74,32 +80,34 @@
# Strip out the namespace declaration for template directives
prefix, uri = data
ns_prefix[prefix] = uri
- if uri != self.NAMESPACE:
+ if uri not in (self.DIRECTIVE_NAMESPACE,
+ self.XINCLUDE_NAMESPACE):
stream.append((kind, data, pos))
elif kind is END_NS:
uri = ns_prefix.pop(data, None)
- if uri and uri != self.NAMESPACE:
+ if uri and uri not in (self.DIRECTIVE_NAMESPACE,
+ self.XINCLUDE_NAMESPACE):
stream.append((kind, data, pos))
elif kind is START:
# Record any directive attributes in start tags
- tag, attrib = data
+ tag, attrs = data
directives = []
strip = False
- if tag in self.NAMESPACE:
+ if tag in self.DIRECTIVE_NAMESPACE:
cls = self._dir_by_name.get(tag.localname)
if cls is None:
raise BadDirectiveError(tag.localname, self.filepath,
pos[1])
- value = attrib.get(getattr(cls, 'ATTRIBUTE', None), '')
+ value = attrs.get(getattr(cls, 'ATTRIBUTE', None), '')
directives.append((cls, value, ns_prefix.copy(), pos))
strip = True
- new_attrib = []
- for name, value in attrib:
- if name in self.NAMESPACE:
+ new_attrs = []
+ for name, value in attrs:
+ if name in self.DIRECTIVE_NAMESPACE:
cls = self._dir_by_name.get(name.localname)
if cls is None:
raise BadDirectiveError(name.localname,
@@ -113,19 +121,41 @@
value = value[0][1]
else:
value = [(TEXT, u'', pos)]
- new_attrib.append((name, value))
+ new_attrs.append((name, value))
+ new_attrs = Attrs(new_attrs)
if directives:
index = self._dir_order.index
directives.sort(lambda a, b: cmp(index(a[0]), index(b[0])))
dirmap[(depth, tag)] = (directives, len(stream), strip)
- stream.append((kind, (tag, Attrs(new_attrib)), pos))
+ if tag in self.XINCLUDE_NAMESPACE:
+ if tag.localname == 'include':
+ include_href = new_attrs.get('href')
+ if not include_href:
+ raise TemplateSyntaxError('Include misses required '
+ 'attribute "href"', *pos)
+ elif tag.localname == 'fallback':
+ in_fallback = True
+ fallback_stream = []
+
+ else:
+ stream.append((kind, (tag, new_attrs), pos))
+
depth += 1
elif kind is END:
depth -= 1
- stream.append((kind, data, pos))
+
+ if in_fallback:
+ if data == self.XINCLUDE_NAMESPACE['fallback']:
+ in_fallback = False
+ else:
+ fallback_stream.append((kind, data, pos))
+ elif data == self.XINCLUDE_NAMESPACE['include']:
+ stream.append((INCLUDE, (include_href, fallback_stream), pos))
+ else:
+ stream.append((kind, data, pos))
# If there have have directive attributes with the corresponding
# start tag, move the events inbetween into a "subprogram"
@@ -151,6 +181,31 @@
return stream
+ def _include(self, stream, ctxt):
+ """Internal stream filter that performs inclusion of external
+ template files.
+ """
+ for event in stream:
+ if event[0] is INCLUDE:
+ href, fallback = event[1]
+ if not isinstance(href, basestring):
+ parts = []
+ for subkind, subdata, subpos in self._eval(href, ctxt):
+ if subkind is TEXT:
+ parts.append(subdata)
+ href = u''.join([x for x in parts if x is not None])
+ try:
+ tmpl = self.loader.load(href, relative_to=event[2][0])
+ for event in tmpl.generate(ctxt):
+ yield event
+ except TemplateNotFound:
+ if fallback is None:
+ raise
+ for event in fallback:
+ yield event
+ else:
+ yield event
+
def _match(self, stream, ctxt, match_templates=None):
"""Internal stream filter that applies any defined match templates
to the stream.
@@ -196,9 +251,7 @@
# corresponding to this start event is encountered
content = chain([event], self._match(_strip(stream), ctxt),
tail)
- for filter_ in self.filters[3:]:
- content = filter_(content, ctxt)
- content = list(content)
+ content = list(self._include(content, ctxt))
for test in [mt[0] for mt in match_templates]:
test(tail[0], namespaces, ctxt, updateonly=True)
@@ -223,3 +276,6 @@
else: # no matches
yield event
+
+
+INCLUDE = MarkupTemplate.INCLUDE
diff --git a/genshi/template/tests/markup.py b/genshi/template/tests/markup.py
--- a/genshi/template/tests/markup.py
+++ b/genshi/template/tests/markup.py
@@ -12,11 +12,15 @@
# history and logs, available at http://genshi.edgewall.org/log/.
import doctest
+import os
+import shutil
import sys
+import tempfile
import unittest
from genshi.core import Markup
from genshi.template.core import BadDirectiveError, TemplateSyntaxError
+from genshi.template.loader import TemplateLoader
from genshi.template.markup import MarkupTemplate
@@ -179,6 +183,87 @@
\xf6
""", unicode(tmpl.generate()))
+ def test_include_in_loop(self):
+ dirname = tempfile.mkdtemp(suffix='genshi_test')
+ try:
+ file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+ try:
+ file1.write("""Included $idx
""")
+ finally:
+ file1.close()
+
+ file2 = open(os.path.join(dirname, 'tmpl2.html'), 'w')
+ try:
+ file2.write("""
+
+ """)
+ finally:
+ file2.close()
+
+ loader = TemplateLoader([dirname])
+ tmpl = loader.load('tmpl2.html')
+ self.assertEqual("""
+ Included 0
Included 1
Included 2
+ """, tmpl.generate(name='tmpl1').render())
+ finally:
+ shutil.rmtree(dirname)
+
+ def test_dynamic_inlude_href(self):
+ dirname = tempfile.mkdtemp(suffix='genshi_test')
+ try:
+ file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+ try:
+ file1.write("""Included
""")
+ finally:
+ file1.close()
+
+ file2 = open(os.path.join(dirname, 'tmpl2.html'), 'w')
+ try:
+ file2.write("""
+
+ """)
+ finally:
+ file2.close()
+
+ loader = TemplateLoader([dirname])
+ tmpl = loader.load('tmpl2.html')
+ self.assertEqual("""
+ Included
+ """, tmpl.generate(name='tmpl1').render())
+ finally:
+ shutil.rmtree(dirname)
+
+ def test_select_inluded_elements(self):
+ dirname = tempfile.mkdtemp(suffix='genshi_test')
+ try:
+ file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+ try:
+ file1.write("""$item""")
+ finally:
+ file1.close()
+
+ file2 = open(os.path.join(dirname, 'tmpl2.html'), 'w')
+ try:
+ file2.write("""
+
+
+ """)
+ finally:
+ file2.close()
+
+ loader = TemplateLoader([dirname])
+ tmpl = loader.load('tmpl2.html')
+ self.assertEqual("""
+
+ """, tmpl.generate().render())
+ finally:
+ shutil.rmtree(dirname)
+
def suite():
suite = unittest.TestSuite()
diff --git a/genshi/tests/filters.py b/genshi/tests/filters.py
--- a/genshi/tests/filters.py
+++ b/genshi/tests/filters.py
@@ -12,16 +12,11 @@
# history and logs, available at http://genshi.edgewall.org/log/.
import doctest
-import os
-import shutil
-import tempfile
import unittest
from genshi import filters
-from genshi.core import Stream
from genshi.input import HTML, ParseError
from genshi.filters import HTMLFormFiller, HTMLSanitizer
-from genshi.template import TemplateLoader
class HTMLFormFillerTestCase(unittest.TestCase):
@@ -374,46 +369,11 @@
self.assertEquals(u'
', unicode(html | HTMLSanitizer()))
-class IncludeFilterTestCase(unittest.TestCase):
-
- def setUp(self):
- self.dirname = tempfile.mkdtemp(suffix='markup_test')
-
- def tearDown(self):
- shutil.rmtree(self.dirname)
-
- def test_select_inluded_elements(self):
- file1 = open(os.path.join(self.dirname, 'tmpl1.html'), 'w')
- try:
- file1.write("""$item""")
- finally:
- file1.close()
-
- file2 = open(os.path.join(self.dirname, 'tmpl2.html'), 'w')
- try:
- file2.write("""
-
-
- """)
- finally:
- file2.close()
-
- loader = TemplateLoader([self.dirname])
- tmpl = loader.load('tmpl2.html')
- self.assertEqual("""
-
- """, tmpl.generate().render())
-
-
def suite():
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(filters))
suite.addTest(unittest.makeSuite(HTMLFormFillerTestCase, 'test'))
suite.addTest(unittest.makeSuite(HTMLSanitizerTestCase, 'test'))
- suite.addTest(unittest.makeSuite(IncludeFilterTestCase, 'test'))
return suite
if __name__ == '__main__':