QScintilla/Exporters/ExporterHTML.py

changeset 5837
9ef6a28f1694
parent 5389
9b1c800daff3
child 5861
26250d8ff4e4
diff -r 159e6057ad34 -r 9ef6a28f1694 QScintilla/Exporters/ExporterHTML.py
--- a/QScintilla/Exporters/ExporterHTML.py	Mon Aug 07 12:19:34 2017 +0200
+++ b/QScintilla/Exporters/ExporterHTML.py	Mon Aug 07 18:00:51 2017 +0200
@@ -9,10 +9,16 @@
 
 from __future__ import unicode_literals
 
+try:  # Only for Py2
+    import StringIO as io   # __IGNORE_EXCEPTION__
+except (ImportError, NameError):
+    import io       # __IGNORE_WARNING__
+
 # This code is a port of the C++ code found in SciTE 1.74
 # Original code: Copyright 1998-2006 by Neil Hodgson <neilh@scintilla.org>
 
 import os
+import sys
 
 from PyQt5.QtCore import Qt
 from PyQt5.QtGui import QCursor, QFontInfo
@@ -378,39 +384,206 @@
             QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
             QApplication.processEvents()
             
-            tabSize = Preferences.getEditor("TabWidth")
-            if tabSize == 0:
-                tabSize = 4
-            wysiwyg = Preferences.getEditorExporter("HTML/WYSIWYG")
-            folding = Preferences.getEditorExporter("HTML/Folding")
-            onlyStylesUsed = Preferences.getEditorExporter(
-                "HTML/OnlyStylesUsed")
-            titleFullPath = Preferences.getEditorExporter(
-                "HTML/FullPathAsTitle")
-            tabs = Preferences.getEditorExporter("HTML/UseTabs")
+            fn = self.editor.getFileName()
+            if fn:
+                extension = os.path.normcase(os.path.splitext(fn)[1][1:])
+            else:
+                extension = ""
             
-            generator = HTMLGenerator(self.editor)
-            html = generator.generate(
-                tabSize=tabSize,
-                useTabs=tabs,
-                wysiwyg=wysiwyg,
-                folding=folding,
-                onlyStylesUsed=onlyStylesUsed,
-                titleFullPath=titleFullPath
-            )
+            if extension in \
+                Preferences.getEditor("PreviewMarkdownFileNameExtensions") or \
+                    self.editor.getLanguage().lower() == "markdown":
+                # export markdown to HTML
+                html = self.__generateFromMarkdown()
+            elif extension in \
+                Preferences.getEditor("PreviewRestFileNameExtensions") or \
+                    self.editor.getLanguage().lower() == "restructuredtext":
+                # export ReST to HTML
+                html = self.__generateFromReSTDocutils()
+            else:
+                tabSize = Preferences.getEditor("TabWidth")
+                if tabSize == 0:
+                    tabSize = 4
+                wysiwyg = Preferences.getEditorExporter("HTML/WYSIWYG")
+                folding = Preferences.getEditorExporter("HTML/Folding")
+                onlyStylesUsed = Preferences.getEditorExporter(
+                    "HTML/OnlyStylesUsed")
+                titleFullPath = Preferences.getEditorExporter(
+                    "HTML/FullPathAsTitle")
+                tabs = Preferences.getEditorExporter("HTML/UseTabs")
+                
+                generator = HTMLGenerator(self.editor)
+                html = generator.generate(
+                    tabSize=tabSize,
+                    useTabs=tabs,
+                    wysiwyg=wysiwyg,
+                    folding=folding,
+                    onlyStylesUsed=onlyStylesUsed,
+                    titleFullPath=titleFullPath
+                )
             
-            try:
-                f = open(filename, "w", encoding="utf-8")
-                f.write(html)
-                f.close()
-            except IOError as err:
+            if html:
+                try:
+                    f = open(filename, "w", encoding="utf-8")
+                    f.write(html)
+                    f.close()
+                except IOError as err:
+                    QApplication.restoreOverrideCursor()
+                    E5MessageBox.critical(
+                        self.editor,
+                        self.tr("Export source"),
+                        self.tr(
+                            """<p>The source could not be exported to"""
+                            """ <b>{0}</b>.</p><p>Reason: {1}</p>""")
+                        .format(filename, str(err)))
+            else:
                 QApplication.restoreOverrideCursor()
                 E5MessageBox.critical(
                     self.editor,
                     self.tr("Export source"),
                     self.tr(
                         """<p>The source could not be exported to"""
-                        """ <b>{0}</b>.</p><p>Reason: {1}</p>""")
-                    .format(filename, str(err)))
+                        """ <b>{0}</b>.</p><p>Reason: No HTML code"""
+                        """ generated.</p>""")
+                    .format(filename))
         finally:
             QApplication.restoreOverrideCursor()
