|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing an exporter for HTML. |
|
8 """ |
|
9 |
|
10 # This code is a port of the C++ code found in SciTE 1.74 |
|
11 # Original code: Copyright 1998-2006 by Neil Hodgson <neilh@scintilla.org> |
|
12 |
|
13 import os |
|
14 import sys |
|
15 import io |
|
16 import contextlib |
|
17 |
|
18 from PyQt5.QtGui import QFontInfo |
|
19 from PyQt5.QtWidgets import QInputDialog |
|
20 from PyQt5.Qsci import QsciScintilla |
|
21 |
|
22 from E5Gui import E5MessageBox |
|
23 from E5Gui.E5OverrideCursor import E5OverrideCursor |
|
24 |
|
25 from .ExporterBase import ExporterBase |
|
26 |
|
27 import Preferences |
|
28 import Utilities |
|
29 |
|
30 |
|
31 class HTMLGenerator: |
|
32 """ |
|
33 Class implementing an HTML generator for exporting source code. |
|
34 """ |
|
35 def __init__(self, editor): |
|
36 """ |
|
37 Constructor |
|
38 |
|
39 @param editor reference to the editor object (QScintilla.Editor.Editor) |
|
40 """ |
|
41 self.editor = editor |
|
42 |
|
43 def generate(self, tabSize=4, useTabs=False, wysiwyg=True, folding=False, |
|
44 onlyStylesUsed=False, titleFullPath=False): |
|
45 """ |
|
46 Public method to generate HTML for the source editor. |
|
47 |
|
48 @param tabSize size of tabs (integer) |
|
49 @param useTabs flag indicating the use of tab characters (boolean) |
|
50 @param wysiwyg flag indicating colorization (boolean) |
|
51 @param folding flag indicating usage of fold markers |
|
52 @param onlyStylesUsed flag indicating to include only style |
|
53 definitions for styles used in the source (boolean) |
|
54 @param titleFullPath flag indicating to include the full file path |
|
55 in the title tag (boolean) |
|
56 @return generated HTML text (string) |
|
57 """ |
|
58 self.editor.recolor(0, -1) |
|
59 |
|
60 lengthDoc = self.editor.length() |
|
61 styleIsUsed = {} |
|
62 if onlyStylesUsed: |
|
63 for index in range(QsciScintilla.STYLE_MAX + 1): |
|
64 styleIsUsed[index] = False |
|
65 # check the used styles |
|
66 pos = 0 |
|
67 while pos < lengthDoc: |
|
68 styleIsUsed[self.editor.styleAt(pos) & 0x7F] = True |
|
69 pos += 1 |
|
70 else: |
|
71 for index in range(QsciScintilla.STYLE_MAX + 1): |
|
72 styleIsUsed[index] = True |
|
73 styleIsUsed[QsciScintilla.STYLE_DEFAULT] = True |
|
74 |
|
75 html = ( |
|
76 '''<!DOCTYPE html PUBLIC "-//W3C//DTD''' |
|
77 ''' XHTML 1.0 Transitional//EN"\n''' |
|
78 ''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">''' |
|
79 '''\n''' |
|
80 '''<html xmlns="http://www.w3.org/1999/xhtml">\n''' |
|
81 '''<head>\n''' |
|
82 ) |
|
83 if titleFullPath: |
|
84 html += '''<title>{0}</title>\n'''.format( |
|
85 self.editor.getFileName()) |
|
86 else: |
|
87 html += '''<title>{0}</title>\n'''.format( |
|
88 os.path.basename(self.editor.getFileName())) |
|
89 html += ( |
|
90 '''<meta name="Generator" content="eric" />\n''' |
|
91 '''<meta http-equiv="Content-Type" ''' |
|
92 '''content="text/html; charset=utf-8" />\n''' |
|
93 ) |
|
94 if folding: |
|
95 html += ( |
|
96 '''<script language="JavaScript" type="text/javascript">\n''' |
|
97 '''<!--\n''' |
|
98 '''function symbol(id, sym) {\n''' |
|
99 ''' if (id.textContent == undefined) {\n''' |
|
100 ''' id.innerText = sym;\n''' |
|
101 ''' } else {\n''' |
|
102 ''' id.textContent = sym;\n''' |
|
103 ''' }\n''' |
|
104 '''}\n''' |
|
105 '''function toggle(id) {\n''' |
|
106 ''' var thislayer = document.getElementById('ln' + id);\n''' |
|
107 ''' id -= 1;\n''' |
|
108 ''' var togline = document.getElementById('hd' + id);\n''' |
|
109 ''' var togsym = document.getElementById('bt' + id);\n''' |
|
110 ''' if (thislayer.style.display == 'none') {\n''' |
|
111 ''' thislayer.style.display = 'block';\n''' |
|
112 ''' togline.style.textDecoration = 'none';\n''' |
|
113 ''' symbol(togsym, '- ');\n''' |
|
114 ''' } else {\n''' |
|
115 ''' thislayer.style.display = 'none';\n''' |
|
116 ''' togline.style.textDecoration = 'underline';\n''' |
|
117 ''' symbol(togsym, '+ ');\n''' |
|
118 ''' }\n''' |
|
119 '''}\n''' |
|
120 '''//-->\n''' |
|
121 '''</script>\n''' |
|
122 ) |
|
123 |
|
124 lex = self.editor.getLexer() |
|
125 bgColour = ( |
|
126 lex.paper(QsciScintilla.STYLE_DEFAULT).name() |
|
127 if lex else |
|
128 self.editor.paper().name() |
|
129 ) |
|
130 |
|
131 html += '''<style type="text/css">\n''' |
|
132 if lex: |
|
133 istyle = 0 |
|
134 while istyle <= QsciScintilla.STYLE_MAX: |
|
135 if ( |
|
136 (istyle <= QsciScintilla.STYLE_DEFAULT or |
|
137 istyle > QsciScintilla.STYLE_LASTPREDEFINED) and |
|
138 styleIsUsed[istyle] |
|
139 ): |
|
140 if ( |
|
141 lex.description(istyle) or |
|
142 istyle == QsciScintilla.STYLE_DEFAULT |
|
143 ): |
|
144 font = lex.font(istyle) |
|
145 colour = lex.color(istyle) |
|
146 paper = lex.paper(istyle) |
|
147 if istyle == QsciScintilla.STYLE_DEFAULT: |
|
148 html += '''span {\n''' |
|
149 else: |
|
150 html += '''.S{0:d} {{\n'''.format(istyle) |
|
151 if font.italic(): |
|
152 html += ''' font-style: italic;\n''' |
|
153 if font.bold(): |
|
154 html += ''' font-weight: bold;\n''' |
|
155 if wysiwyg: |
|
156 html += ''' font-family: '{0}';\n'''.format( |
|
157 font.family()) |
|
158 html += ''' color: {0};\n'''.format(colour.name()) |
|
159 if ( |
|
160 istyle != QsciScintilla.STYLE_DEFAULT and |
|
161 bgColour != paper.name() |
|
162 ): |
|
163 html += ''' background: {0};\n'''.format( |
|
164 paper.name()) |
|
165 html += ''' text-decoration: inherit;\n''' |
|
166 if wysiwyg: |
|
167 html += ''' font-size: {0:d}pt;\n'''.format( |
|
168 QFontInfo(font).pointSize()) |
|
169 html += '''}\n''' |
|
170 |
|
171 # get substyles |
|
172 subs_start, subs_count = self.editor.getSubStyleRange( |
|
173 istyle) |
|
174 for subs_idx in range(subs_count): |
|
175 styleIsUsed[subs_idx - subs_start] = True |
|
176 font = lex.font(subs_start + subs_idx) |
|
177 colour = lex.color(subs_start + subs_idx) |
|
178 paper = lex.paper(subs_start + subs_idx) |
|
179 html += '.S{0:d} {{\n'.format( |
|
180 subs_idx - subs_start) |
|
181 if font.italic(): |
|
182 html += ' font-style: italic;\n' |
|
183 if font.bold(): |
|
184 html += ' font-weight: bold;\n' |
|
185 if wysiwyg: |
|
186 html += " font-family: '{0}';\n".format( |
|
187 font.family()) |
|
188 html += ' color: {0};\n'.format(colour.name()) |
|
189 if wysiwyg: |
|
190 html += ' font-size: {0:d}pt;\n'.format( |
|
191 QFontInfo(font).pointSize()) |
|
192 html += '}\n' |
|
193 # __IGNORE_WARNING_Y113__ |
|
194 else: |
|
195 styleIsUsed[istyle] = False |
|
196 istyle += 1 |
|
197 else: |
|
198 colour = self.editor.color() |
|
199 paper = self.editor.paper() |
|
200 font = Preferences.getEditorOtherFonts("DefaultFont") |
|
201 html += '''.S0 {\n''' |
|
202 if font.italic(): |
|
203 html += ''' font-style: italic;\n''' |
|
204 if font.bold(): |
|
205 html += ''' font-weight: bold;\n''' |
|
206 if wysiwyg: |
|
207 html += ''' font-family: '{0}';\n'''.format(font.family()) |
|
208 html += ''' color: {0};\n'''.format(colour.name()) |
|
209 if bgColour != paper.name(): |
|
210 html += ''' background: {0};\n'''.format(paper.name()) |
|
211 html += ''' text-decoration: inherit;\n''' |
|
212 if wysiwyg: |
|
213 html += ''' font-size: {0:d}pt;\n'''.format( |
|
214 QFontInfo(font).pointSize()) |
|
215 html += '''}\n''' |
|
216 html += '''</style>\n''' |
|
217 html += '''</head>\n''' |
|
218 |
|
219 html += '''<body bgcolor="{0}">\n'''.format(bgColour) |
|
220 line = self.editor.lineAt(0) |
|
221 level = self.editor.foldLevelAt(line) - QsciScintilla.SC_FOLDLEVELBASE |
|
222 levelStack = [level] |
|
223 styleCurrent = self.editor.styleAt(0) |
|
224 inStyleSpan = False |
|
225 inFoldSpan = False |
|
226 # Global span for default attributes |
|
227 if wysiwyg: |
|
228 html += '''<span>''' |
|
229 else: |
|
230 html += '''<pre>''' |
|
231 |
|
232 if folding: |
|
233 if ( |
|
234 self.editor.foldFlagsAt(line) & |
|
235 QsciScintilla.SC_FOLDLEVELHEADERFLAG |
|
236 ): |
|
237 html += ( |
|
238 '''<span id="hd{0:d}" onclick="toggle('{1:d}')">''' |
|
239 ).format(line, line + 1) |
|
240 html += '''<span id="bt{0:d}">- </span>'''.format(line) |
|
241 inFoldSpan = True |
|
242 else: |
|
243 html += ''' ''' |
|
244 |
|
245 if styleIsUsed[styleCurrent]: |
|
246 html += '''<span class="S{0:0d}">'''.format(styleCurrent) |
|
247 inStyleSpan = True |
|
248 |
|
249 column = 0 |
|
250 pos = 0 |
|
251 utf8 = self.editor.isUtf8() |
|
252 utf8Ch = b"" |
|
253 utf8Len = 0 |
|
254 |
|
255 while pos < lengthDoc: |
|
256 ch = self.editor.byteAt(pos) |
|
257 style = self.editor.styleAt(pos) |
|
258 if style != styleCurrent: |
|
259 if inStyleSpan: |
|
260 html += '''</span>''' |
|
261 inStyleSpan = False |
|
262 if ch not in [b'\r', b'\n']: # no need of a span for the EOL |
|
263 if styleIsUsed[style]: |
|
264 html += '''<span class="S{0:d}">'''.format(style) |
|
265 inStyleSpan = True |
|
266 styleCurrent = style |
|
267 |
|
268 if ch == b' ': |
|
269 if wysiwyg: |
|
270 prevCh = b'' |
|
271 if column == 0: |
|
272 # at start of line, must put a |
|
273 # because regular space will be collapsed |
|
274 prevCh = b' ' |
|
275 while pos < lengthDoc and self.editor.byteAt(pos) == b' ': |
|
276 if prevCh != b' ': |
|
277 html += ' ' |
|
278 else: |
|
279 html += ''' ''' |
|
280 prevCh = self.editor.byteAt(pos) |
|
281 pos += 1 |
|
282 column += 1 |
|
283 pos -= 1 |
|
284 # the last incrementation will be done by the outer loop |
|
285 else: |
|
286 html += ' ' |
|
287 column += 1 |
|
288 elif ch == b'\t': |
|
289 ts = tabSize - (column % tabSize) |
|
290 if wysiwyg: |
|
291 html += ''' ''' * ts |
|
292 column += ts |
|
293 else: |
|
294 if useTabs: |
|
295 html += '\t' |
|
296 column += 1 |
|
297 else: |
|
298 html += ' ' * ts |
|
299 column += ts |
|
300 elif ch in [b'\r', b'\n']: |
|
301 if inStyleSpan: |
|
302 html += '''</span>''' |
|
303 inStyleSpan = False |
|
304 if inFoldSpan: |
|
305 html += '''</span>''' |
|
306 inFoldSpan = False |
|
307 if ch == b'\r' and self.editor.byteAt(pos + 1) == b'\n': |
|
308 pos += 1 # CR+LF line ending, skip the "extra" EOL char |
|
309 column = 0 |
|
310 if wysiwyg: |
|
311 html += '''<br />''' |
|
312 |
|
313 styleCurrent = self.editor.styleAt(pos + 1) |
|
314 if folding: |
|
315 line = self.editor.lineAt(pos + 1) |
|
316 newLevel = self.editor.foldLevelAt(line) |
|
317 |
|
318 if newLevel < level: |
|
319 while levelStack[-1] > newLevel: |
|
320 html += '''</span>''' |
|
321 levelStack.pop() |
|
322 html += '\n' # here to get clean code |
|
323 if newLevel > level: |
|
324 html += '''<span id="ln{0:d}">'''.format(line) |
|
325 levelStack.append(newLevel) |
|
326 if ( |
|
327 self.editor.foldFlagsAt(line) & |
|
328 QsciScintilla.SC_FOLDLEVELHEADERFLAG |
|
329 ): |
|
330 html += ( |
|
331 '''<span id="hd{0:d}"''' |
|
332 ''' onclick="toggle('{1:d}')">''' |
|
333 ).format(line, line + 1) |
|
334 html += '''<span id="bt{0:d}">- </span>'''.format(line) |
|
335 inFoldSpan = True |
|
336 else: |
|
337 html += ''' ''' |
|
338 level = newLevel |
|
339 else: |
|
340 html += '\n' |
|
341 |
|
342 if ( |
|
343 styleIsUsed[styleCurrent] and |
|
344 self.editor.byteAt(pos + 1) not in [b'\r', b'\n'] |
|
345 ): |
|
346 # We know it's the correct next style, |
|
347 # but no (empty) span for an empty line |
|
348 html += '''<span class="S{0:0d}">'''.format(styleCurrent) |
|
349 inStyleSpan = True |
|
350 else: |
|
351 if ch == b'<': |
|
352 html += '''<''' |
|
353 elif ch == b'>': |
|
354 html += '''>''' |
|
355 elif ch == b'&': |
|
356 html += '''&''' |
|
357 else: |
|
358 if ord(ch) > 127 and utf8: |
|
359 utf8Ch += ch |
|
360 if utf8Len == 0: |
|
361 if (utf8Ch[0] & 0xF0) == 0xF0: |
|
362 utf8Len = 4 |
|
363 elif (utf8Ch[0] & 0xE0) == 0xE0: |
|
364 utf8Len = 3 |
|
365 elif (utf8Ch[0] & 0xC0) == 0xC0: |
|
366 utf8Len = 2 |
|
367 column -= 1 # will be incremented again later |
|
368 elif len(utf8Ch) == utf8Len: |
|
369 ch = utf8Ch.decode('utf8') |
|
370 html += Utilities.html_encode(ch) |
|
371 utf8Ch = b"" |
|
372 utf8Len = 0 |
|
373 else: |
|
374 column -= 1 # will be incremented again later |
|
375 else: |
|
376 html += ch.decode() |
|
377 column += 1 |
|
378 |
|
379 pos += 1 |
|
380 |
|
381 if inStyleSpan: |
|
382 html += '''</span>''' |
|
383 |
|
384 if folding: |
|
385 while levelStack: |
|
386 html += '''</span>''' |
|
387 levelStack.pop() |
|
388 |
|
389 if wysiwyg: |
|
390 html += '''</span>''' |
|
391 else: |
|
392 html += '''</pre>''' |
|
393 |
|
394 html += '''</body>\n</html>\n''' |
|
395 |
|
396 return html |
|
397 |
|
398 |
|
399 class ExporterHTML(ExporterBase): |
|
400 """ |
|
401 Class implementing an exporter for HTML. |
|
402 """ |
|
403 def __init__(self, editor, parent=None): |
|
404 """ |
|
405 Constructor |
|
406 |
|
407 @param editor reference to the editor object (QScintilla.Editor.Editor) |
|
408 @param parent parent object of the exporter (QObject) |
|
409 """ |
|
410 ExporterBase.__init__(self, editor, parent) |
|
411 |
|
412 def exportSource(self): |
|
413 """ |
|
414 Public method performing the export. |
|
415 """ |
|
416 filename = self._getFileName(self.tr("HTML Files (*.html)")) |
|
417 if not filename: |
|
418 return |
|
419 |
|
420 fn = self.editor.getFileName() |
|
421 extension = os.path.normcase(os.path.splitext(fn)[1][1:]) if fn else "" |
|
422 |
|
423 if ( |
|
424 extension in Preferences.getEditor( |
|
425 "PreviewMarkdownFileNameExtensions") or |
|
426 self.editor.getLanguage().lower() == "markdown" |
|
427 ): |
|
428 # export markdown to HTML |
|
429 colorSchemes = [ |
|
430 self.tr("Light Background Color"), |
|
431 self.tr("Dark Background Color"), |
|
432 ] |
|
433 colorScheme, ok = QInputDialog.getItem( |
|
434 None, |
|
435 self.tr("Markdown Export"), |
|
436 self.tr("Select color scheme:"), |
|
437 colorSchemes, |
|
438 0, False) |
|
439 if ok: |
|
440 colorSchemeIndex = colorSchemes.index(colorScheme) |
|
441 else: |
|
442 # light background as default |
|
443 colorSchemeIndex = 0 |
|
444 with E5OverrideCursor(): |
|
445 html = self.__generateFromMarkdown(colorSchemeIndex == 1) |
|
446 elif ( |
|
447 extension in Preferences.getEditor( |
|
448 "PreviewRestFileNameExtensions") or |
|
449 self.editor.getLanguage().lower() == "restructuredtext" |
|
450 ): |
|
451 # export ReST to HTML |
|
452 with E5OverrideCursor(): |
|
453 html = self.__generateFromReSTDocutils() |
|
454 else: |
|
455 tabSize = self.editor.getEditorConfig("TabWidth") |
|
456 if tabSize == 0: |
|
457 tabSize = 4 |
|
458 wysiwyg = Preferences.getEditorExporter("HTML/WYSIWYG") |
|
459 folding = Preferences.getEditorExporter("HTML/Folding") |
|
460 onlyStylesUsed = Preferences.getEditorExporter( |
|
461 "HTML/OnlyStylesUsed") |
|
462 titleFullPath = Preferences.getEditorExporter( |
|
463 "HTML/FullPathAsTitle") |
|
464 tabs = Preferences.getEditorExporter("HTML/UseTabs") |
|
465 |
|
466 with E5OverrideCursor(): |
|
467 generator = HTMLGenerator(self.editor) |
|
468 html = generator.generate( |
|
469 tabSize=tabSize, |
|
470 useTabs=tabs, |
|
471 wysiwyg=wysiwyg, |
|
472 folding=folding, |
|
473 onlyStylesUsed=onlyStylesUsed, |
|
474 titleFullPath=titleFullPath |
|
475 ) |
|
476 |
|
477 if html: |
|
478 with E5OverrideCursor(), open(filename, "w", encoding="utf-8" |
|
479 ) as f: |
|
480 try: |
|
481 f.write(html) |
|
482 except OSError as err: |
|
483 E5MessageBox.critical( |
|
484 self.editor, |
|
485 self.tr("Export source"), |
|
486 self.tr( |
|
487 """<p>The source could not be exported to""" |
|
488 """ <b>{0}</b>.</p><p>Reason: {1}</p>""") |
|
489 .format(filename, str(err))) |
|
490 else: |
|
491 E5MessageBox.critical( |
|
492 self.editor, |
|
493 self.tr("Export source"), |
|
494 self.tr( |
|
495 """<p>The source could not be exported to""" |
|
496 """ <b>{0}</b>.</p><p>Reason: No HTML code""" |
|
497 """ generated.</p>""") |
|
498 .format(filename)) |
|
499 |
|
500 def __generateFromReSTDocutils(self): |
|
501 """ |
|
502 Private method to convert ReST text into HTML using 'docutils'. |
|
503 |
|
504 @return processed HTML (string) |
|
505 """ |
|
506 if 'sphinx' in sys.modules: |
|
507 # Make sure any Sphinx polution of docutils has been removed. |
|
508 unloadKeys = [k for k in sys.modules.keys() |
|
509 if k.startswith(('docutils', 'sphinx'))] |
|
510 for key in unloadKeys: |
|
511 sys.modules.pop(key) |
|
512 |
|
513 try: |
|
514 import docutils.core # __IGNORE_EXCEPTION__ |
|
515 except ImportError: |
|
516 E5MessageBox.critical( |
|
517 self.editor, |
|
518 self.tr("Export source"), |
|
519 self.tr( |
|
520 """<p>ReStructuredText export requires the""" |
|
521 """ <b>python-docutils</b> package.<br/>Install it with""" |
|
522 """ your package manager, 'pip install docutils' or see""" |
|
523 """ <a href="http://pypi.python.org/pypi/docutils">""" |
|
524 """this page.</a></p>""") |
|
525 ) |
|
526 return "" |
|
527 |
|
528 htmlFormat = Preferences.getEditor( |
|
529 "PreviewRestDocutilsHTMLFormat").lower() |
|
530 # redirect sys.stderr because we are not interested in it here |
|
531 origStderr = sys.stderr |
|
532 sys.stderr = io.StringIO() |
|
533 html = docutils.core.publish_string( |
|
534 self.editor.text(), writer_name=htmlFormat).decode("utf-8") |
|
535 sys.stderr = origStderr |
|
536 return html |
|
537 |
|
538 def __generateFromMarkdown(self, useDarkScheme): |
|
539 """ |
|
540 Private method to convert Markdown text into HTML. |
|
541 |
|
542 @param useDarkScheme flag indicating to export using a dark color |
|
543 scheme |
|
544 @type bool |
|
545 @return processed HTML |
|
546 @rtype str |
|
547 """ |
|
548 try: |
|
549 import markdown # __IGNORE_EXCEPTION__ |
|
550 except ImportError: |
|
551 E5MessageBox.critical( |
|
552 self.editor, |
|
553 self.tr("Export source"), |
|
554 self.tr( |
|
555 """<p>Markdown export requires the <b>python-markdown""" |
|
556 """</b> package.<br/>Install it with your package""" |
|
557 """ manager, 'pip install docutils' or see """ |
|
558 """<a href="http://pythonhosted.org/Markdown/install""" |
|
559 """.html"> installation instructions.</a></p>""") |
|
560 ) |
|
561 return "" |
|
562 |
|
563 from UI.Previewers import PreviewerHTMLStyles |
|
564 from UI.Previewers import MarkdownExtensions |
|
565 |
|
566 extensions = [] |
|
567 |
|
568 text = self.editor.text() |
|
569 |
|
570 mermaidNeeded = False |
|
571 if ( |
|
572 Preferences.getEditor("PreviewMarkdownMermaid") and |
|
573 MarkdownExtensions.MermaidRegexFullText.search(text) |
|
574 ): |
|
575 extensions.append(MarkdownExtensions.MermaidExtension()) |
|
576 mermaidNeeded = True |
|
577 |
|
578 if Preferences.getEditor("PreviewMarkdownNLtoBR"): |
|
579 extensions.append('nl2br') |
|
580 |
|
581 pyMdown = False |
|
582 if Preferences.getEditor("PreviewMarkdownUsePyMdownExtensions"): |
|
583 with contextlib.suppress(ImportError): |
|
584 import pymdownx # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
|
585 # PyPI package is 'pymdown-extensions' |
|
586 |
|
587 extensions.extend([ |
|
588 'toc', |
|
589 'pymdownx.extra', 'pymdownx.caret', 'pymdownx.emoji', |
|
590 'pymdownx.mark', 'pymdownx.tilde', 'pymdownx.keys', |
|
591 'pymdownx.tasklist', 'pymdownx.smartsymbols', |
|
592 ]) |
|
593 pyMdown = True |
|
594 |
|
595 if not pyMdown: |
|
596 extensions.extend(['extra', 'toc']) |
|
597 |
|
598 # version 2.0 supports only extension names, not instances |
|
599 if ( |
|
600 markdown.version_info[0] > 2 or |
|
601 (markdown.version_info[0] == 2 and |
|
602 markdown.version_info[1] > 0) |
|
603 ): |
|
604 extensions.append(MarkdownExtensions.SimplePatternExtension()) |
|
605 |
|
606 if Preferences.getEditor("PreviewMarkdownMathJax"): |
|
607 mathjax = ( |
|
608 "<script type='text/javascript' id='MathJax-script' async" |
|
609 " src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/" |
|
610 "tex-chtml.js'>\n" |
|
611 "</script>\n" |
|
612 ) |
|
613 # prepare text for mathjax |
|
614 text = ( |
|
615 text |
|
616 .replace(r"\(", r"\\(") |
|
617 .replace(r"\)", r"\\)") |
|
618 .replace(r"\[", r"\\[") |
|
619 .replace(r"\]", r"\\]") |
|
620 ) |
|
621 else: |
|
622 mathjax = "" |
|
623 |
|
624 if mermaidNeeded: |
|
625 mermaid = ( |
|
626 "<script type='text/javascript' id='Mermaid-script'" |
|
627 " src='https://unpkg.com/mermaid@8/dist/mermaid.min.js'>\n" |
|
628 "</script>\n" |
|
629 ) |
|
630 if useDarkScheme: |
|
631 mermaid_initialize = ( |
|
632 "<script>mermaid.initialize({" |
|
633 "theme: 'dark', " |
|
634 "startOnLoad:true" |
|
635 "});</script>" |
|
636 ) |
|
637 else: |
|
638 mermaid_initialize = ( |
|
639 "<script>mermaid.initialize({" |
|
640 "theme: 'default', " |
|
641 "startOnLoad:true" |
|
642 "});</script>" |
|
643 ) |
|
644 else: |
|
645 mermaid = "" |
|
646 mermaid_initialize = "" |
|
647 |
|
648 htmlFormat = Preferences.getEditor("PreviewMarkdownHTMLFormat").lower() |
|
649 body = markdown.markdown(text, extensions=extensions, |
|
650 output_format=htmlFormat) |
|
651 style = ( |
|
652 (PreviewerHTMLStyles.css_markdown_dark + |
|
653 PreviewerHTMLStyles.css_pygments_dark) |
|
654 if useDarkScheme else |
|
655 (PreviewerHTMLStyles.css_markdown_light + |
|
656 PreviewerHTMLStyles.css_pygments_light) |
|
657 ) |
|
658 |
|
659 if htmlFormat == "xhtml1": |
|
660 head = ( |
|
661 '''<!DOCTYPE html PUBLIC "-//W3C//DTD''' |
|
662 ''' XHTML 1.0 Transitional//EN"\n''' |
|
663 ''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional''' |
|
664 '''.dtd">\n''' |
|
665 '''<html xmlns="http://www.w3.org/1999/xhtml">\n''' |
|
666 ) |
|
667 elif htmlFormat == "html5": |
|
668 head = ( |
|
669 '''<!DOCTYPE html>\n''' |
|
670 '''<html lang="EN">\n''' |
|
671 ) |
|
672 else: |
|
673 head = '<html lang="EN">\n' |
|
674 head += '''<head>\n''' |
|
675 if Preferences.getEditorExporter("HTML/FullPathAsTitle"): |
|
676 head += '''<title>{0}</title>\n'''.format( |
|
677 self.editor.getFileName()) |
|
678 else: |
|
679 head += '''<title>{0}</title>\n'''.format( |
|
680 os.path.basename(self.editor.getFileName())) |
|
681 head += ( |
|
682 '''<meta name="Generator" content="eric" />\n''' |
|
683 '''<meta http-equiv="Content-Type" ''' |
|
684 '''content="text/html; charset=utf-8" />\n''' |
|
685 '''{0}''' |
|
686 '''{1}''' |
|
687 '''<style type="text/css">''' |
|
688 '''{2}''' |
|
689 '''</style>\n''' |
|
690 '''</head>\n''' |
|
691 '''<body>\n''' |
|
692 ).format(mathjax, mermaid, style) |
|
693 |
|
694 foot = '''\n</body>\n</html>\n''' |
|
695 |
|
696 return head + body + mermaid_initialize + foot |