# 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 @@
  • 1
  • 2
  • 3
  • """ - 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__':