# HG changeset patch # User cmlenz # Date 1188256847 0 # Node ID 5e358de79e4c87d55242074da7be3a4dfa1f77ba # Parent 6d4877844e2880f2c3c4c911c07fa0c468bd37c4 * XInclude elements in markup templates now support the `parse` attribute; when set to "xml" (the default), the include is processed as before, but when set to "text", the included template is parsed as a text template using the new syntax (ticket #101). * If an include is found when parsing a template, but no template loader has been specified, a `TemplateSyntaxError` is raised. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -29,6 +29,12 @@ * Text templates now default to rendering as plain text; it is no longer necessary to explicitly specify the "text" method to the `render()` or `serialize()` method of the generated markup stream. + * XInclude elements in markup templates now support the `parse` attribute; when + set to "xml" (the default), the include is processed as before, but when set + to "text", the included template is parsed as a text template using the new + syntax (ticket #101). + * If an include is found when parsing a template, but no template loader has + been specified, a `TemplateSyntaxError` is raised. Version 0.4.4 diff --git a/doc/xml-templates.txt b/doc/xml-templates.txt --- a/doc/xml-templates.txt +++ b/doc/xml-templates.txt @@ -601,6 +601,10 @@ .. _`xinclude specification`: http://www.w3.org/TR/xinclude/ + +Dynamic Includes +================ + Incudes in Genshi are fully dynamic: Just like normal attributes, the `href` attribute accepts expressions, and directives_ can be used on the ```` element just as on any other element, meaning you can do @@ -612,6 +616,23 @@ py:for="name in ('foo', 'bar', 'baz')" /> +Including Text Templates +======================== + +The ``parse`` attribute of the ```` element can be used to specify +whether the included template is an XML template or a text template (using the +new syntax added in Genshi 0.5): + +.. code-block:: genshi + + + +This example would load the ``myscript.js`` file as a ``NewTextTemplate``. See +`text templates`_ for details on the syntax of text templates. + +.. _`text templates`: text-templates.html + + .. _comments: -------- diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -39,7 +39,7 @@ class TemplateError(Exception): """Base exception class for errors related to template processing.""" - def __init__(self, message, filename='', lineno=-1, offset=-1): + def __init__(self, message, filename=None, lineno=-1, offset=-1): """Create the exception. :param message: the error message @@ -48,6 +48,8 @@ occurred :param offset: the column number at which the error occurred """ + if filename is None: + filename = '' self.msg = message #: the error message string if filename != '' or lineno >= 0: message = '%s (%s, line %d)' % (self.msg, filename, lineno) @@ -62,7 +64,7 @@ error, or the template is not well-formed. """ - def __init__(self, message, filename='', lineno=-1, offset=-1): + def __init__(self, message, filename=None, lineno=-1, offset=-1): """Create the exception :param message: the error message @@ -84,7 +86,7 @@ with a local name that doesn't match any registered directive. """ - def __init__(self, name, filename='', lineno=-1): + def __init__(self, name, filename=None, lineno=-1): """Create the exception :param name: the name of the directive @@ -333,6 +335,10 @@ self.lookup = lookup self.allow_exec = allow_exec + self.filters = [self._flatten, self._eval, self._exec] + if loader: + self.filters.append(self._include) + if isinstance(source, basestring): source = StringIO(source) else: @@ -341,9 +347,6 @@ self.stream = list(self._prepare(self._parse(source, encoding))) except ParseError, e: raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) - self.filters = [self._flatten, self._eval, self._exec] - if loader: - self.filters.append(self._include) def __repr__(self): return '<%s "%s">' % (self.__class__.__name__, self.filename) @@ -386,7 +389,7 @@ yield event else: if kind is INCLUDE: - href, fallback = data + href, cls, fallback = data if isinstance(href, basestring) and \ not getattr(self.loader, 'auto_reload', True): # If the path to the included template is static, and @@ -394,7 +397,7 @@ # the template is inlined into the stream try: tmpl = self.loader.load(href, relative_to=pos[0], - cls=self.__class__) + cls=cls or self.__class__) for event in tmpl.stream: yield event except TemplateNotFound: @@ -513,7 +516,7 @@ for event in stream: if event[0] is INCLUDE: - href, fallback = event[1] + href, cls, fallback = event[1] if not isinstance(href, basestring): parts = [] for subkind, subdata, subpos in self._eval(href, ctxt): @@ -522,7 +525,7 @@ href = u''.join([x for x in parts if x is not None]) try: tmpl = self.loader.load(href, relative_to=event[2][0], - cls=self.__class__) + cls=cls or self.__class__) for event in tmpl.generate(ctxt): yield event except TemplateNotFound: diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -24,6 +24,7 @@ from genshi.template.eval import Suite from genshi.template.interpolation import interpolate from genshi.template.directives import * +from genshi.template.text import NewTextTemplate __all__ = ['MarkupTemplate'] __docformat__ = 'restructuredtext en' @@ -141,13 +142,17 @@ dirmap[(depth, tag)] = (directives, len(stream), strip) if tag in self.XINCLUDE_NAMESPACE: + if self._include not in self.filters: + raise TemplateSyntaxError('Include found but no ' + 'template loader specified', + self.filepath, *pos[1:]) if tag.localname == 'include': include_href = new_attrs.get('href') if not include_href: raise TemplateSyntaxError('Include misses required ' 'attribute "href"', self.filepath, *pos[1:]) - includes.append(include_href) + includes.append((include_href, new_attrs.get('parse'))) streams.append([]) elif tag.localname == 'fallback': streams.append([]) @@ -170,7 +175,17 @@ streams.pop() # discard anything between the include tags # and the fallback element stream = streams[-1] - stream.append((INCLUDE, (includes.pop(), fallback), pos)) + href, parse = includes.pop() + try: + cls = { + 'xml': MarkupTemplate, + 'text': NewTextTemplate + }[parse or 'xml'] + except KeyError: + raise TemplateSyntaxError('Invalid value for "parse" ' + 'attribute of include', + self.filepath, *pos[1:]) + stream.append((INCLUDE, (href, cls, fallback), pos)) else: stream.append((kind, data, pos)) 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 @@ -244,6 +244,12 @@ """, str(tmpl.generate())) + def test_include_without_loader(self): + xml = """ + + """ + self.assertRaises(TemplateSyntaxError, MarkupTemplate, xml) + def test_include_in_loop(self): dirname = tempfile.mkdtemp(suffix='genshi_test') try: diff --git a/genshi/template/tests/text.py b/genshi/template/tests/text.py --- a/genshi/template/tests/text.py +++ b/genshi/template/tests/text.py @@ -17,6 +17,7 @@ import tempfile import unittest +from genshi.template.base import TemplateSyntaxError from genshi.template.loader import TemplateLoader from genshi.template.text import OldTextTemplate, NewTextTemplate @@ -111,6 +112,10 @@ ----- Included data above this line -----""", tmpl.generate().render()) + def test_include_without_loader(self): + text = '#include "oops.html"' + self.assertRaises(TemplateSyntaxError, OldTextTemplate, text) + class NewTextTemplateTestCase(unittest.TestCase): """Tests for text template processing.""" @@ -231,6 +236,10 @@ Included ----- Included data above this line -----""", tmpl.generate().render()) + def test_include_without_loader(self): + text = '{% include "oops.html" %}' + self.assertRaises(TemplateSyntaxError, NewTextTemplate, text) + def suite(): suite = unittest.TestSuite() diff --git a/genshi/template/text.py b/genshi/template/text.py --- a/genshi/template/text.py +++ b/genshi/template/text.py @@ -28,7 +28,8 @@ import re -from genshi.template.base import BadDirectiveError, Template, EXEC, INCLUDE, SUB +from genshi.template.base import BadDirectiveError, Template, \ + TemplateSyntaxError, EXEC, INCLUDE, SUB from genshi.template.eval import Suite from genshi.template.directives import * from genshi.template.directives import Directive, _apply_directives @@ -186,8 +187,12 @@ command, value = mo.group(2, 3) if command == 'include': + if self._include not in self.filters: + raise TemplateSyntaxError('Include found but no template ' + 'loader specified', self.filepath, + lineno) pos = (self.filename, lineno, 0) - stream.append((INCLUDE, (value.strip(), []), pos)) + stream.append((INCLUDE, (value.strip(), None, []), pos)) elif command == 'python': if not self.allow_exec: @@ -306,8 +311,12 @@ stream[start_offset:] = [(SUB, ([directive], substream), (self.filepath, lineno, 0))] elif command == 'include': + if self._include not in self.filters: + raise TemplateSyntaxError('Include found but no template ' + 'loader specified', self.filepath, + lineno) pos = (self.filename, lineno, 0) - stream.append((INCLUDE, (value.strip(), []), pos)) + stream.append((INCLUDE, (value.strip(), None, []), pos)) elif command != '#': cls = self._dir_by_name.get(command) if cls is None: