drop xhtml content-type support (closes #2065651)
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Fri, 26 Apr 2013 12:10:37 +0200
changeset 8941 7b26fe71404f
parent 8940 ae898a084da2
child 8942 0f60f1061a2e
drop xhtml content-type support (closes #2065651) * HTMLStream does not care about xml any more * reqquest.demote_to_html and .xhtml_browser are deprecated * web config: drop force-html-content-type option * adjust tests
devtools/htmlparser.py
devtools/httptest.py
devtools/test/unittest_testlib.py
utils.py
view.py
web/data/cubicweb.edition.js
web/request.py
web/test/test_views.py
web/test/unittest_idownloadable.py
web/test/unittest_views_basecontrollers.py
web/test/unittest_views_baseviews.py
web/views/calendar.py
web/webconfig.py
--- a/devtools/htmlparser.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/devtools/htmlparser.py	Fri Apr 26 12:10:37 2013 +0200
@@ -39,7 +39,7 @@
         except etree.XMLSyntaxError as exc:
             def save_in(fname=''):
                 file(fname, 'w').write(data)
-            new_exc = AssertionError(u'invalid xml %s' % exc)
+            new_exc = AssertionError(u'invalid document: %s' % exc)
             new_exc.position = exc.position
             raise new_exc
 
--- a/devtools/httptest.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/devtools/httptest.py	Fri Apr 26 12:10:37 2013 +0200
@@ -78,10 +78,6 @@
     def pyro_enabled(self):
         return False
 
-    def load_configuration(self):
-        super(CubicWebServerConfig, self).load_configuration()
-        self.global_set_option('force-html-content-type', True)
-
 
 class CubicWebServerTC(CubicWebTC):
     """Class for running test web server. See :class:`CubicWebServerConfig`.
--- a/devtools/test/unittest_testlib.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/devtools/test/unittest_testlib.py	Fri Apr 26 12:10:37 2013 +0200
@@ -97,7 +97,9 @@
 class HTMLPageInfoTC(TestCase):
     """test cases for PageInfo"""
     def setUp(self):
-        parser = htmlparser.DTDValidator()
+        parser = htmlparser.HTMLValidator()
+        # disable cleanup that would remove doctype
+        parser.preprocess_data = lambda data: data
         self.page_info = parser.parse_string(HTML_PAGE2)
 
     def test_source1(self):
--- a/utils.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/utils.py	Fri Apr 26 12:10:37 2013 +0200
@@ -229,11 +229,8 @@
     jQuery(window).unload(unloadPageData);
     pageDataUnloaded = true;
 }'''
-    # Making <script> tag content work properly with all possible
-    # content-types (xml/html) and all possible browsers is very
-    # tricky, see http://www.hixie.ch/advocacy/xhtml for an in-depth discussion
-    xhtml_safe_script_opening = u'<script type="text/javascript"><!--//--><![CDATA[//><!--\n'
-    xhtml_safe_script_closing = u'\n//--><!]]></script>'
+    script_opening = u'<script type="text/javascript">\n'
+    script_closing = u'\n</script>'
 
     def __init__(self, req):
         super(HTMLHead, self).__init__()
@@ -344,14 +341,14 @@
         w = self.write
         # 1/ variable declaration if any
         if self.jsvars:
-            w(self.xhtml_safe_script_opening)
+            w(self.script_opening)
             for var, value, override in self.jsvars:
                 vardecl = u'%s = %s;' % (var, json.dumps(value))
                 if not override:
                     vardecl = (u'if (typeof %s == "undefined") {%s}' %
                                (var, vardecl))
                 w(vardecl + u'\n')
-            w(self.xhtml_safe_script_closing)
+            w(self.script_closing)
         # 2/ css files
         ie_cssfiles = ((x, (y, z)) for x, y, z in self.ie_cssfiles)
         if self.datadir_url and self._cw.vreg.config['concat-resources']:
@@ -397,9 +394,9 @@
                     w(xml_escape(script))
                     w(u'</pre>')
             else:
-                w(self.xhtml_safe_script_opening)
+                w(self.script_opening)
                 w(u'\n\n'.join(self.post_inlined_scripts))
-                w(self.xhtml_safe_script_closing)
+                w(self.script_closing)
         header = super(HTMLHead, self).getvalue()
         if skiphead:
             return header
@@ -422,20 +419,17 @@
         # main stream
         self.body = UStringIO()
         self.doctype = u''
-        # xmldecl and html opening tag
-        self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
-        self._namespaces = [('xmlns', 'http://www.w3.org/1999/xhtml'),
-                            ('xmlns:cubicweb','http://www.logilab.org/2008/cubicweb')]
-        self._htmlattrs = [('xml:lang', req.lang),
-                           ('lang', req.lang)]
+        self._htmlattrs = [('lang', req.lang)]
         # keep main_stream's reference on req for easier text/html demoting
         req.main_stream = self
 
+    @deprecated('[3.17] there are no namespaces in html, xhtml is not served any longer')
     def add_namespace(self, prefix, uri):
-        self._namespaces.append( (prefix, uri) )
+        pass
 
+    @deprecated('[3.17] there are no namespaces in html, xhtml is not served any longer')
     def set_namespaces(self, namespaces):
-        self._namespaces = namespaces
+        pass
 
     def add_htmlattr(self, attrname, attrvalue):
         self._htmlattrs.append( (attrname, attrvalue) )
@@ -443,10 +437,11 @@
     def set_htmlattrs(self, attrs):
         self._htmlattrs = attrs
 
-    def set_doctype(self, doctype, reset_xmldecl=True):
+    def set_doctype(self, doctype, reset_xmldecl=None):
         self.doctype = doctype
-        if reset_xmldecl:
-            self.xmldecl = u''
+        if reset_xmldecl is not None:
+            warn('[3.17] xhtml is no more supported',
+                 DeprecationWarning, stacklevel=2)
 
     def write(self, data):
         """StringIO interface: this method will be assigned to self.w
@@ -456,17 +451,17 @@
     @property
     def htmltag(self):
         attrs = ' '.join('%s="%s"' % (attr, xml_escape(value))
-                         for attr, value in (self._namespaces + self._htmlattrs))
+                         for attr, value in self._htmlattrs)
         if attrs:
             return '<html %s>' % attrs
         return '<html>'
 
     def getvalue(self):
         """writes HTML headers, closes </head> tag and writes HTML body"""
-        return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
-                                                 self.htmltag,
-                                                 self.head.getvalue(),
-                                                 self.body.getvalue())
+        return u'%s\n%s\n%s\n%s\n</html>' % (self.doctype,
+                                             self.htmltag,
+                                             self.head.getvalue(),
+                                             self.body.getvalue())
 
 try:
     # may not be there if cubicweb-web not installed
--- a/view.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/view.py	Fri Apr 26 12:10:37 2013 +0200
@@ -42,50 +42,11 @@
 NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
 NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
 
-CW_XHTML_EXTENSIONS = '''[
-  <!ATTLIST html xmlns:cubicweb CDATA  #FIXED \'http://www.logilab.org/2008/cubicweb\'  >
-
-<!ENTITY % coreattrs
- "id          ID            #IMPLIED
-  class       CDATA         #IMPLIED
-  style       CDATA         #IMPLIED
-  title       CDATA         #IMPLIED
+TRANSITIONAL_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
+TRANSITIONAL_DOCTYPE = TRANSITIONAL_DOCTYPE_NOEXT # bw compat
 
- cubicweb:accesskey         CDATA   #IMPLIED
- cubicweb:actualrql         CDATA   #IMPLIED
- cubicweb:dataurl           CDATA   #IMPLIED
- cubicweb:facetName         CDATA   #IMPLIED
- cubicweb:facetargs         CDATA   #IMPLIED
- cubicweb:fallbackvid       CDATA   #IMPLIED
- cubicweb:fname             CDATA   #IMPLIED
- cubicweb:initfunc          CDATA   #IMPLIED
- cubicweb:inputid           CDATA   #IMPLIED
- cubicweb:inputname         CDATA   #IMPLIED
- cubicweb:limit             CDATA   #IMPLIED
- cubicweb:loadtype          CDATA   #IMPLIED
- cubicweb:loadurl           CDATA   #IMPLIED
- cubicweb:maxlength         CDATA   #IMPLIED
- cubicweb:required          CDATA   #IMPLIED
- cubicweb:rooteid           CDATA   #IMPLIED
- cubicweb:rql               CDATA   #IMPLIED
- cubicweb:size              CDATA   #IMPLIED
- cubicweb:sortvalue         CDATA   #IMPLIED
- cubicweb:target            CDATA   #IMPLIED
- cubicweb:tindex            CDATA   #IMPLIED
- cubicweb:tlunit            CDATA   #IMPLIED
- cubicweb:type              CDATA   #IMPLIED
- cubicweb:unselimg          CDATA   #IMPLIED
- cubicweb:uselabel          CDATA   #IMPLIED
- cubicweb:value             CDATA   #IMPLIED
- cubicweb:variables         CDATA   #IMPLIED
- cubicweb:vid               CDATA   #IMPLIED
- cubicweb:wdgtype           CDATA   #IMPLIED
-  "> ] '''
-
-TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n' % CW_XHTML_EXTENSIONS
-TRANSITIONAL_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
-STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n' % CW_XHTML_EXTENSIONS
 STRICT_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
