# HG changeset patch # User athomas # Date 1199373901 0 # Node ID 8a9a7a8e9478f62ca2103e13610bc2ce2de7c51a # Parent 61d54ec0719237b15cb90e8150c29247e3378981 Add a stream filter to insert the XML DOCTYPE in the correct location (ie. after the XML declaration). Fixes #156. diff --git a/genshi/output.py b/genshi/output.py --- 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 diff --git a/genshi/tests/output.py b/genshi/tests/output.py --- 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('\n' + '\n', + output) + def test_doctype_in_stream(self): stream = Stream([(Stream.DOCTYPE, DocType.HTML_STRICT, (None, -1, -1))]) output = stream.render(XMLSerializer)