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