+STRICT_DOCTYPE = STRICT_DOCTYPE_NOEXT # bw compat
 
 # base view object ############################################################
 
@@ -510,11 +471,7 @@
     one to display error if the first one failed
     """
 
-    @property
-    def doctype(self):
-        if self._cw.xhtml_browser():
-            return STRICT_DOCTYPE
-        return STRICT_DOCTYPE_NOEXT
+    doctype = STRICT_DOCTYPE
 
     def set_stream(self, w=None):
         if self.w is not None:
--- a/web/data/cubicweb.edition.js	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/data/cubicweb.edition.js	Fri Apr 26 12:10:37 2013 +0200
@@ -543,7 +543,8 @@
  *
  * .. note::
  *
- *    this is a hack to make the XHTML compliant.
+ *    This was a hack to make form loop handling XHTML compliant.
+ *    Since we do not care about xhtml any longer, this may go away.
  *
  * .. note::
  *
@@ -551,8 +552,10 @@
  *
  * .. note::
  *
- *    there is a XHTML module allowing iframe elements but there
- *    is still the problem of the form's `target` attribute
+ *    The form's `target` attribute should probably become a simple data-target
+ *    immediately generated server-side.
+ *    Since we don't do xhtml any longer, the iframe should probably be either
+ *    reconsidered or at least emitted server-side.
  */
 function setFormsTarget(node) {
     var $node = jQuery(node || document.body);
--- a/web/request.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/request.py	Fri Apr 26 12:10:37 2013 +0200
@@ -42,7 +42,7 @@
 from cubicweb.dbapi import DBAPIRequest
 from cubicweb.uilib import remove_html_tags, js
 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
-from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
+from cubicweb.view import TRANSITIONAL_DOCTYPE_NOEXT
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
                           RequestError, StatusResponse)
 from cubicweb.web.httpcache import GMTOFFSET, get_validators
@@ -902,29 +902,26 @@
         values = _parse_accept_header(accepteds, value_parser, value_sort_key)
         return (raw_value for (raw_value, parsed_value, score) in values)
 
+    @deprecated('[3.17] demote_to_html is deprecated as we always serve html')
     def demote_to_html(self):
         """helper method to dynamically set request content type to text/html
 
         The global doctype and xmldec must also be changed otherwise the browser
         will display '<[' at the beginning of the page
         """
-        if not self.vreg.config['force-html-content-type']:
-            if not hasattr(self, 'main_stream'):
-                raise Exception("Can't demote to html from an ajax context. You "
-                                "should change force-html-content-type to yes "
-                                "in the instance configuration file.")
-            self.set_content_type('text/html')
-            self.main_stream.set_doctype(TRANSITIONAL_DOCTYPE_NOEXT)
+        pass
+
 
     # xml doctype #############################################################
 
-    def set_doctype(self, doctype, reset_xmldecl=True):
+    def set_doctype(self, doctype, reset_xmldecl=None):
         """helper method to dynamically change page doctype
 
         :param doctype: the new doctype, e.g. '<!DOCTYPE html>'
