Mercurial > genshi > mirror
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)