changeset 610:6a37018199fd

* 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.
author cmlenz
date Mon, 27 Aug 2007 23:20:47 +0000
parents 237050080827
children 16b1be35c265
files ChangeLog doc/xml-templates.txt genshi/template/base.py genshi/template/markup.py genshi/template/tests/markup.py genshi/template/tests/text.py genshi/template/text.py
diffstat 7 files changed, 84 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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
 ``<xi:include />`` 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 ``<xi:include>`` 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
+
+  <xi:include href="myscript.js" parse="text" />
+
+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:
 
 --------
--- 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='<string>', 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 = '<string>'
         self.msg = message #: the error message string
         if filename != '<string>' 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='<string>', 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='<string>', 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:
--- 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))
 
--- a/genshi/template/tests/markup.py
+++ b/genshi/template/tests/markup.py
@@ -244,6 +244,12 @@
           <Item/>
         </Test>""", str(tmpl.generate()))
 
+    def test_include_without_loader(self):
+        xml = """<html xmlns:xi="http://www.w3.org/2001/XInclude">
+          <xi:include href="oops.html" />
+        </html>"""
+        self.assertRaises(TemplateSyntaxError, MarkupTemplate, xml)
+
     def test_include_in_loop(self):
         dirname = tempfile.mkdtemp(suffix='genshi_test')
         try:
--- 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()
--- 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:
Copyright (C) 2012-2017 Edgewall Software