src/eric7/UI/Previewers/PreviewerHTML.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a previewer widget for HTML, Markdown and ReST files.
8 """
9
10 import os
11 import threading
12 import re
13 import shutil
14 import tempfile
15 import sys
16 import io
17 import contextlib
18
19 from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QThread
20 from PyQt6.QtGui import QCursor
21 from PyQt6.QtWidgets import (
22 QWidget, QVBoxLayout, QLabel, QCheckBox, QSizePolicy, QToolTip
23 )
24
25 from EricWidgets.EricApplication import ericApp
26
27 import Utilities
28 import Preferences
29
30
31 class PreviewerHTML(QWidget):
32 """
33 Class implementing a previewer widget for HTML, Markdown and ReST files.
34 """
35 def __init__(self, parent=None):
36 """
37 Constructor
38
39 @param parent reference to the parent widget (QWidget)
40 """
41 super().__init__(parent)
42
43 self.__layout = QVBoxLayout(self)
44
45 self.titleLabel = QLabel(self)
46 self.titleLabel.setWordWrap(True)
47 self.titleLabel.setTextInteractionFlags(
48 Qt.TextInteractionFlag.NoTextInteraction)
49 self.__layout.addWidget(self.titleLabel)
50
51 self.__previewAvailable = True
52
53 try:
54 from PyQt6.QtWebEngineWidgets import QWebEngineView
55 self.previewView = QWebEngineView(self)
56 self.previewView.page().linkHovered.connect(self.__showLink)
57 except ImportError:
58 self.__previewAvailable = False
59 self.titleLabel.setText(self.tr(
60 "<b>HTML Preview is not available!<br/>"
61 "Install PyQt6-WebEngine.</b>"))
62 self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignHCenter)
63 self.__layout.addStretch()
64 return
65
66 sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred,
67 QSizePolicy.Policy.Expanding)
68 sizePolicy.setHorizontalStretch(0)
69 sizePolicy.setVerticalStretch(0)
70 sizePolicy.setHeightForWidth(
71 self.previewView.sizePolicy().hasHeightForWidth())
72 self.previewView.setSizePolicy(sizePolicy)
73 self.previewView.setContextMenuPolicy(
74 Qt.ContextMenuPolicy.NoContextMenu)
75 self.previewView.setUrl(QUrl("about:blank"))
76 self.__layout.addWidget(self.previewView)
77
78 self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self)
79 self.jsCheckBox.setToolTip(self.tr(
80 "Select to enable JavaScript for HTML previews"))
81 self.__layout.addWidget(self.jsCheckBox)
82
83 self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"),
84 self)
85 self.ssiCheckBox.setToolTip(self.tr(
86 "Select to enable support for Server Side Includes"))
87 self.__layout.addWidget(self.ssiCheckBox)
88
89 self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked)
90 self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked)
91 self.previewView.titleChanged.connect(self.on_previewView_titleChanged)
92
93 self.jsCheckBox.setChecked(
94 Preferences.getUI("ShowFilePreviewJS"))
95 self.ssiCheckBox.setChecked(
96 Preferences.getUI("ShowFilePreviewSSI"))
97
98 self.__scrollBarPositions = {}
99 self.__vScrollBarAtEnd = {}
100 self.__hScrollBarAtEnd = {}
101
102 self.__processingThread = PreviewProcessingThread()
103 self.__processingThread.htmlReady.connect(self.__setHtml)
104
105 self.__previewedPath = None
106 self.__previewedEditor = None
107
108 def shutdown(self):
109 """
110 Public method to perform shutdown actions.
111 """
112 if self.__previewAvailable:
113 self.__processingThread.wait()
114
115 @pyqtSlot(bool)
116 def on_jsCheckBox_clicked(self, checked):
117 """
118 Private slot to enable/disable JavaScript.
119
120 @param checked state of the checkbox (boolean)
121 """
122 Preferences.setUI("ShowFilePreviewJS", checked)
123 self.__setJavaScriptEnabled(checked)
124
125 def __setJavaScriptEnabled(self, enable):
126 """
127 Private method to enable/disable JavaScript.
128
129 @param enable flag indicating the enable state (boolean)
130 """
131 self.jsCheckBox.setChecked(enable)
132
133 settings = self.previewView.settings()
134 settings.setAttribute(settings.JavascriptEnabled, enable)
135
136 self.processEditor()
137
138 @pyqtSlot(bool)
139 def on_ssiCheckBox_clicked(self, checked):
140 """
141 Private slot to enable/disable SSI.
142
143 @param checked state of the checkbox (boolean)
144 """
145 Preferences.setUI("ShowFilePreviewSSI", checked)
146 self.processEditor()
147
148 @pyqtSlot(str)
149 def __showLink(self, urlStr):
150 """
151 Private slot to show the hovered link in a tooltip.
152
153 @param urlStr hovered URL
154 @type str
155 """
156 QToolTip.showText(QCursor.pos(), urlStr, self.previewView)
157
158 def processEditor(self, editor=None):
159 """
160 Public slot to process an editor's text.
161
162 @param editor editor to be processed (Editor)
163 """
164 if not self.__previewAvailable:
165 return
166
167 if editor is None:
168 editor = self.__previewedEditor
169 else:
170 self.__previewedEditor = editor
171
172 if editor is not None:
173 fn = editor.getFileName()
174
175 if fn:
176 extension = os.path.normcase(os.path.splitext(fn)[1][1:])
177 else:
178 extension = ""
179 if (
180 extension in Preferences.getEditor(
181 "PreviewHtmlFileNameExtensions") or
182 editor.getLanguage() == "HTML"
183 ):
184 language = "HTML"
185 elif (
186 extension in Preferences.getEditor(
187 "PreviewMarkdownFileNameExtensions") or
188 editor.getLanguage().lower() == "markdown"
189 ):
190 language = "Markdown"
191 elif (
192 extension in Preferences.getEditor(
193 "PreviewRestFileNameExtensions") or
194 editor.getLanguage().lower() == "restructuredtext"
195 ):
196 language = "ReST"
197 else:
198 self.__setHtml(fn, self.tr(
199 "<p>No preview available for this type of file.</p>"))
200 return
201
202 if fn:
203 rootPath = os.path.dirname(os.path.abspath(fn))
204 else:
205 rootPath = ""
206
207 if bool(editor.text()):
208 self.__processingThread.process(
209 fn, language, editor.text(),
210 self.ssiCheckBox.isChecked(), rootPath,
211 Preferences.getEditor("PreviewRestUseSphinx"),
212 Preferences.getEditor("PreviewMarkdownNLtoBR"),
213 Preferences.getEditor(
214 "PreviewMarkdownUsePyMdownExtensions"),
215 Preferences.getEditor("PreviewMarkdownHTMLFormat"),
216 Preferences.getEditor("PreviewRestDocutilsHTMLFormat"))
217
218 def __setHtml(self, filePath, html, rootPath):
219 """
220 Private method to set the HTML to the view and restore the scroll bars
221 positions.
222
223 @param filePath file path of the previewed editor
224 @type str
225 @param html processed HTML text ready to be shown
226 @type str
227 @param rootPath path of the web site root
228 @type str
229 """
230 self.__previewedPath = Utilities.normcasepath(
231 Utilities.fromNativeSeparators(filePath))
232 self.__saveScrollBarPositions()
233 self.previewView.page().loadFinished.connect(
234 self.__restoreScrollBarPositions)
235 if not filePath:
236 filePath = "/"
237 baseUrl = (
238 QUrl.fromLocalFile(rootPath + "/index.html")
239 if rootPath else
240 QUrl.fromLocalFile(filePath)
241 )
242 self.previewView.setHtml(html, baseUrl=baseUrl)
243 if self.__previewedEditor:
244 self.__previewedEditor.setFocus()
245
246 @pyqtSlot(str)
247 def on_previewView_titleChanged(self, title):
248 """
249 Private slot to handle a change of the title.
250
251 @param title new title (string)
252 """
253 if title:
254 self.titleLabel.setText(self.tr("Preview - {0}").format(title))
255 else:
256 self.titleLabel.setText(self.tr("Preview"))
257
258 def __saveScrollBarPositions(self):
259 """
260 Private method to save scroll bar positions for a previewed editor.
261 """
262 from PyQt6.QtCore import QPoint
263 try:
264 pos = self.previewView.scrollPosition()
265 except AttributeError:
266 pos = self.__execJavaScript(
267 "(function() {"
268 "var res = {"
269 " x: 0,"
270 " y: 0,"
271 "};"
272 "res.x = window.scrollX;"
273 "res.y = window.scrollY;"
274 "return res;"
275 "})()"
276 )
277 pos = QPoint(0, 0) if pos is None else QPoint(pos["x"], pos["y"])
278 self.__scrollBarPositions[self.__previewedPath] = pos
279 self.__hScrollBarAtEnd[self.__previewedPath] = False
280 self.__vScrollBarAtEnd[self.__previewedPath] = False
281
282 def __restoreScrollBarPositions(self):
283 """
284 Private method to restore scroll bar positions for a previewed editor.
285 """
286 if self.__previewedPath not in self.__scrollBarPositions:
287 return
288
289 pos = self.__scrollBarPositions[self.__previewedPath]
290 self.previewView.page().runJavaScript(
291 "window.scrollTo({0}, {1});".format(pos.x(), pos.y()))
292
293 def __execJavaScript(self, script):
294 """
295 Private function to execute a JavaScript function Synchroneously.
296
297 @param script JavaScript script source to be executed
298 @type str
299 @return result of the script
300 @rtype depending upon script result
301 """
302 from PyQt6.QtCore import QEventLoop
303 loop = QEventLoop()
304 resultDict = {"res": None}
305
306 def resultCallback(res, resDict=resultDict):
307 if loop and loop.isRunning():
308 resDict["res"] = res
309 loop.quit()
310
311 self.previewView.page().runJavaScript(
312 script, resultCallback)
313
314 loop.exec()
315 return resultDict["res"]
316
317
318 class PreviewProcessingThread(QThread):
319 """
320 Class implementing a thread to process some text into HTML usable by the
321 previewer view.
322
323 @signal htmlReady(str, str, str) emitted with the file name, the processed
324 HTML and the web site root path to signal the availability of the
325 processed HTML
326 """
327 htmlReady = pyqtSignal(str, str, str)
328
329 def __init__(self, parent=None):
330 """
331 Constructor
332
333 @param parent reference to the parent object (QObject)
334 """
335 super().__init__()
336
337 self.__lock = threading.Lock()
338
339 def process(self, filePath, language, text, ssiEnabled, rootPath,
340 useSphinx, convertNewLineToBreak, usePyMdownExtensions,
341 markdownHtmlFormat, restDocutilsHtmlFormat):
342 """
343 Public method to convert the given text to HTML.
344
345 @param filePath file path of the text
346 @type str
347 @param language language of the text
348 @type str
349 @param text text to be processed
350 @type str
351 @param ssiEnabled flag indicating to do some (limited) SSI processing
352 @type bool
353 @param rootPath root path to be used for SSI processing
354 @type str
355 @param useSphinx flag indicating to use Sphinx to generate the
356 ReST preview
357 @type bool
358 @param convertNewLineToBreak flag indicating to convert new lines
359 to HTML break (Markdown only)
360 @type bool
361 @param usePyMdownExtensions flag indicating to enable the PyMdown
362 extensions, if they are available
363 @type bool
364 @param markdownHtmlFormat HTML format to be generated by markdown
365 @type str
366 @param restDocutilsHtmlFormat HTML format to be generated by docutils
367 @type str
368 """
369 with self.__lock:
370 self.__filePath = filePath
371 self.__language = language
372 self.__text = text
373 self.__ssiEnabled = ssiEnabled
374 self.__rootPath = rootPath
375 self.__haveData = True
376 self.__useSphinx = useSphinx
377 self.__convertNewLineToBreak = convertNewLineToBreak
378 self.__usePyMdownExtensions = usePyMdownExtensions
379 self.__markdownHtmlFormat = markdownHtmlFormat
380 self.__restDocutilsHtmlFormat = restDocutilsHtmlFormat
381 if not self.isRunning():
382 self.start(QThread.Priority.LowPriority)
383
384 def run(self):
385 """
386 Public thread method to convert the stored data.
387 """
388 while True:
389 # exits with break
390 with self.__lock:
391 filePath = self.__filePath
392 language = self.__language
393 text = self.__text
394 ssiEnabled = self.__ssiEnabled
395 rootPath = self.__rootPath
396 useSphinx = self.__useSphinx
397 convertNewLineToBreak = self.__convertNewLineToBreak
398 usePyMdownExtensions = self.__usePyMdownExtensions
399 markdownHtmlFormat = self.__markdownHtmlFormat
400 restDocutilsHtmlFormat = self.__restDocutilsHtmlFormat
401
402 self.__haveData = False
403
404 html = self.__getHtml(language, text, ssiEnabled, filePath,
405 rootPath, useSphinx, convertNewLineToBreak,
406 usePyMdownExtensions, markdownHtmlFormat,
407 restDocutilsHtmlFormat)
408
409 with self.__lock:
410 if not self.__haveData:
411 self.htmlReady.emit(filePath, html, rootPath)
412 break
413 # else - next iteration
414
415 def __getHtml(self, language, text, ssiEnabled, filePath, rootPath,
416 useSphinx, convertNewLineToBreak, usePyMdownExtensions,
417 markdownHtmlFormat, restDocutilsHtmlFormat):
418 """
419 Private method to process the given text depending upon the given
420 language.
421
422 @param language language of the text
423 @type str
424 @param text to be processed
425 @type str
426 @param ssiEnabled flag indicating to do some (limited) SSI processing
427 @type bool
428 @param filePath file path of the text
429 @type str
430 @param rootPath root path to be used for SSI processing
431 @type str
432 @param useSphinx flag indicating to use Sphinx to generate the
433 ReST preview
434 @type bool
435 @param convertNewLineToBreak flag indicating to convert new lines
436 to HTML break (Markdown only)
437 @type bool
438 @param usePyMdownExtensions flag indicating to enable the PyMdown
439 extensions, if they are available
440 @type bool
441 @param markdownHtmlFormat HTML format to be generated by markdown
442 @type str
443 @param restDocutilsHtmlFormat HTML format to be generated by docutils
444 @type str
445 @return processed HTML text
446 @rtype str
447 """
448 if language == "HTML":
449 if ssiEnabled:
450 html = self.__processSSI(text, filePath, rootPath)
451 else:
452 html = text
453 return self.__processRootPath(html, rootPath)
454 elif language == "Markdown":
455 return self.__convertMarkdown(
456 text, convertNewLineToBreak, usePyMdownExtensions,
457 markdownHtmlFormat)
458 elif language == "ReST":
459 return self.__convertReST(text, useSphinx, restDocutilsHtmlFormat)
460 else:
461 return self.tr(
462 "<p>No preview available for this type of file.</p>")
463
464 def __processSSI(self, txt, filename, root):
465 """
466 Private method to process the given text for SSI statements.
467
468 Note: Only a limited subset of SSI statements are supported.
469
470 @param txt text to be processed (string)
471 @param filename name of the file associated with the given text
472 (string)
473 @param root directory of the document root (string)
474 @return processed HTML (string)
475 """
476 if not filename:
477 return txt
478
479 # SSI include
480 incRe = re.compile(
481 r"""<!--#include[ \t]+(virtual|file)=[\"']([^\"']+)[\"']\s*-->""",
482 re.IGNORECASE)
483 baseDir = os.path.dirname(os.path.abspath(filename))
484 docRoot = root if root != "" else baseDir
485 while True:
486 incMatch = incRe.search(txt)
487 if incMatch is None:
488 break
489
490 if incMatch.group(1) == "virtual":
491 incFile = Utilities.normjoinpath(docRoot, incMatch.group(2))
492 elif incMatch.group(1) == "file":
493 incFile = Utilities.normjoinpath(baseDir, incMatch.group(2))
494 else:
495 incFile = ""
496 if os.path.exists(incFile):
497 try:
498 with open(incFile, "r") as f:
499 incTxt = f.read()
500 except OSError:
501 # remove SSI include
502 incTxt = ""
503 else:
504 # remove SSI include
505 incTxt = ""
506 txt = txt[:incMatch.start(0)] + incTxt + txt[incMatch.end(0):]
507
508 return txt
509
510 def __processRootPath(self, txt, root):
511 """
512 Private method to adjust absolute references to the given root path.
513
514 @param txt text to be processed
515 @type str
516 @param root directory of the document root
517 @type str
518 @return processed HTML
519 @rtype str
520 """
521 if not root:
522 return txt
523
524 root = Utilities.fromNativeSeparators(root)
525 if not root.endswith("/"):
526 root += "/"
527 rootLen = len(root)
528
529 refRe = re.compile(
530 r"""(href|src)=[\\"']/([^\\"']+)[\\"']""",
531 re.IGNORECASE)
532 pos = 0
533 while True:
534 refMatch = refRe.search(txt, pos)
535 if refMatch is None:
536 break
537
538 txt = (txt[:refMatch.start(0)] + refMatch.group(1) + '="' + root +
539 refMatch.group(2) + '"' + txt[refMatch.end(0):])
540 pos = refMatch.end(0) + rootLen
541
542 return txt
543
544 def __convertReST(self, text, useSphinx, restDocutilsHtmlFormat):
545 """
546 Private method to convert ReST text into HTML.
547
548 @param text text to be processed (string)
549 @param useSphinx flag indicating to use Sphinx to generate the
550 ReST preview (boolean)
551 @param restDocutilsHtmlFormat HTML format to be generated by docutils
552 (string)
553 @return processed HTML (string)
554 """
555 if useSphinx:
556 return self.__convertReSTSphinx(text)
557 else:
558 return self.__convertReSTDocutils(text, restDocutilsHtmlFormat)
559
560 def __convertReSTSphinx(self, text):
561 """
562 Private method to convert ReST text into HTML using 'sphinx'.
563
564 @param text text to be processed (string)
565 @return processed HTML (string)
566 """
567 try:
568 from sphinx.application import Sphinx # __IGNORE_EXCEPTION__
569 except ImportError:
570 return self.tr(
571 """<p>ReStructuredText preview requires the"""
572 """ <b>sphinx</b> package.<br/>Install it with"""
573 """ your package manager,'pip install Sphinx' or see"""
574 """ <a href="http://pypi.python.org/pypi/Sphinx">"""
575 """this page.</a></p>"""
576 """<p>Alternatively you may disable Sphinx usage"""
577 """ on the Editor, Filehandling configuration page.</p>""")
578
579 srcTempDir = tempfile.mkdtemp(prefix="eric-rest-src-")
580 outTempDir = tempfile.mkdtemp(prefix="eric-rest-out-")
581 doctreeTempDir = tempfile.mkdtemp(prefix="eric-rest-doctree-")
582 try:
583 filename = 'sphinx_preview'
584 basePath = os.path.join(srcTempDir, filename)
585 with open(basePath + '.rst', 'w', encoding='utf-8') as fh:
586 fh.write(text)
587
588 overrides = {'html_add_permalinks': False,
589 'html_copy_source': False,
590 'html_title': 'Sphinx preview',
591 'html_use_index': False,
592 'html_use_modindex': False,
593 'html_use_smartypants': True,
594 'master_doc': filename}
595 app = Sphinx(srcdir=srcTempDir, confdir=None, outdir=outTempDir,
596 doctreedir=doctreeTempDir, buildername='html',
597 confoverrides=overrides, status=None,
598 warning=io.StringIO())
599 app.build(force_all=True, filenames=None)
600
601 basePath = os.path.join(outTempDir, filename)
602 with open(basePath + '.html', 'r', encoding='utf-8') as fh:
603 html = fh.read()
604 finally:
605 shutil.rmtree(srcTempDir)
606 shutil.rmtree(outTempDir)
607 shutil.rmtree(doctreeTempDir)
608
609 return html
610
611 def __convertReSTDocutils(self, text, htmlFormat):
612 """
613 Private method to convert ReST text into HTML using 'docutils'.
614
615 @param text text to be processed (string)
616 @param htmlFormat HTML format to be generated (string)
617 @return processed HTML (string)
618 """
619 if 'sphinx' in sys.modules:
620 # Make sure any Sphinx polution of docutils has been removed.
621 unloadKeys = [k for k in sys.modules.keys()
622 if k.startswith(('docutils', 'sphinx'))]
623 for key in unloadKeys:
624 sys.modules.pop(key)
625
626 try:
627 import docutils.core # __IGNORE_EXCEPTION__
628 import docutils.utils # __IGNORE_EXCEPTION__
629 except ImportError:
630 return self.tr(
631 """<p>ReStructuredText preview requires the"""
632 """ <b>python-docutils</b> package.<br/>Install it with"""
633 """ your package manager, 'pip install docutils' or see"""
634 """ <a href="http://pypi.python.org/pypi/docutils">"""
635 """this page.</a></p>""")
636
637 # redirect sys.stderr because we are not interested in it here
638 origStderr = sys.stderr
639 sys.stderr = io.StringIO()
640 try:
641 html = docutils.core.publish_string(
642 text, writer_name=htmlFormat.lower()).decode("utf-8")
643 except docutils.utils.SystemMessage as err:
644 errStr = str(err).split(":")[-1].replace("\n", "<br/>")
645 return self.tr(
646 """<p>Docutils returned an error:</p><p>{0}</p>"""
647 ).format(errStr)
648
649 sys.stderr = origStderr
650 return html
651
652 def __convertMarkdown(self, text, convertNewLineToBreak,
653 usePyMdownExtensions, htmlFormat):
654 """
655 Private method to convert Markdown text into HTML.
656
657 @param text text to be processed
658 @type str
659 @param convertNewLineToBreak flag indicating to convert new lines
660 to HTML break (Markdown only)
661 @type bool
662 @param usePyMdownExtensions flag indicating to enable the PyMdown
663 extensions, if they are available
664 @type bool
665 @param htmlFormat HTML format to be generated by markdown
666 @type str
667 @return processed HTML
668 @rtype str
669 """
670 try:
671 import markdown # __IGNORE_EXCEPTION__
672 except ImportError:
673 return self.tr(
674 """<p>Markdown preview requires the <b>Markdown</b> """
675 """package.<br/>Install it with your package manager,"""
676 """ 'pip install Markdown' or see """
677 """<a href="http://pythonhosted.org/Markdown/install.html">"""
678 """installation instructions.</a></p>""")
679
680 from . import PreviewerHTMLStyles
681 from . import MarkdownExtensions
682
683 extensions = []
684
685 mermaidNeeded = False
686 if (
687 Preferences.getEditor("PreviewMarkdownMermaid") and
688 MarkdownExtensions.MermaidRegexFullText.search(text)
689 ):
690 extensions.append(MarkdownExtensions.MermaidExtension())
691 mermaidNeeded = True
692
693 if convertNewLineToBreak:
694 extensions.append('nl2br')
695
696 pyMdown = False
697 if usePyMdownExtensions:
698 with contextlib.suppress(ImportError):
699 import pymdownx # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
700 # PyPI package is 'pymdown-extensions'
701
702 extensions.extend([
703 'toc',
704 'pymdownx.extra', 'pymdownx.caret', 'pymdownx.emoji',
705 'pymdownx.mark', 'pymdownx.tilde', 'pymdownx.keys',
706 'pymdownx.tasklist', 'pymdownx.smartsymbols',
707 ])
708 pyMdown = True
709
710 if not pyMdown:
711 extensions.extend(['extra', 'toc'])
712
713 # version 2.0 supports only extension names, not instances
714 if (
715 markdown.version_info[0] > 2 or
716 (markdown.version_info[0] == 2 and
717 markdown.version_info[1] > 0)
718 ):
719 extensions.append(MarkdownExtensions.SimplePatternExtension())
720
721 if Preferences.getEditor("PreviewMarkdownMathJax"):
722 mathjax = (
723 "<script type='text/javascript' id='MathJax-script' async"
724 " src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/"
725 "tex-chtml.js'>\n"
726 "</script>\n"
727 )
728 # prepare text for mathjax
729 text = (
730 text
731 .replace(r"\(", r"\\(")
732 .replace(r"\)", r"\\)")
733 .replace(r"\[", r"\\[")
734 .replace(r"\]", r"\\]")
735 )
736 else:
737 mathjax = ""
738
739 if mermaidNeeded:
740 mermaid = (
741 "<script type='text/javascript' id='Mermaid-script'"
742 " src='https://unpkg.com/mermaid@8/dist/mermaid.min.js'>\n"
743 "</script>\n"
744 )
745 if ericApp().usesDarkPalette():
746 mermaid_initialize = (
747 "<script>mermaid.initialize({"
748 "theme: 'dark', "
749 "startOnLoad:true"
750 "});</script>"
751 )
752 else:
753 mermaid_initialize = (
754 "<script>mermaid.initialize({"
755 "theme: 'default', "
756 "startOnLoad:true"
757 "});</script>"
758 )
759 else:
760 mermaid = ""
761 mermaid_initialize = ""
762
763 htmlFormat = Preferences.getEditor("PreviewMarkdownHTMLFormat").lower()
764 body = markdown.markdown(text, extensions=extensions,
765 output_format=htmlFormat.lower())
766 style = (
767 (PreviewerHTMLStyles.css_markdown_dark +
768 PreviewerHTMLStyles.css_pygments_dark)
769 if ericApp().usesDarkPalette() else
770 (PreviewerHTMLStyles.css_markdown_light +
771 PreviewerHTMLStyles.css_pygments_light)
772 )
773
774 if htmlFormat == "xhtml1":
775 head = (
776 '''<!DOCTYPE html PUBLIC "-//W3C//DTD'''
777 ''' XHTML 1.0 Transitional//EN"\n'''
778 ''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional'''
779 '''.dtd">\n'''
780 '''<html xmlns="http://www.w3.org/1999/xhtml">\n'''
781 )
782 elif htmlFormat == "html5":
783 head = (
784 '''<!DOCTYPE html>\n'''
785 '''<html lang="EN">\n'''
786 )
787 else:
788 head = '<html lang="EN">\n'
789 head += '''<head>\n'''
790 head += (
791 '''<meta name="Generator" content="eric" />\n'''
792 '''<meta http-equiv="Content-Type" '''
793 '''content="text/html; charset=utf-8" />\n'''
794 '''{0}'''
795 '''{1}'''
796 '''<style type="text/css">'''
797 '''{2}'''
798 '''</style>\n'''
799 '''</head>\n'''
800 '''<body>\n'''
801 ).format(mathjax, mermaid, style)
802
803 foot = '''\n</body>\n</html>\n'''
804
805 return head + body + mermaid_initialize + foot

eric ide

mercurial