--- a/src/eric7/UI/Previewers/PreviewerHTML.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/UI/Previewers/PreviewerHTML.py Wed Jul 13 14:55:47 2022 +0200 @@ -19,7 +19,12 @@ from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QThread from PyQt6.QtGui import QCursor from PyQt6.QtWidgets import ( - QWidget, QVBoxLayout, QLabel, QCheckBox, QSizePolicy, QToolTip + QWidget, + QVBoxLayout, + QLabel, + QCheckBox, + QSizePolicy, + QToolTip, ) from EricWidgets.EricApplication import ericApp @@ -32,194 +37,199 @@ """ Class implementing a previewer widget for HTML, Markdown and ReST files. """ + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent widget (QWidget) """ super().__init__(parent) - + self.__layout = QVBoxLayout(self) - + self.titleLabel = QLabel(self) self.titleLabel.setWordWrap(True) self.titleLabel.setTextInteractionFlags( - Qt.TextInteractionFlag.NoTextInteraction) + Qt.TextInteractionFlag.NoTextInteraction + ) self.__layout.addWidget(self.titleLabel) - + self.__previewAvailable = True - + try: from PyQt6.QtWebEngineWidgets import QWebEngineView + self.previewView = QWebEngineView(self) self.previewView.page().linkHovered.connect(self.__showLink) except ImportError: self.__previewAvailable = False - self.titleLabel.setText(self.tr( - "<b>HTML Preview is not available!<br/>" - "Install PyQt6-WebEngine.</b>")) + self.titleLabel.setText( + self.tr( + "<b>HTML Preview is not available!<br/>" + "Install PyQt6-WebEngine.</b>" + ) + ) self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignHCenter) self.__layout.addStretch() return - - sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, - QSizePolicy.Policy.Expanding) + + sizePolicy = QSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.previewView.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth(self.previewView.sizePolicy().hasHeightForWidth()) self.previewView.setSizePolicy(sizePolicy) - self.previewView.setContextMenuPolicy( - Qt.ContextMenuPolicy.NoContextMenu) + self.previewView.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) self.previewView.setUrl(QUrl("about:blank")) self.__layout.addWidget(self.previewView) - + self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self) - self.jsCheckBox.setToolTip(self.tr( - "Select to enable JavaScript for HTML previews")) + self.jsCheckBox.setToolTip( + self.tr("Select to enable JavaScript for HTML previews") + ) self.__layout.addWidget(self.jsCheckBox) - - self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"), - self) - self.ssiCheckBox.setToolTip(self.tr( - "Select to enable support for Server Side Includes")) + + self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"), self) + self.ssiCheckBox.setToolTip( + self.tr("Select to enable support for Server Side Includes") + ) self.__layout.addWidget(self.ssiCheckBox) - + self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked) self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked) self.previewView.titleChanged.connect(self.on_previewView_titleChanged) - - self.jsCheckBox.setChecked( - Preferences.getUI("ShowFilePreviewJS")) - self.ssiCheckBox.setChecked( - Preferences.getUI("ShowFilePreviewSSI")) - + + self.jsCheckBox.setChecked(Preferences.getUI("ShowFilePreviewJS")) + self.ssiCheckBox.setChecked(Preferences.getUI("ShowFilePreviewSSI")) + self.__scrollBarPositions = {} self.__vScrollBarAtEnd = {} self.__hScrollBarAtEnd = {} - + self.__processingThread = PreviewProcessingThread() self.__processingThread.htmlReady.connect(self.__setHtml) self.__previewedPath = None self.__previewedEditor = None - + def shutdown(self): """ Public method to perform shutdown actions. """ if self.__previewAvailable: self.__processingThread.wait() - + @pyqtSlot(bool) def on_jsCheckBox_clicked(self, checked): """ Private slot to enable/disable JavaScript. - + @param checked state of the checkbox (boolean) """ Preferences.setUI("ShowFilePreviewJS", checked) self.__setJavaScriptEnabled(checked) - + def __setJavaScriptEnabled(self, enable): """ Private method to enable/disable JavaScript. - + @param enable flag indicating the enable state (boolean) """ self.jsCheckBox.setChecked(enable) - + settings = self.previewView.settings() settings.setAttribute(settings.JavascriptEnabled, enable) - + self.processEditor() - + @pyqtSlot(bool) def on_ssiCheckBox_clicked(self, checked): """ Private slot to enable/disable SSI. - + @param checked state of the checkbox (boolean) """ Preferences.setUI("ShowFilePreviewSSI", checked) self.processEditor() - + @pyqtSlot(str) def __showLink(self, urlStr): """ Private slot to show the hovered link in a tooltip. - + @param urlStr hovered URL @type str """ QToolTip.showText(QCursor.pos(), urlStr, self.previewView) - + def processEditor(self, editor=None): """ Public slot to process an editor's text. - + @param editor editor to be processed (Editor) """ if not self.__previewAvailable: return - + if editor is None: editor = self.__previewedEditor else: self.__previewedEditor = editor - + if editor is not None: fn = editor.getFileName() - + if fn: extension = os.path.normcase(os.path.splitext(fn)[1][1:]) else: extension = "" if ( - extension in Preferences.getEditor( - "PreviewHtmlFileNameExtensions") or - editor.getLanguage() == "HTML" + extension in Preferences.getEditor("PreviewHtmlFileNameExtensions") + or editor.getLanguage() == "HTML" ): language = "HTML" elif ( - extension in Preferences.getEditor( - "PreviewMarkdownFileNameExtensions") or - editor.getLanguage().lower() == "markdown" + extension in Preferences.getEditor("PreviewMarkdownFileNameExtensions") + or editor.getLanguage().lower() == "markdown" ): language = "Markdown" elif ( - extension in Preferences.getEditor( - "PreviewRestFileNameExtensions") or - editor.getLanguage().lower() == "restructuredtext" + extension in Preferences.getEditor("PreviewRestFileNameExtensions") + or editor.getLanguage().lower() == "restructuredtext" ): language = "ReST" else: - self.__setHtml(fn, self.tr( - "<p>No preview available for this type of file.</p>")) + self.__setHtml( + fn, self.tr("<p>No preview available for this type of file.</p>") + ) return - + if fn: rootPath = os.path.dirname(os.path.abspath(fn)) else: rootPath = "" - + if bool(editor.text()): self.__processingThread.process( - fn, language, editor.text(), - self.ssiCheckBox.isChecked(), rootPath, + fn, + language, + editor.text(), + self.ssiCheckBox.isChecked(), + rootPath, Preferences.getEditor("PreviewRestUseSphinx"), Preferences.getEditor("PreviewMarkdownNLtoBR"), - Preferences.getEditor( - "PreviewMarkdownUsePyMdownExtensions"), + Preferences.getEditor("PreviewMarkdownUsePyMdownExtensions"), Preferences.getEditor("PreviewMarkdownHTMLFormat"), - Preferences.getEditor("PreviewRestDocutilsHTMLFormat")) + Preferences.getEditor("PreviewRestDocutilsHTMLFormat"), + ) def __setHtml(self, filePath, html, rootPath): """ Private method to set the HTML to the view and restore the scroll bars positions. - + @param filePath file path of the previewed editor @type str @param html processed HTML text ready to be shown @@ -228,38 +238,39 @@ @type str """ self.__previewedPath = Utilities.normcasepath( - Utilities.fromNativeSeparators(filePath)) + Utilities.fromNativeSeparators(filePath) + ) self.__saveScrollBarPositions() - self.previewView.page().loadFinished.connect( - self.__restoreScrollBarPositions) + self.previewView.page().loadFinished.connect(self.__restoreScrollBarPositions) if not filePath: filePath = "/" baseUrl = ( QUrl.fromLocalFile(rootPath + "/index.html") - if rootPath else - QUrl.fromLocalFile(filePath) + if rootPath + else QUrl.fromLocalFile(filePath) ) self.previewView.setHtml(html, baseUrl=baseUrl) if self.__previewedEditor: self.__previewedEditor.setFocus() - + @pyqtSlot(str) def on_previewView_titleChanged(self, title): """ Private slot to handle a change of the title. - + @param title new title (string) """ if title: self.titleLabel.setText(self.tr("Preview - {0}").format(title)) else: self.titleLabel.setText(self.tr("Preview")) - + def __saveScrollBarPositions(self): """ Private method to save scroll bar positions for a previewed editor. """ from PyQt6.QtCore import QPoint + try: pos = self.previewView.scrollPosition() except AttributeError: @@ -285,32 +296,33 @@ """ if self.__previewedPath not in self.__scrollBarPositions: return - + pos = self.__scrollBarPositions[self.__previewedPath] self.previewView.page().runJavaScript( - "window.scrollTo({0}, {1});".format(pos.x(), pos.y())) - + "window.scrollTo({0}, {1});".format(pos.x(), pos.y()) + ) + def __execJavaScript(self, script): """ Private function to execute a JavaScript function Synchroneously. - + @param script JavaScript script source to be executed @type str @return result of the script @rtype depending upon script result """ from PyQt6.QtCore import QEventLoop + loop = QEventLoop() resultDict = {"res": None} - + def resultCallback(res, resDict=resultDict): if loop and loop.isRunning(): resDict["res"] = res loop.quit() - - self.previewView.page().runJavaScript( - script, resultCallback) - + + self.previewView.page().runJavaScript(script, resultCallback) + loop.exec() return resultDict["res"] @@ -319,29 +331,40 @@ """ Class implementing a thread to process some text into HTML usable by the previewer view. - + @signal htmlReady(str, str, str) emitted with the file name, the processed HTML and the web site root path to signal the availability of the processed HTML """ + htmlReady = pyqtSignal(str, str, str) - + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent object (QObject) """ super().__init__() - + self.__lock = threading.Lock() - - def process(self, filePath, language, text, ssiEnabled, rootPath, - useSphinx, convertNewLineToBreak, usePyMdownExtensions, - markdownHtmlFormat, restDocutilsHtmlFormat): + + def process( + self, + filePath, + language, + text, + ssiEnabled, + rootPath, + useSphinx, + convertNewLineToBreak, + usePyMdownExtensions, + markdownHtmlFormat, + restDocutilsHtmlFormat, + ): """ Public method to convert the given text to HTML. - + @param filePath file path of the text @type str @param language language of the text @@ -380,7 +403,7 @@ self.__restDocutilsHtmlFormat = restDocutilsHtmlFormat if not self.isRunning(): self.start(QThread.Priority.LowPriority) - + def run(self): """ Public thread method to convert the stored data. @@ -398,27 +421,45 @@ usePyMdownExtensions = self.__usePyMdownExtensions markdownHtmlFormat = self.__markdownHtmlFormat restDocutilsHtmlFormat = self.__restDocutilsHtmlFormat - + self.__haveData = False - html = self.__getHtml(language, text, ssiEnabled, filePath, - rootPath, useSphinx, convertNewLineToBreak, - usePyMdownExtensions, markdownHtmlFormat, - restDocutilsHtmlFormat) - + html = self.__getHtml( + language, + text, + ssiEnabled, + filePath, + rootPath, + useSphinx, + convertNewLineToBreak, + usePyMdownExtensions, + markdownHtmlFormat, + restDocutilsHtmlFormat, + ) + with self.__lock: if not self.__haveData: self.htmlReady.emit(filePath, html, rootPath) break # else - next iteration - - def __getHtml(self, language, text, ssiEnabled, filePath, rootPath, - useSphinx, convertNewLineToBreak, usePyMdownExtensions, - markdownHtmlFormat, restDocutilsHtmlFormat): + + def __getHtml( + self, + language, + text, + ssiEnabled, + filePath, + rootPath, + useSphinx, + convertNewLineToBreak, + usePyMdownExtensions, + markdownHtmlFormat, + restDocutilsHtmlFormat, + ): """ Private method to process the given text depending upon the given language. - + @param language language of the text @type str @param text to be processed @@ -453,20 +494,19 @@ return self.__processRootPath(html, rootPath) elif language == "Markdown": return self.__convertMarkdown( - text, convertNewLineToBreak, usePyMdownExtensions, - markdownHtmlFormat) + text, convertNewLineToBreak, usePyMdownExtensions, markdownHtmlFormat + ) elif language == "ReST": return self.__convertReST(text, useSphinx, restDocutilsHtmlFormat) else: - return self.tr( - "<p>No preview available for this type of file.</p>") - + return self.tr("<p>No preview available for this type of file.</p>") + def __processSSI(self, txt, filename, root): """ Private method to process the given text for SSI statements. - + Note: Only a limited subset of SSI statements are supported. - + @param txt text to be processed (string) @param filename name of the file associated with the given text (string) @@ -475,18 +515,19 @@ """ if not filename: return txt - + # SSI include incRe = re.compile( r"""<!--#include[ \t]+(virtual|file)=[\"']([^\"']+)[\"']\s*-->""", - re.IGNORECASE) + re.IGNORECASE, + ) baseDir = os.path.dirname(os.path.abspath(filename)) docRoot = root if root != "" else baseDir while True: incMatch = incRe.search(txt) if incMatch is None: break - + if incMatch.group(1) == "virtual": incFile = Utilities.normjoinpath(docRoot, incMatch.group(2)) elif incMatch.group(1) == "file": @@ -503,14 +544,14 @@ else: # remove SSI include incTxt = "" - txt = txt[:incMatch.start(0)] + incTxt + txt[incMatch.end(0):] - + txt = txt[: incMatch.start(0)] + incTxt + txt[incMatch.end(0) :] + return txt - + def __processRootPath(self, txt, root): """ Private method to adjust absolute references to the given root path. - + @param txt text to be processed @type str @param root directory of the document root @@ -520,31 +561,36 @@ """ if not root: return txt - + root = Utilities.fromNativeSeparators(root) if not root.endswith("/"): root += "/" rootLen = len(root) - - refRe = re.compile( - r"""(href|src)=[\\"']/([^\\"']+)[\\"']""", - re.IGNORECASE) + + refRe = re.compile(r"""(href|src)=[\\"']/([^\\"']+)[\\"']""", re.IGNORECASE) pos = 0 while True: refMatch = refRe.search(txt, pos) if refMatch is None: break - - txt = (txt[:refMatch.start(0)] + refMatch.group(1) + '="' + root + - refMatch.group(2) + '"' + txt[refMatch.end(0):]) + + txt = ( + txt[: refMatch.start(0)] + + refMatch.group(1) + + '="' + + root + + refMatch.group(2) + + '"' + + txt[refMatch.end(0) :] + ) pos = refMatch.end(0) + rootLen - + return txt - + def __convertReST(self, text, useSphinx, restDocutilsHtmlFormat): """ Private method to convert ReST text into HTML. - + @param text text to be processed (string) @param useSphinx flag indicating to use Sphinx to generate the ReST preview (boolean) @@ -556,16 +602,16 @@ return self.__convertReSTSphinx(text) else: return self.__convertReSTDocutils(text, restDocutilsHtmlFormat) - + def __convertReSTSphinx(self, text): """ Private method to convert ReST text into HTML using 'sphinx'. - + @param text text to be processed (string) @return processed HTML (string) """ try: - from sphinx.application import Sphinx # __IGNORE_EXCEPTION__ + from sphinx.application import Sphinx # __IGNORE_EXCEPTION__ except ImportError: return self.tr( """<p>ReStructuredText preview requires the""" @@ -574,86 +620,99 @@ """ <a href="http://pypi.python.org/pypi/Sphinx">""" """this page.</a></p>""" """<p>Alternatively you may disable Sphinx usage""" - """ on the Editor, Filehandling configuration page.</p>""") - + """ on the Editor, Filehandling configuration page.</p>""" + ) + srcTempDir = tempfile.mkdtemp(prefix="eric-rest-src-") outTempDir = tempfile.mkdtemp(prefix="eric-rest-out-") doctreeTempDir = tempfile.mkdtemp(prefix="eric-rest-doctree-") try: - filename = 'sphinx_preview' + filename = "sphinx_preview" basePath = os.path.join(srcTempDir, filename) - with open(basePath + '.rst', 'w', encoding='utf-8') as fh: + with open(basePath + ".rst", "w", encoding="utf-8") as fh: fh.write(text) - - overrides = {'html_add_permalinks': False, - 'html_copy_source': False, - 'html_title': 'Sphinx preview', - 'html_use_index': False, - 'html_use_modindex': False, - 'html_use_smartypants': True, - 'master_doc': filename} - app = Sphinx(srcdir=srcTempDir, confdir=None, outdir=outTempDir, - doctreedir=doctreeTempDir, buildername='html', - confoverrides=overrides, status=None, - warning=io.StringIO()) + + overrides = { + "html_add_permalinks": False, + "html_copy_source": False, + "html_title": "Sphinx preview", + "html_use_index": False, + "html_use_modindex": False, + "html_use_smartypants": True, + "master_doc": filename, + } + app = Sphinx( + srcdir=srcTempDir, + confdir=None, + outdir=outTempDir, + doctreedir=doctreeTempDir, + buildername="html", + confoverrides=overrides, + status=None, + warning=io.StringIO(), + ) app.build(force_all=True, filenames=None) basePath = os.path.join(outTempDir, filename) - with open(basePath + '.html', 'r', encoding='utf-8') as fh: + with open(basePath + ".html", "r", encoding="utf-8") as fh: html = fh.read() finally: shutil.rmtree(srcTempDir) shutil.rmtree(outTempDir) shutil.rmtree(doctreeTempDir) - + return html - + def __convertReSTDocutils(self, text, htmlFormat): """ Private method to convert ReST text into HTML using 'docutils'. - + @param text text to be processed (string) @param htmlFormat HTML format to be generated (string) @return processed HTML (string) """ - if 'sphinx' in sys.modules: + 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'))] + 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__ - import docutils.utils # __IGNORE_EXCEPTION__ + import docutils.core # __IGNORE_EXCEPTION__ + import docutils.utils # __IGNORE_EXCEPTION__ except ImportError: return self.tr( """<p>ReStructuredText preview 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>""") - + """this page.</a></p>""" + ) + # redirect sys.stderr because we are not interested in it here origStderr = sys.stderr sys.stderr = io.StringIO() try: html = docutils.core.publish_string( - text, writer_name=htmlFormat.lower()).decode("utf-8") + text, writer_name=htmlFormat.lower() + ).decode("utf-8") except docutils.utils.SystemMessage as err: errStr = str(err).split(":")[-1].replace("\n", "<br/>") - return self.tr( - """<p>Docutils returned an error:</p><p>{0}</p>""" - ).format(errStr) - + return self.tr("""<p>Docutils returned an error:</p><p>{0}</p>""").format( + errStr + ) + sys.stderr = origStderr return html - - def __convertMarkdown(self, text, convertNewLineToBreak, - usePyMdownExtensions, htmlFormat): + + def __convertMarkdown( + self, text, convertNewLineToBreak, usePyMdownExtensions, htmlFormat + ): """ Private method to convert Markdown text into HTML. - + @param text text to be processed @type str @param convertNewLineToBreak flag indicating to convert new lines @@ -668,53 +727,59 @@ @rtype str """ try: - import markdown # __IGNORE_EXCEPTION__ + import markdown # __IGNORE_EXCEPTION__ except ImportError: return self.tr( """<p>Markdown preview requires the <b>Markdown</b> """ """package.<br/>Install it with your package manager,""" """ 'pip install Markdown' or see """ """<a href="http://pythonhosted.org/Markdown/install.html">""" - """installation instructions.</a></p>""") - + """installation instructions.</a></p>""" + ) + from . import PreviewerHTMLStyles from . import MarkdownExtensions - + extensions = [] - + mermaidNeeded = False - if ( - Preferences.getEditor("PreviewMarkdownMermaid") and - MarkdownExtensions.MermaidRegexFullText.search(text) - ): + if Preferences.getEditor( + "PreviewMarkdownMermaid" + ) and MarkdownExtensions.MermaidRegexFullText.search(text): extensions.append(MarkdownExtensions.MermaidExtension()) mermaidNeeded = True - + if convertNewLineToBreak: - extensions.append('nl2br') - + extensions.append("nl2br") + pyMdown = False if usePyMdownExtensions: with contextlib.suppress(ImportError): - import pymdownx # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ + import pymdownx # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ + # PyPI package is 'pymdown-extensions' - - extensions.extend([ - 'toc', - 'pymdownx.extra', 'pymdownx.caret', 'pymdownx.emoji', - 'pymdownx.mark', 'pymdownx.tilde', 'pymdownx.keys', - 'pymdownx.tasklist', 'pymdownx.smartsymbols', - ]) + + extensions.extend( + [ + "toc", + "pymdownx.extra", + "pymdownx.caret", + "pymdownx.emoji", + "pymdownx.mark", + "pymdownx.tilde", + "pymdownx.keys", + "pymdownx.tasklist", + "pymdownx.smartsymbols", + ] + ) pyMdown = True - + if not pyMdown: - extensions.extend(['extra', 'toc']) - + extensions.extend(["extra", "toc"]) + # 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) + if markdown.version_info[0] > 2 or ( + markdown.version_info[0] == 2 and markdown.version_info[1] > 0 ): extensions.append(MarkdownExtensions.SimplePatternExtension()) @@ -727,15 +792,14 @@ ) # prepare text for mathjax text = ( - text - .replace(r"\(", r"\\(") + text.replace(r"\(", r"\\(") .replace(r"\)", r"\\)") .replace(r"\[", r"\\[") .replace(r"\]", r"\\]") ) else: mathjax = "" - + if mermaidNeeded: mermaid = ( "<script type='text/javascript' id='Mermaid-script'" @@ -759,47 +823,49 @@ else: mermaid = "" mermaid_initialize = "" - + htmlFormat = Preferences.getEditor("PreviewMarkdownHTMLFormat").lower() - body = markdown.markdown(text, extensions=extensions, - output_format=htmlFormat.lower()) + body = markdown.markdown( + text, extensions=extensions, output_format=htmlFormat.lower() + ) style = ( - (PreviewerHTMLStyles.css_markdown_dark + - PreviewerHTMLStyles.css_pygments_dark) - if ericApp().usesDarkPalette() else - (PreviewerHTMLStyles.css_markdown_light + - PreviewerHTMLStyles.css_pygments_light) + ( + PreviewerHTMLStyles.css_markdown_dark + + PreviewerHTMLStyles.css_pygments_dark + ) + if ericApp().usesDarkPalette() + else ( + PreviewerHTMLStyles.css_markdown_light + + PreviewerHTMLStyles.css_pygments_light + ) ) - + 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''' + """<!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''' - ) + head = """<!DOCTYPE html>\n""" """<html lang="EN">\n""" else: head = '<html lang="EN">\n' - head += '''<head>\n''' + head += """<head>\n""" head += ( - '''<meta name="Generator" content="eric" />\n''' - '''<meta http-equiv="Content-Type" ''' - '''content="text/html; charset=utf-8" />\n''' - '''{0}''' - '''{1}''' - '''<style type="text/css">''' - '''{2}''' - '''</style>\n''' - '''</head>\n''' - '''<body>\n''' + """<meta name="Generator" content="eric" />\n""" + """<meta http-equiv="Content-Type" """ + """content="text/html; charset=utf-8" />\n""" + """{0}""" + """{1}""" + """<style type="text/css">""" + """{2}""" + """</style>\n""" + """</head>\n""" + """<body>\n""" ).format(mathjax, mermaid, style) - - foot = '''\n</body>\n</html>\n''' - + + foot = """\n</body>\n</html>\n""" + return head + body + mermaid_initialize + foot