HTML detector and switcher

HTML messages are detected and if present, the top of the message
textedit displays a clickable area that switches HTML rendering on and
off.

Fixes #13
This commit is contained in:
mailchuck 2015-12-14 19:43:39 +01:00 committed by Peter Surda
parent 8ff5bf0464
commit 751f9108d8
4 changed files with 147 additions and 5 deletions

View File

@ -34,6 +34,7 @@ from addaddressdialog import *
from newsubscriptiondialog import * from newsubscriptiondialog import *
from regenerateaddresses import * from regenerateaddresses import *
from newchandialog import * from newchandialog import *
from safehtmlparser import *
from specialaddressbehavior import * from specialaddressbehavior import *
from emailgateway import * from emailgateway import *
from settings import * from settings import *
@ -4025,10 +4026,9 @@ class MyForm(settingsmixin.SMainWindow):
data = self.getCurrentMessageId() data = self.getCurrentMessageId()
if data != False: if data != False:
message = "Error occurred: could not load message from disk." message = "Error occurred: could not load message from disk."
message = unicode(message, 'utf-8)')
messageTextedit.setCurrentFont(QtGui.QFont()) messageTextedit.setCurrentFont(QtGui.QFont())
messageTextedit.setTextColor(QtGui.QColor()) messageTextedit.setTextColor(QtGui.QColor())
messageTextedit.setPlainText(message) messageTextedit.setContent(message)
def tableWidgetAddressBookItemChanged(self): def tableWidgetAddressBookItemChanged(self):
currentRow = self.ui.tableWidgetAddressBook.currentRow() currentRow = self.ui.tableWidgetAddressBook.currentRow()

View File

@ -8,6 +8,7 @@
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from messageview import MessageView
import settingsmixin import settingsmixin
try: try:
@ -123,7 +124,7 @@ class Ui_MainWindow(object):
self.tableWidgetInbox.verticalHeader().setVisible(False) self.tableWidgetInbox.verticalHeader().setVisible(False)
self.tableWidgetInbox.verticalHeader().setDefaultSectionSize(26) self.tableWidgetInbox.verticalHeader().setDefaultSectionSize(26)
self.verticalSplitter_7.addWidget(self.tableWidgetInbox) self.verticalSplitter_7.addWidget(self.tableWidgetInbox)
self.textEditInboxMessage = QtGui.QTextEdit(self.inbox) self.textEditInboxMessage = MessageView(self.inbox)
self.textEditInboxMessage.setBaseSize(QtCore.QSize(0, 500)) self.textEditInboxMessage.setBaseSize(QtCore.QSize(0, 500))
self.textEditInboxMessage.setReadOnly(True) self.textEditInboxMessage.setReadOnly(True)
self.textEditInboxMessage.setObjectName(_fromUtf8("textEditInboxMessage")) self.textEditInboxMessage.setObjectName(_fromUtf8("textEditInboxMessage"))
@ -428,7 +429,7 @@ class Ui_MainWindow(object):
self.tableWidgetInboxSubscriptions.verticalHeader().setVisible(False) self.tableWidgetInboxSubscriptions.verticalHeader().setVisible(False)
self.tableWidgetInboxSubscriptions.verticalHeader().setDefaultSectionSize(26) self.tableWidgetInboxSubscriptions.verticalHeader().setDefaultSectionSize(26)
self.verticalSplitter_4.addWidget(self.tableWidgetInboxSubscriptions) self.verticalSplitter_4.addWidget(self.tableWidgetInboxSubscriptions)
self.textEditInboxMessageSubscriptions = QtGui.QTextEdit(self.subscriptions) self.textEditInboxMessageSubscriptions = MessageView(self.subscriptions)
self.textEditInboxMessageSubscriptions.setBaseSize(QtCore.QSize(0, 500)) self.textEditInboxMessageSubscriptions.setBaseSize(QtCore.QSize(0, 500))
self.textEditInboxMessageSubscriptions.setReadOnly(True) self.textEditInboxMessageSubscriptions.setReadOnly(True)
self.textEditInboxMessageSubscriptions.setObjectName(_fromUtf8("textEditInboxMessageSubscriptions")) self.textEditInboxMessageSubscriptions.setObjectName(_fromUtf8("textEditInboxMessageSubscriptions"))
@ -527,7 +528,7 @@ class Ui_MainWindow(object):
self.tableWidgetInboxChans.verticalHeader().setVisible(False) self.tableWidgetInboxChans.verticalHeader().setVisible(False)
self.tableWidgetInboxChans.verticalHeader().setDefaultSectionSize(26) self.tableWidgetInboxChans.verticalHeader().setDefaultSectionSize(26)
self.verticalSplitter_8.addWidget(self.tableWidgetInboxChans) self.verticalSplitter_8.addWidget(self.tableWidgetInboxChans)
self.textEditInboxMessageChans = QtGui.QTextEdit(self.chans) self.textEditInboxMessageChans = MessageView(self.chans)
self.textEditInboxMessageChans.setBaseSize(QtCore.QSize(0, 500)) self.textEditInboxMessageChans.setBaseSize(QtCore.QSize(0, 500))
self.textEditInboxMessageChans.setReadOnly(True) self.textEditInboxMessageChans.setReadOnly(True)
self.textEditInboxMessageChans.setObjectName(_fromUtf8("textEditInboxMessageChans")) self.textEditInboxMessageChans.setObjectName(_fromUtf8("textEditInboxMessageChans"))