-        :param reset_xmldecl: if True, remove the '<?xml version="1.0"?>'
-                              declaration from the page
         """
+        if reset_xmldecl is not None:
+            warn('[3.17] reset_xmldecl is deprecated as we only serve html',
+                 DeprecationWarning, stacklevel=2)
         self.main_stream.set_doctype(doctype, reset_xmldecl)
 
     # page data management ####################################################
@@ -965,6 +962,7 @@
         useragent = self.useragent()
         return useragent and 'MSIE' in useragent
 
+    @deprecated('[3.17] xhtml_browser is deprecated (xhtml is no longer served)')
     def xhtml_browser(self):
         """return True if the browser is considered as xhtml compatible.
 
@@ -972,26 +970,12 @@
         application/xhtml+xml, this method will always return False, even though
         this is semantically different
         """
-        if self.vreg.config['force-html-content-type']:
-            return False
-        useragent = self.useragent()
-        # * MSIE/Konqueror does not support xml content-type
-        # * Opera supports xhtml and handles namespaces properly but it breaks
-        #   jQuery.attr()
-        if useragent and ('MSIE' in useragent or 'KHTML' in useragent
-                          or 'Opera' in useragent):
-            return False
-        return True
+        return False
 
     def html_content_type(self):
-        if self.xhtml_browser():
-            return 'application/xhtml+xml'
         return 'text/html'
 
     def document_surrounding_div(self):
-        if self.xhtml_browser():
-            return (u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE + # XXX encoding ?
-                    u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">')
         return u'<div>'
 
     @deprecated('[3.9] use req.uiprops[rid]')
--- a/web/test/test_views.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/test/test_views.py	Fri Apr 26 12:10:37 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """automatic tests"""
-
+from cubicweb.devtools import htmlparser
 from cubicweb.devtools.testlib import CubicWebTC, AutoPopulateTest, AutomaticWebTest
 from cubicweb.view import AnyRsetView
 
@@ -51,7 +51,7 @@
         self.assertFalse('jquery.tablesorter.js' in self.view('oneline', rset))
         # but should be included by the tableview
         rset = self.execute('Any P,F,S LIMIT 1 WHERE P is CWUser, P firstname F, P surname S')
-        self.assertTrue('jquery.tablesorter.js' in self.view('table', rset))
+        self.assertIn('jquery.tablesorter.js', self.view('table', rset).source)
 
     def test_js_added_only_once(self):
         self.vreg._loadedmods[__name__] = {}
--- a/web/test/unittest_idownloadable.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/test/unittest_idownloadable.py	Fri Apr 26 12:10:37 2013 +0200
@@ -146,7 +146,7 @@
         finally:
             self.app.error_handler = errhdlr
         get = req.headers_out.getRawHeaders
-        self.assertEqual(['application/xhtml+xml'],
+        self.assertEqual(['text/html;charset=UTF-8'],
                          get('content-type'))
         self.assertEqual(None,
                          get('content-disposition'))
--- a/web/test/unittest_views_basecontrollers.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/test/unittest_views_basecontrollers.py	Fri Apr 26 12:10:37 2013 +0200
@@ -566,11 +566,6 @@
         rset = self.john.as_rset()
         rset.req = req
         source = ctrl.publish()
-        self.assertTrue(source.startswith('<?xml version="1.0"?>\n' + STRICT_DOCTYPE +
-                                          u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">')
-                        )
-        req.xhtml_browser = lambda: False
-        source = ctrl.publish()
         self.assertTrue(source.startswith('<div>'))
 
 #     def test_json_exec(self):
@@ -744,9 +739,7 @@
         def js_foo(self):
             return u'hello'
         res, req = self.remote_call('foo')
-        self.assertEqual(res,
-                         '<?xml version="1.0"?>\n' + STRICT_DOCTYPE +
-                         u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">hello</div>')
+        self.assertEqual(u'<div>hello</div>', res)
 
     def test_monkeypatch_jsoncontroller_jsonize(self):
         self.assertRaises(RemoteCallFailed, self.remote_call, 'foo')
--- a/web/test/unittest_views_baseviews.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/test/unittest_views_baseviews.py	Fri Apr 26 12:10:37 2013 +0200
@@ -21,7 +21,7 @@
 
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.utils import json
-from cubicweb.view import StartupView, TRANSITIONAL_DOCTYPE_NOEXT
+from cubicweb.view import StartupView, TRANSITIONAL_DOCTYPE
 from cubicweb.web.htmlwidgets import TableWidget
 from cubicweb.web.views import vid_from_rset
 
@@ -133,31 +133,26 @@
             html_source = self.view('my-view').source
             source_lines = [line.strip() for line in html_source.splitlines(False)
                             if line.strip()]
-            self.assertListEqual(source_lines[:2],
-                                 ['<!DOCTYPE html>',
-                                  '<html xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" xml:lang="en" lang="en">'])
+            self.assertListEqual(['<!DOCTYPE html>', '<html lang="en">'], source_lines[:2])
 
     def test_set_doctype_no_reset_xmldecl(self):
         """
         tests `cubicweb.web.request.CubicWebRequestBase.set_doctype`
         with no xmldecl reset
         """
-        html_doctype = TRANSITIONAL_DOCTYPE_NOEXT.strip()
+        html_doctype = TRANSITIONAL_DOCTYPE.strip()
         class MyView(StartupView):
             __regid__ = 'my-view'
             def call(self):
                 self._cw.set_doctype(html_doctype, reset_xmldecl=False)
-                self._cw.main_stream.set_namespaces([('xmlns', 'http://www.w3.org/1999/xhtml')])
                 self._cw.main_stream.set_htmlattrs([('lang', 'cz')])
 
         with self.temporary_appobjects(MyView):
             html_source = self.view('my-view').source
             source_lines = [line.strip() for line in html_source.splitlines(False)
                             if line.strip()]
-            self.assertListEqual(source_lines[:3],
-                                 ['<?xml version="1.0" encoding="UTF-8"?>',
-                                  html_doctype,
-                                  '<html xmlns="http://www.w3.org/1999/xhtml" lang="cz">'])
+            self.assertListEqual([html_doctype, '<html lang="cz">', '<head>'],
+                                 source_lines[:3])
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/views/calendar.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/views/calendar.py	Fri Apr 26 12:10:37 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -188,7 +188,6 @@
         }
 
     def call(self):
-        self._cw.demote_to_html()
         self._cw.add_css(('fullcalendar.css', 'cubicweb.calendar.css'))
         self._cw.add_js(('jquery.ui.js', 'fullcalendar.min.js', 'jquery.qtip.min.js', 'fullcalendar.locale.js'))
         self.calendar_id = 'cal' + make_uid('uid')
--- a/web/webconfig.py	Fri Apr 26 11:53:47 2013 +0200
+++ b/web/webconfig.py	Fri Apr 26 12:10:37 2013 +0200
@@ -170,13 +170,6 @@
           'transparent to the user. Default to 5min.',
           'group': 'web', 'level': 3,
           }),
-        ('force-html-content-type',
-         {'type' : 'yn',
-          'default': False,
-          'help': 'force text/html content type for your html pages instead of cubicweb user-agent based'\
-          'deduction of an appropriate content type',
-          'group': 'web', 'level': 3,
-          }),
         ('embed-allowed',
          {'type' : 'regexp',
           'default': None,