+    
+    def __generateFromReSTDocutils(self):
+        """
+        Private method to convert ReST text into HTML using 'docutils'.
+        
+        @return processed HTML (string)
+        """
+        if 'sphinx' in sys.modules:
+            # Make sure any Sphinx polution of docutils has been removed.
+            unloadKeys = [k for k in sys.modules.keys()
+                          if k.startswith(('docutils', 'sphinx'))]
+            for key in unloadKeys:
+                sys.modules.pop(key)
+        
+        try:
+            import docutils.core    # __IGNORE_EXCEPTION__
+        except ImportError:
+            E5MessageBox.critical(
+                self.editor,
+                self.tr("Export source"),
+                self.tr(
+                    """<p>ReStructuredText export requires the"""
+                    """ <b>python-docutils</b> package.<br/>Install it with"""
+                    """ your package manager, 'pip install docutils' or see"""
+                    """ <a href="http://pypi.python.org/pypi/docutils">"""
+                    """this page.</a></p>""")
+            )
+            return ""
+        
+        htmlFormat = Preferences.getEditor(
+            "PreviewRestDocutilsHTMLFormat").lower()
+        # redirect sys.stderr because we are not interested in it here
+        origStderr = sys.stderr
+        sys.stderr = io.StringIO()
+        html = docutils.core.publish_string(
+            self.editor.text(), writer_name=htmlFormat).decode("utf-8")
+        sys.stderr = origStderr
+        return html
+    
+    def __generateFromMarkdown(self):
+        """
+        Private method to convert Markdown text into HTML.
+        
+        @return processed HTML
+        @rtype str
+        """
+        try:
+            import markdown     # __IGNORE_EXCEPTION__
+        except ImportError:
+            E5MessageBox.critical(
+                self.editor,
+                self.tr("Export source"),
+                self.tr(
+                    """<p>Markdown export requires the <b>python-markdown"""
+                    """</b> package.<br/>Install it with your package"""
+                    """ manager, pip install docutils' or see """
+                    """<a href="http://pythonhosted.org/Markdown/install"""
+                    """.html"> installation instructions.</a></p>""")
+            )
+            return ""
+        
+        try:
+            import mdx_mathjax  # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
+        except ImportError:
+            # mathjax doesn't require import statement if installed as
+            # extension
+            pass
+        
+        if Preferences.getEditor("PreviewMarkdownNLtoBR"):
+            extensions = ['fenced_code', 'nl2br', 'extra']
+        else:
+            extensions = ['fenced_code', 'extra']
+        
+        # version 2.0 supports only extension names, not instances
+        if markdown.version_info[0] > 2 or \
+                (markdown.version_info[0] == 2 and
+                 markdown.version_info[1] > 0):
+            class _StrikeThroughExtension(markdown.Extension):
+                """
+                Class is placed here, because it depends on imported markdown,
+                and markdown import is lazy.
+                
+                (see http://achinghead.com/
+                python-markdown-adding-insert-delete.html this page for
+                details)
+                """
+                DEL_RE = r'(~~)(.*?)~~'
+
+                def extendMarkdown(self, md, md_globals):
+                    # Create the del pattern
+                    del_tag = markdown.inlinepatterns.SimpleTagPattern(
+                        self.DEL_RE, 'del')
+                    # Insert del pattern into markdown parser
+                    md.inlinePatterns.add('del', del_tag, '>not_strong')
+            
+            extensions.append(_StrikeThroughExtension())
+        
+        htmlFormat = Preferences.getEditor("PreviewMarkdownHTMLFormat").lower()
+        try:
+            body = markdown.markdown(self.editor.text(),
+                                     extensions=extensions + ['mathjax'],
+                                     output_format=htmlFormat)
+        except (ImportError, ValueError):
+            # markdown raises ValueError or ImportError, depends on version
+            # It is not clear, how to distinguish missing mathjax from other
+            # errors. So keep going without mathjax.
+            body = markdown.markdown(self.editor.text(),
+                                     extensions=extensions,
+                                     output_format=htmlFormat)
+        
+        if htmlFormat == "xhtml1":
+            head = \
+                '''<!DOCTYPE html PUBLIC "-//W3C//DTD''' \
+                ''' XHTML 1.0 Transitional//EN"\n''' \
+                ''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional''' \
+                '''.dtd">\n''' \
+                '''<html xmlns="http://www.w3.org/1999/xhtml">\n'''
+        elif htmlFormat == "html5":
+            head = \
+                '''<!DOCTYPE html>\n''' \
+                '''<html lang="EN">\n'''
+        else:
+            head = '<html lang="EN">\n'
+        head += '''<head>\n'''
+        if Preferences.getEditorExporter("HTML/FullPathAsTitle"):
+            head += '''<title>{0}</title>\n'''.format(
+                self.editor.getFileName())
+        else:
+            head += '''<title>{0}</title>\n'''.format(
+                os.path.basename(self.editor.getFileName()))
+        head += '''<meta name="Generator" content="eric6" />\n''' \
+            '''<meta http-equiv="Content-Type" ''' \
+            '''content="text/html; charset=utf-8" />\n''' \
+            '''</head>\n''' \
+            '''<body>\n'''
+        
+        foot = '''\n</body>\n</html>\n'''
+        
+        return head + body + foot

eric ide

mercurial