src/eric7/QScintilla/Exporters/ExporterHTML.py

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

eric ide

mercurial