# HG changeset patch # User cmlenz # Date 1164810405 0 # Node ID b9fc7a1f76ca7c82c0cc5d2b8d2001440e341404 # Parent bf2068f5ef748d8c714aec0c46b9b1b206188912 Fix for #80: fallback only shown when the template to include wasn't found. In addition, the nesting of includes and fallback content should work correctly, and directives/expressions/etc inside fallback content are processed. Thanks to Christian Boos for the original patch and unit tests. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -31,6 +31,7 @@ to strings and file-like objects (ticket #69). * Improve handling of incorrectly nested tags in the HTML parser. * The builder API now accepts streams as children of elements and fragments. + * Template includes can you be nested inside fallback content. Version 0.3.5 diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -65,12 +65,11 @@ def _parse(self, source, encoding): """Parse the template from an XML document.""" - stream = [] # list of events of the "compiled" template + streams = [[]] # stacked lists of events of the "compiled" template dirmap = {} # temporary mapping of directives to elements ns_prefix = {} depth = 0 - in_fallback = False - fallback_stream = None + in_fallback = 0 include_href = None if not isinstance(source, Stream): @@ -78,6 +77,7 @@ encoding=encoding) for kind, data, pos in source: + stream = streams[-1] if kind is START_NS: # Strip out the namespace declaration for template directives @@ -138,9 +138,9 @@ if not include_href: raise TemplateSyntaxError('Include misses required ' 'attribute "href"', *pos) + streams.append([]) elif tag.localname == 'fallback': - in_fallback = True - fallback_stream = [] + in_fallback += 1 else: stream.append((kind, (tag, new_attrs), pos)) @@ -150,13 +150,12 @@ elif kind is END: depth -= 1 - if in_fallback: - if data == self.XINCLUDE_NAMESPACE['fallback']: - in_fallback = False - else: - fallback_stream.append((kind, data, pos)) + if in_fallback and data == self.XINCLUDE_NAMESPACE['fallback']: + in_fallback -= 1 elif data == self.XINCLUDE_NAMESPACE['include']: - stream.append((INCLUDE, (include_href, fallback_stream), pos)) + fallback = streams.pop() + stream = streams[-1] + stream.append((INCLUDE, (include_href, fallback), pos)) else: stream.append((kind, data, pos)) @@ -182,7 +181,14 @@ else: stream.append((kind, data, pos)) - return stream + assert len(streams) == 1 + return streams[0] + + def _prepare(self, stream): + for kind, data, pos in Template._prepare(self, stream): + if kind is INCLUDE: + data = data[0], list(self._prepare(data[1])) + yield kind, data, pos def _include(self, stream, ctxt): """Internal stream filter that performs inclusion of external @@ -204,6 +210,8 @@ except TemplateNotFound: if fallback is None: raise + for filter_ in self.filters: + fallback = filter_(iter(fallback), ctxt) for event in fallback: yield event else: 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 @@ -276,6 +276,130 @@ finally: shutil.rmtree(dirname) + def test_fallback_when_include_found(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(""" + + Missing + """) + finally: + file2.close() + + loader = TemplateLoader([dirname]) + tmpl = loader.load('tmpl2.html') + self.assertEqual(""" +
Included
+ """, tmpl.generate().render()) + finally: + shutil.rmtree(dirname) + + def test_fallback_when_include_not_found(self): + dirname = tempfile.mkdtemp(suffix='genshi_test') + try: + file2 = open(os.path.join(dirname, 'tmpl2.html'), 'w') + try: + file2.write(""" + + Missing + """) + finally: + file2.close() + + loader = TemplateLoader([dirname]) + tmpl = loader.load('tmpl2.html') + self.assertEqual(""" + Missing + """, tmpl.generate().render()) + finally: + shutil.rmtree(dirname) + + def test_include_in_fallback(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, 'tmpl3.html'), 'w') + try: + file2.write(""" + + + + Missing + + + + """) + finally: + file2.close() + + loader = TemplateLoader([dirname]) + tmpl = loader.load('tmpl3.html') + self.assertEqual(""" +
Included
+ """, tmpl.generate().render()) + finally: + shutil.rmtree(dirname) + + def test_nested_include_fallback(self): + dirname = tempfile.mkdtemp(suffix='genshi_test') + try: + file2 = open(os.path.join(dirname, 'tmpl3.html'), 'w') + try: + file2.write(""" + + + + Missing + + + + """) + finally: + file2.close() + + loader = TemplateLoader([dirname]) + tmpl = loader.load('tmpl3.html') + self.assertEqual(""" + Missing + """, tmpl.generate().render()) + finally: + shutil.rmtree(dirname) + + def test_include_fallback_with_directive(self): + dirname = tempfile.mkdtemp(suffix='genshi_test') + try: + file2 = open(os.path.join(dirname, 'tmpl2.html'), 'w') + try: + file2.write(""" + + tmpl1.html not found + + """) + finally: + file2.close() + + loader = TemplateLoader([dirname]) + tmpl = loader.load('tmpl2.html') + self.assertEqual(""" + tmpl1.html not found + """, tmpl.generate(debug=True).render()) + finally: + shutil.rmtree(dirname) + def suite(): suite = unittest.TestSuite()