# HG changeset patch # User hodgestar # Date 1395237406 0 # Node ID 348ba73df25c22556efe32cd08c95ea84d67f134 # Parent fa0e84724feeefeb68982f1d16f0961759dd83fa Merge r1257 from trunk (fix for infinite template inlining). diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -451,8 +451,7 @@ @property def stream(self): if not self._prepared: - self._stream = list(self._prepare(self._stream)) - self._prepared = True + self._prepare_self() return self._stream def _parse(self, source, encoding): @@ -469,12 +468,19 @@ """ raise NotImplementedError - def _prepare(self, stream): + def _prepare_self(self, inlined=None): + if not self._prepared: + self._stream = list(self._prepare(self._stream, inlined)) + self._prepared = True + + def _prepare(self, stream, inlined): """Call the `attach` method of every directive found in the template. :param stream: the event stream of the template """ from genshi.template.loader import TemplateNotFound + if inlined is None: + inlined = set((self.filepath,)) for kind, data, pos in stream: if kind is SUB: @@ -485,7 +491,7 @@ namespaces, pos) if directive: directives.append(directive) - substream = self._prepare(substream) + substream = self._prepare(substream, inlined) if directives: yield kind, (directives, list(substream)), pos else: @@ -494,27 +500,42 @@ else: if kind is INCLUDE: href, cls, fallback = data - if isinstance(href, basestring) and \ - not getattr(self.loader, 'auto_reload', True): + tmpl_inlined = False + if (isinstance(href, basestring) and + not getattr(self.loader, 'auto_reload', True)): # If the path to the included template is static, and # auto-reloading is disabled on the template loader, - # the template is inlined into the stream + # the template is inlined into the stream provided it + # is not already in the stack of templates being + # processed. + tmpl = None try: tmpl = self.loader.load(href, relative_to=pos[0], cls=cls or self.__class__) - for event in tmpl.stream: - yield event except TemplateNotFound: if fallback is None: raise - for event in self._prepare(fallback): + if tmpl is not None: + if tmpl.filepath not in inlined: + inlined.add(tmpl.filepath) + tmpl._prepare_self(inlined) + for event in tmpl.stream: + yield event + inlined.discard(tmpl.filepath) + tmpl_inlined = True + else: + for event in self._prepare(fallback, inlined): yield event + tmpl_inlined = True + if tmpl_inlined: continue - elif fallback: + if fallback: # Otherwise the include is performed at run time - data = href, cls, list(self._prepare(fallback)) - - yield kind, data, pos + data = href, cls, list( + self._prepare(fallback, inlined)) + yield kind, data, pos + else: + yield kind, data, pos def generate(self, *args, **kwargs): """Apply the template to the given context data. diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -284,10 +284,10 @@ yield kind, data, pos - def _prepare(self, stream): - return Template._prepare(self, - self._extract_includes(self._interpolate_attrs(stream)) - ) + def _prepare(self, stream, inlined=None): + return Template._prepare( + self, self._extract_includes(self._interpolate_attrs(stream)), + inlined=inlined) def add_directives(self, namespace, factory): """Register a custom `DirectiveFactory` for a given namespace. 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 @@ -580,7 +580,33 @@ finally: shutil.rmtree(dirname) - def test_allow_exec_false(self): + def test_include_inline_recursive(self): + dirname = tempfile.mkdtemp(suffix='genshi_test') + try: + file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w') + try: + file1.write( + '
' + '$depth' + '' + '' + '' + '
' + ) + finally: + file1.close() + + loader = TemplateLoader([dirname], auto_reload=False) + tmpl = loader.load(os.path.join(dirname, 'tmpl1.html')) + self.assertEqual( + "
0
1
2
", + tmpl.generate(depth=0).render(encoding=None)) + finally: + shutil.rmtree(dirname) + + def test_allow_exec_false(self): xml = ("""