--- a/QScintilla/Exporters/ExporterHTML.py Mon Aug 30 12:30:29 2010 +0200 +++ b/QScintilla/Exporters/ExporterHTML.py Mon Aug 30 15:24:28 2010 +0200 @@ -21,6 +21,326 @@ import Preferences import Utilities +class HTMLGenerator(object): + """ + Class implementing an HTML generator for exporting source code. + """ + def __init__(self, editor): + """ + Constructor + + @param editor reference to the editor object (QScintilla.Editor.Editor) + """ + self.editor = editor + + def generate(self, tabSize = 4, useTabs = False, wysiwyg = True, folding = False, + onlyStylesUsed = False, titleFullPath = False): + """ + Public method to generate HTML for the source editor. + + @keyparam tabSize size of tabs (integer) + @keyparam useTabs flag indicating the use of tab characters (boolean) + @keyparam wysiwyg flag indicating colorization (boolean) + @keyparam folding flag indicating usage of fold markers + @keyparam onlyStylesUsed flag indicating to include only style definitions + for styles used in the source (boolean) + @keyparam titleFullPath flag indicating to include the full file path + in the title tag (boolean) + @return generated HTML text (string) + """ + self.editor.recolor(0, -1) + + lengthDoc = self.editor.length() + styleIsUsed = {} + if onlyStylesUsed: + for index in range(QsciScintilla.STYLE_MAX + 1): + styleIsUsed[index] = False + # check the used styles + pos = 0 + while pos < lengthDoc: + styleIsUsed[self.editor.styleAt(pos) & 0x7F] = True + pos += 1 + else: + for index in range(QsciScintilla.STYLE_MAX + 1): + styleIsUsed[index] = True + styleIsUsed[QsciScintilla.STYLE_DEFAULT] = True + + html = '''<!DOCTYPE html PUBLIC "-//W3C//DTD''' \ + ''' XHTML 1.0 Transitional//EN"\n''' \ + ''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n''' \ + '''<html xmlns="http://www.w3.org/1999/xhtml">\n''' \ + '''<head>\n''' + if titleFullPath: + html += '''<title>{0}</title>\n'''.format(self.editor.getFileName()) + else: + html += '''<title>{0}</title>\n'''.format( + os.path.basename(self.editor.getFileName())) + html += '''<meta name="Generator" content="eric5" />\n''' \ + '''<meta http-equiv="Content-Type" ''' \ + '''content="text/html; charset=utf-8" />\n''' + if folding: + html += '''<script language="JavaScript" type="text/javascript">\n''' \ + '''<!--\n''' \ + '''function symbol(id, sym) {\n''' \ + ''' if (id.textContent == undefined) {\n''' \ + ''' id.innerText = sym;\n''' \ + ''' } else {\n''' \ + ''' id.textContent = sym;\n''' \ + ''' }\n''' \ + '''}\n''' \ + '''function toggle(id) {\n''' \ + ''' var thislayer = document.getElementById('ln' + id);\n''' \ + ''' id -= 1;\n''' \ + ''' var togline = document.getElementById('hd' + id);\n''' \ + ''' var togsym = document.getElementById('bt' + id);\n''' \ + ''' if (thislayer.style.display == 'none') {\n''' \ + ''' thislayer.style.display = 'block';\n''' \ + ''' togline.style.textDecoration = 'none';\n''' \ + ''' symbol(togsym, '- ');\n''' \ + ''' } else {\n''' \ + ''' thislayer.style.display = 'none';\n''' \ + ''' togline.style.textDecoration = 'underline';\n''' \ + ''' symbol(togsym, '+ ');\n''' \ + ''' }\n''' \ + '''}\n''' \ + '''//-->\n''' \ + '''</script>\n''' + + lex = self.editor.getLexer() + if lex: + bgColour = lex.paper(QsciScintilla.STYLE_DEFAULT).name() + else: + bgColour = self.editor.paper().name() + + html += '''<style type="text/css">\n''' + if lex: + istyle = 0 + while istyle <= QsciScintilla.STYLE_MAX: + if (istyle <= QsciScintilla.STYLE_DEFAULT or \ + istyle > QsciScintilla.STYLE_LASTPREDEFINED) and \ + styleIsUsed[istyle]: + if lex.description(istyle) or \ + istyle == QsciScintilla.STYLE_DEFAULT: + font = lex.font(istyle) + colour = lex.color(istyle) + paper = lex.paper(istyle) + if istyle == QsciScintilla.STYLE_DEFAULT: + html += '''span {\n''' + else: + html += '''.S{0:d} {{\n'''.format(istyle) + if font.italic(): + html += ''' font-style: italic;\n''' + if font.bold(): + html += ''' font-weight: bold;\n''' + if wysiwyg: + html += ''' font-family: '{0}';\n'''.format( + font.family()) + html += ''' color: {0};\n'''.format(colour.name()) + if istyle != QsciScintilla.STYLE_DEFAULT and \ + bgColour != paper.name(): + html += ''' background: {0};\n'''.format( + paper.name()) + html += ''' text-decoration: inherit;\n''' + if wysiwyg: + html += ''' font-size: {0:d}pt;\n'''.format( + QFontInfo(font).pointSize()) + html += '''}\n''' + else: + styleIsUsed[istyle] = False + istyle += 1 + else: + colour = self.editor.color() + paper = self.editor.paper() + font = Preferences.getEditorOtherFonts("DefaultFont") + html += '''.S0 {\n''' + if font.italic(): + html += ''' font-style: italic;\n''' + if font.bold(): + html += ''' font-weight: bold;\n''' + if wysiwyg: + html += ''' font-family: '{0}';\n'''.format(font.family()) + html += ''' color: {0};\n'''.format(colour.name()) + if bgColour != paper.name(): + html += ''' background: {0};\n'''.format(paper.name()) + html += ''' text-decoration: inherit;\n''' + if wysiwyg: + html += ''' font-size: {0:d}pt;\n'''.format( + QFontInfo(font).pointSize()) + html += '''}\n''' + html += '''</style>\n''' + html += '''</head>\n''' + + html += '''<body bgcolor="{0}">\n'''.format(bgColour) + line = self.editor.lineAt(0) + level = self.editor.foldLevelAt(line) - QsciScintilla.SC_FOLDLEVELBASE + levelStack = [level] + styleCurrent = self.editor.styleAt(0) + inStyleSpan = False + inFoldSpan = False + # Global span for default attributes + if wysiwyg: + html += '''<span>''' + else: + html += '''<pre>''' + + if folding: + if self.editor.foldFlagsAt(line) & \ + QsciScintilla.SC_FOLDLEVELHEADERFLAG: + html += '''<span id="hd{0:d}" onclick="toggle('{1:d}')">'''\ + .format(line, line + 1) + html += '''<span id="bt{0:d}">- </span>'''.format(line) + inFoldSpan = True + else: + html += ''' ''' + + if styleIsUsed[styleCurrent]: + html += '''<span class="S{0:0d}">'''.format(styleCurrent) + inStyleSpan = True + + column = 0 + pos = 0 + utf8 = self.editor.isUtf8() + utf8Ch = b"" + utf8Len = 0 + + while pos < lengthDoc: + ch = self.editor.byteAt(pos) + style = self.editor.styleAt(pos) + if style != styleCurrent: + if inStyleSpan: + html += '''</span>''' + inStyleSpan = False + if ch not in [b'\r', b'\n']: # no need of a span for the EOL + if styleIsUsed[style]: + html += '''<span class="S{0:d}">'''.format(style) + inStyleSpan = True + styleCurrent = style + + if ch == b' ': + if wysiwyg: + prevCh = b'' + if column == 0: + # at start of line, must put a + # because regular space will be collapsed + prevCh = b' ' + while pos < lengthDoc and self.editor.byteAt(pos) == b' ': + if prevCh != b' ': + html += ' ' + else: + html += ''' ''' + prevCh = self.editor.byteAt(pos) + pos += 1 + column += 1 + pos -= 1 + # the last incrementation will be done by the outer loop + else: + html += ' ' + column += 1 + elif ch == b'\t': + ts = tabSize - (column % tabSize) + if wysiwyg: + html += ''' ''' * ts + column += ts + else: + if tabs: + html += '\t' + column += 1 + else: + html += ' ' * ts + column += ts + elif ch in [b'\r', b'\n']: + if inStyleSpan: + html += '''</span>''' + inStyleSpan = False + if inFoldSpan: + html += '''</span>''' + inFoldSpan = False + if ch == b'\r' and self.editor.byteAt(pos + 1) == b'\n': + pos += 1 # CR+LF line ending, skip the "extra" EOL char + column = 0 + if wysiwyg: + html += '''<br />''' + + styleCurrent = self.editor.styleAt(pos + 1) + if folding: + line = self.editor.lineAt(pos + 1) + newLevel = self.editor.foldLevelAt(line) + + if newLevel < level: + while levelStack[-1] > newLevel: + html += '''</span>''' + levelStack.pop() + html += '\n' # here to get clean code + if newLevel > level: + html += '''<span id="ln{0:d}">'''.format(line) + levelStack.append(newLevel) + if self.editor.foldFlagsAt(line) & \ + QsciScintilla.SC_FOLDLEVELHEADERFLAG: + html += \ + '''<span id="hd{0:d}" onclick="toggle('{1:d}')">'''\ + .format(line, line + 1) + html += '''<span id="bt{0:d}">- </span>'''.format(line) + inFoldSpan = True + else: + html += ''' ''' + level = newLevel + else: + html += '\n' + + if styleIsUsed[styleCurrent] and \ + self.editor.byteAt(pos + 1) not in [b'\r', b'\n']: + # We know it's the correct next style, + # but no (empty) span for an empty line + html += '''<span class="S{0:0d}">'''.format(styleCurrent) + inStyleSpan = True + else: + if ch == b'<': + html += '''<''' + elif ch == b'>': + html += '''>''' + elif ch == b'&': + html += '''&''' + else: + if ord(ch) > 127 and utf8: + utf8Ch += ch + if utf8Len == 0: + if (utf8Ch[0] & 0xF0) == 0xF0: + utf8Len = 4 + elif (utf8Ch[0] & 0xE0) == 0xE0: + utf8Len = 3 + elif (utf8Ch[0] & 0xC0) == 0xC0: + utf8Len = 2 + column -= 1 # will be incremented again later + elif len(utf8Ch) == utf8Len: + ch = utf8Ch.decode('utf8') + html += Utilities.html_encode(ch) + utf8Ch = b"" + utf8Len = 0 + else: + column -= 1 # will be incremented again later + else: + html += ch.decode() + column += 1 + + pos += 1 + + if inStyleSpan: + html += '''</span>''' + + if folding: + while levelStack: + html += '''</span>''' + levelStack.pop() + + if wysiwyg: + html += '''</span>''' + else: + html += '''</pre>''' + + html += '''</body>\n</html>\n''' + + return html + class ExporterHTML(ExporterBase): """ Class implementing an exporter for HTML. @@ -46,8 +366,6 @@ QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() - self.editor.recolor(0, -1) - tabSize = Preferences.getEditor("TabWidth") if tabSize == 0: tabSize = 4 @@ -57,300 +375,19 @@ titleFullPath = Preferences.getEditorExporter("HTML/FullPathAsTitle") tabs = Preferences.getEditorExporter("HTML/UseTabs") - lengthDoc = self.editor.length() - styleIsUsed = {} - if onlyStylesUsed: - for index in range(QsciScintilla.STYLE_MAX + 1): - styleIsUsed[index] = False - # check the used styles - pos = 0 - while pos < lengthDoc: - styleIsUsed[self.editor.styleAt(pos) & 0x7F] = True - pos += 1 - else: - for index in range(QsciScintilla.STYLE_MAX + 1): - styleIsUsed[index] = True - styleIsUsed[QsciScintilla.STYLE_DEFAULT] = True + generator = HTMLGenerator(self.editor) + html = generator.generate( + tabSize = tabSize, + useTabs = tabs, + wysiwyg = wysiwyg, + folding = folding, + onlyStylesUsed = onlyStylesUsed, + titleFullPath = titleFullPath + ) try: f = open(filename, "w", encoding = "utf-8") - - f.write( - '''<!DOCTYPE html PUBLIC "-//W3C//DTD''' - ''' XHTML 1.0 Transitional//EN"\n''') - f.write( - ''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n''') - f.write('''<html xmlns="http://www.w3.org/1999/xhtml">\n''') - f.write('''<head>\n''') - if titleFullPath: - f.write('''<title>{0}</title>\n'''.format(self.editor.getFileName())) - else: - f.write('''<title>{0}</title>\n'''.format( - os.path.basename(self.editor.getFileName()))) - f.write('''<meta name="Generator" content="eric5" />\n''') - f.write('''<meta http-equiv="Content-Type" ''' - '''content="text/html; charset=utf-8" />\n''') - if folding: - f.write('''<script language="JavaScript" type="text/javascript">\n''' - '''<!--\n''' - '''function symbol(id, sym) {\n''' - ''' if (id.textContent == undefined) {\n''' - ''' id.innerText = sym;\n''' - ''' } else {\n''' - ''' id.textContent = sym;\n''' - ''' }\n''' - '''}\n''' - '''function toggle(id) {\n''' - ''' var thislayer = document.getElementById('ln' + id);\n''' - ''' id -= 1;\n''' - ''' var togline = document.getElementById('hd' + id);\n''' - ''' var togsym = document.getElementById('bt' + id);\n''' - ''' if (thislayer.style.display == 'none') {\n''' - ''' thislayer.style.display = 'block';\n''' - ''' togline.style.textDecoration = 'none';\n''' - ''' symbol(togsym, '- ');\n''' - ''' } else {\n''' - ''' thislayer.style.display = 'none';\n''' - ''' togline.style.textDecoration = 'underline';\n''' - ''' symbol(togsym, '+ ');\n''' - ''' }\n''' - '''}\n''' - '''//-->\n''' - '''</script>\n''' - ) - - lex = self.editor.getLexer() - if lex: - bgColour = lex.paper(QsciScintilla.STYLE_DEFAULT).name() - else: - bgColour = self.editor.paper().name() - - f.write('''<style type="text/css">\n''') - if lex: - istyle = 0 - while istyle <= QsciScintilla.STYLE_MAX: - if (istyle <= QsciScintilla.STYLE_DEFAULT or \ - istyle > QsciScintilla.STYLE_LASTPREDEFINED) and \ - styleIsUsed[istyle]: - if lex.description(istyle) or \ - istyle == QsciScintilla.STYLE_DEFAULT: - font = lex.font(istyle) - colour = lex.color(istyle) - paper = lex.paper(istyle) - if istyle == QsciScintilla.STYLE_DEFAULT: - f.write('''span {\n''') - else: - f.write('''.S{0:d} {\n'''.format(istyle)) - if font.italic(): - f.write(''' font-style: italic;\n''') - if font.bold(): - f.write(''' font-weight: bold;\n''') - if wysiwyg: - f.write(''' font-family: '{0}';\n'''.format( - font.family())) - f.write(''' color: {0};\n'''.format(colour.name())) - if istyle != QsciScintilla.STYLE_DEFAULT and \ - bgColour != paper.name(): - f.write(''' background: {0};\n'''.format( - paper.name())) - f.write(''' text-decoration: inherit;\n''') - if wysiwyg: - f.write(''' font-size: {0:d}pt;\n'''.format( - QFontInfo(font).pointSize())) - f.write('''}\n''') - else: - styleIsUsed[istyle] = False - istyle += 1 - else: - colour = self.editor.color() - paper = self.editor.paper() - font = Preferences.getEditorOtherFonts("DefaultFont") - f.write('''.S0 {\n''') - if font.italic(): - f.write(''' font-style: italic;\n''') - if font.bold(): - f.write(''' font-weight: bold;\n''') - if wysiwyg: - f.write(''' font-family: '{0}';\n'''.format(font.family())) - f.write(''' color: {0};\n'''.format(colour.name())) - if bgColour != paper.name(): - f.write(''' background: {0};\n'''.format(paper.name())) - f.write(''' text-decoration: inherit;\n''') - if wysiwyg: - f.write(''' font-size: {0:d}pt;\n'''.format( - QFontInfo(font).pointSize())) - f.write('''}\n''') - f.write('''</style>\n''') - f.write('''</head>\n''') - - f.write('''<body bgcolor="{0}">\n'''.format(bgColour)) - line = self.editor.lineAt(0) - level = self.editor.foldLevelAt(line) - QsciScintilla.SC_FOLDLEVELBASE - levelStack = [level] - styleCurrent = self.editor.styleAt(0) - inStyleSpan = False - inFoldSpan = False - # Global span for default attributes - if wysiwyg: - f.write('''<span>''') - else: - f.write('''<pre>''') - - if folding: - if self.editor.foldFlagsAt(line) & \ - QsciScintilla.SC_FOLDLEVELHEADERFLAG: - f.write('''<span id="hd{0:d}" onclick="toggle('{1:d}')">'''\ - .format(line, line + 1)) - f.write('''<span id="bt{0:d}">- </span>'''.format(line)) - inFoldSpan = True - else: - f.write(''' ''') - - if styleIsUsed[styleCurrent]: - f.write('''<span class="S{0:0d}">'''.format(styleCurrent)) - inStyleSpan = True - - column = 0 - pos = 0 - utf8 = self.editor.isUtf8() - utf8Ch = b"" - utf8Len = 0 - - while pos < lengthDoc: - ch = self.editor.byteAt(pos) - style = self.editor.styleAt(pos) - if style != styleCurrent: - if inStyleSpan: - f.write('''</span>''') - inStyleSpan = False - if ch not in [b'\r', b'\n']: # no need of a span for the EOL - if styleIsUsed[style]: - f.write('''<span class="S{0:d}">'''.format(style)) - inStyleSpan = True - styleCurrent = style - - if ch == b' ': - if wysiwyg: - prevCh = b'' - if column == 0: - # at start of line, must put a - # because regular space will be collapsed - prevCh = b' ' - while pos < lengthDoc and self.editor.byteAt(pos) == b' ': - if prevCh != b' ': - f.write(' ') - else: - f.write(''' ''') - prevCh = self.editor.byteAt(pos) - pos += 1 - column += 1 - pos -= 1 - # the last incrementation will be done by the outer loop - else: - f.write(' ') - column += 1 - elif ch == b'\t': - ts = tabSize - (column % tabSize) - if wysiwyg: - f.write(''' ''' * ts) - column += ts - else: - if tabs: - f.write('\t') - column += 1 - else: - f.write(' ' * ts) - column += ts - elif ch in [b'\r', b'\n']: - if inStyleSpan: - f.write('''</span>''') - inStyleSpan = False - if inFoldSpan: - f.write('''</span>''') - inFoldSpan = False - if ch == b'\r' and self.editor.byteAt(pos + 1) == b'\n': - pos += 1 # CR+LF line ending, skip the "extra" EOL char - column = 0 - if wysiwyg: - f.write('''<br />''') - - styleCurrent = self.editor.styleAt(pos + 1) - if folding: - line = self.editor.lineAt(pos + 1) - newLevel = self.editor.foldLevelAt(line) - - if newLevel < level: - while levelStack[-1] > newLevel: - f.write('''</span>''') - levelStack.pop() - f.write('\n') # here to get clean code - if newLevel > level: - f.write('''<span id="ln{0:d}">'''.format(line)) - levelStack.append(newLevel) - if self.editor.foldFlagsAt(line) & \ - QsciScintilla.SC_FOLDLEVELHEADERFLAG: - f.write( - '''<span id="hd{0:d}" onclick="toggle('{1:d}')">'''\ - .format(line, line + 1)) - f.write('''<span id="bt{0:d}">- </span>'''.format(line)) - inFoldSpan = True - else: - f.write(''' ''') - level = newLevel - else: - f.write('\n') - - if styleIsUsed[styleCurrent] and \ - self.editor.byteAt(pos + 1) not in [b'\r', b'\n']: - # We know it's the correct next style, - # but no (empty) span for an empty line - f.write('''<span class="S{0:0d}">'''.format(styleCurrent)) - inStyleSpan = True - else: - if ch == b'<': - f.write('''<''') - elif ch == b'>': - f.write('''>''') - elif ch == b'&': - f.write('''&''') - else: - if ord(ch) > 127 and utf8: - utf8Ch += ch - if utf8Len == 0: - if (utf8Ch[0] & 0xF0) == 0xF0: - utf8Len = 4 - elif (utf8Ch[0] & 0xE0) == 0xE0: - utf8Len = 3 - elif (utf8Ch[0] & 0xC0) == 0xC0: - utf8Len = 2 - column -= 1 # will be incremented again later - elif len(utf8Ch) == utf8Len: - ch = utf8Ch.decode('utf8') - f.write(Utilities.html_encode(ch)) - utf8Ch = b"" - utf8Len = 0 - else: - column -= 1 # will be incremented again later - else: - f.write(ch.decode()) - column += 1 - - pos += 1 - - if inStyleSpan: - f.write('''</span>''') - - if folding: - while levelStack: - f.write('''</span>''') - levelStack.pop() - - if wysiwyg: - f.write('''</span>''') - else: - f.write('''</pre>''') - - f.write('''</body>\n</html>\n''') + f.write(html) f.close() except IOError as err: QApplication.restoreOverrideCursor()