View File

@ -0,0 +1,44 @@
from PyQt4 import QtCore, QtGui
from safehtmlparser import *
class MessageView(QtGui.QTextEdit):
MODE_PLAIN = 0
MODE_HTML = 1
TEXT_PLAIN = "HTML detected, click here to display"
TEXT_HTML = "Click here to disable HTML"
def __init__(self, parent = 0):
super(MessageView, self).__init__(parent)
self.mode = MessageView.MODE_PLAIN
self.html = None
def mousePressEvent(self, event):
#text = textCursor.block().text()
if event.button() == QtCore.Qt.LeftButton and self.html.has_html and self.cursorForPosition(event.pos()).block().blockNumber() == 0:
if self.mode == MessageView.MODE_PLAIN:
self.showHTML()
else:
self.showPlain()
else:
super(MessageView, self).mousePressEvent(event)
def showPlain(self):
self.mode = MessageView.MODE_PLAIN
out = self.html.raw
if self.html.has_html:
out = "<div align=\"center\" style=\"text-decoration: underline;\"><b>" + QtGui.QApplication.translate("MessageView", MessageView.TEXT_PLAIN) + "</b></div><br/>" + out
self.setHtml(QtCore.QString(out))
def showHTML(self):
self.mode = MessageView.MODE_HTML
out = self.html.sanitised
out = "<div align=\"center\" style=\"text-decoration: underline;\"><b>" + QtGui.QApplication.translate("MessageView", MessageView.TEXT_HTML) + "</b></div><br/>" + out
self.setHtml(QtCore.QString(out))
def setContent(self, data):
self.html = SafeHTMLParser()
self.html.allow_picture = True
self.html.feed(data)
self.html.close()
self.showPlain()

View File

@ -0,0 +1,97 @@
from HTMLParser import HTMLParser
import inspect
from urllib import quote, quote_plus
class SafeHTMLParser(HTMLParser):
# from html5lib.sanitiser
acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area',
'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button',
'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup',
'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn',
'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset',
'figcaption', 'figure', 'footer', 'font', 'header', 'h1',
'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins',
'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter',
'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option',
'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select',
'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong',
'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot',
'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video']
def __init__(self, *args, **kwargs):
HTMLParser.__init__(self, *args, **kwargs)
self.elements = set()
self.sanitised = u""
self.raw = u""
self.has_html = False
def reset_safe(self):
self.raw = u""
self.sanitised = u""
def add_if_acceptable(self, tag, attrs = None):
if not tag in self.acceptable_elements:
return
self.sanitised += "<"
if inspect.stack()[1][3] == "handle_endtag":
self.sanitised += "/"
self.sanitised += tag
if attrs is not None:
for attr in attrs:
if tag == "img" and attr[0] == "src" and not self.allow_picture:
attr[1] = ""
self.sanitised += " " + quote_plus(attr[0]) + "=\"" + attr[1] + "\""
if inspect.stack()[1][3] == "handle_startendtag":
self.sanitised += "/"
self.sanitised += ">"
def add_raw(self, tag, attrs = None):
self.raw += "&lt;"
if inspect.stack()[1][3] == "handle_endtag":
self.raw += "/"
self.raw += tag
if attrs is not None:
for attr in attrs:
if tag == "img" and attr[0] == "src" and not self.allow_picture:
attr[1] = ""
self.raw += " " + attr[0] + "=&quot;" + attr[1] + "&quot;"
if inspect.stack()[1][3] == "handle_startendtag":
self.raw += "/"
self.raw += "&gt;"
def handle_starttag(self, tag, attrs):
if tag in self.acceptable_elements:
self.has_html = True
self.add_if_acceptable(tag, attrs)
self.add_raw(tag, attrs)
def handle_endtag(self, tag):
self.add_if_acceptable(tag)
self.add_raw(tag)
def handle_startendtag(self, tag, attrs):
if tag in self.acceptable_elements:
self.has_html = True
self.add_if_acceptable(tag, attrs)
self.add_raw(tag, attrs)
def handle_data(self, data):
self.sanitised += unicode(data, 'utf-8', 'replace')
tmp = data.replace("\n", "<br/>")
self.raw += unicode(tmp, 'utf-8', 'replace')
def handle_charref(self, name):
self.sanitised += "&#" + name + ";"
self.raw += quote("&#" + name + ";")
def handle_entityref(self, name):
self.sanitised += "&" + name + ";"
self.raw += quote("&" + name + ";")
def is_html(self, text = None, allow_picture = False):
if text:
self.reset()
self.reset_safe()
self.feed(text)
self.close()
self.allow_picture = allow_picture
return self.has_html