changeset 671:8a9a7a8e9478 trunk

Add a stream filter to insert the XML DOCTYPE in the correct location (ie. after the XML declaration). Fixes #156.
author athomas
date Thu, 03 Jan 2008 15:25:01 +0000
parents 61d54ec07192
children 571226acaeff
files genshi/output.py genshi/tests/output.py
diffstat 2 files changed, 53 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/genshi/output.py
+++ b/genshi/output.py
@@ -176,21 +176,17 @@
                                  stripped from the output
         :note: Changed in 0.4.2: The  `doctype` parameter can now be a string.
         """
-        self.preamble = []
-        if doctype:
-            if isinstance(doctype, basestring):
-                doctype = DocType.get(doctype)
-            self.preamble.append((DOCTYPE, doctype, (None, -1, -1)))
         self.filters = [EmptyTagFilter()]
         if strip_whitespace:
             self.filters.append(WhitespaceFilter(self._PRESERVE_SPACE))
         self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes))
+        if doctype:
+            self.filters.append(DocTypeInserter(doctype))
 
     def __call__(self, stream):
         have_decl = have_doctype = False
         in_cdata = False
 
-        stream = chain(self.preamble, stream)
         for filter_ in self.filters:
             stream = filter_(stream)
         for kind, data, pos in stream:
@@ -281,6 +277,8 @@
         namespace_prefixes = namespace_prefixes or {}
         namespace_prefixes['http://www.w3.org/1999/xhtml'] = ''
         self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes))
+        if doctype:
+            self.filters.append(DocTypeInserter(doctype))
 
     def __call__(self, stream):
         boolean_attrs = self._BOOLEAN_ATTRS
@@ -288,7 +286,6 @@
         have_doctype = False
         in_cdata = False
 
-        stream = chain(self.preamble, stream)
         for filter_ in self.filters:
             stream = filter_(stream)
         for kind, data, pos in stream:
@@ -379,6 +376,8 @@
         self.filters.append(NamespaceFlattener(prefixes={
             'http://www.w3.org/1999/xhtml': ''
         }))
+        if doctype:
+            self.filters.append(DocTypeInserter(doctype))
 
     def __call__(self, stream):
         boolean_attrs = self._BOOLEAN_ATTRS
@@ -387,7 +386,6 @@
         have_doctype = False
         noescape = False
 
-        stream = chain(self.preamble, stream)
         for filter_ in self.filters:
             stream = filter_(stream)
         for kind, data, pos in stream:
@@ -696,3 +694,41 @@
 
                 if kind:
                     yield kind, data, pos
+
+
+class DocTypeInserter(object):
+    """A filter that inserts the DOCTYPE declaration in the correct location,
+    after the XML declaration.
+    """
+    def __init__(self, doctype):
+        """Initialize the filter.
+
+        :param doctype: DOCTYPE as a string or DocType object.
+        """
+        if isinstance(doctype, basestring):
+            doctype = DocType.get(doctype)
+        self.doctype_event = (DOCTYPE, doctype, (None, -1, -1))
+
+    def __call__(self, stream):
+        buffer = []
+        doctype_inserted = False
+        for kind, data, pos in stream:
+            # Buffer whitespace TEXT and XML_DECL
+            if not doctype_inserted:
+                if kind is XML_DECL or (kind is TEXT and not data.strip()):
+                    buffer.append((kind, data, pos))
+                    continue
+
+                for event in buffer:
+                    yield event
+
+                yield self.doctype_event
+
+                doctype_inserted = True
+
+            yield (kind, data, pos)
+
+        if not doctype_inserted:
+            for event in buffer:
+                yield event
+            yield self.doctype_event
--- a/genshi/tests/output.py
+++ b/genshi/tests/output.py
@@ -23,6 +23,15 @@
 
 class XMLSerializerTestCase(unittest.TestCase):
 
+    def test_xml_serialiser_with_decl(self):
+        stream = Stream([(Stream.XML_DECL, ('1.0', None, -1), (None, -1, -1))])
+        output = stream.render(XMLSerializer, doctype='xhtml')
+        self.assertEqual('<?xml version="1.0"?>\n'
+                         '<!DOCTYPE html PUBLIC '
+                         '"-//W3C//DTD XHTML 1.0 Strict//EN" '
+                         '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n',
+                         output)
+
     def test_doctype_in_stream(self):
         stream = Stream([(Stream.DOCTYPE, DocType.HTML_STRICT, (None, -1, -1))])
         output = stream.render(XMLSerializer)
Copyright (C) 2012-2017 Edgewall Software