eric6/UI/Previewers/PreviewerHTML.py

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

eric ide

mercurial