Edit Book: A new tool to filter style information (Tools->Filter style

information). Useful to easily remove some CSS properties from the
entire book.
This commit is contained in:
Kovid Goyal 2014-05-07 14:34:11 +05:30
parent 418cb87f13
commit 1767c90680
5 changed files with 170 additions and 4 deletions

View File

@ -400,6 +400,17 @@ If you use this tool multiple times, each invocation will cause the previously
created inline Table of Contents to be replaced. The tool can be accessed via
:guilabel:`Tools->Table of Contents->Insert inline Table of Contents`.
Filter style information
^^^^^^^^^^^^^^^^^^^^^^^^^^^
This tool can be used to easily remove specified CSS style properties from the
entire book. You can tell it what properties you want removed, for example,
``color, background-color, line-height`` and it will remove them from
everywhere they occur --- stylesheets, ``<style>`` tags and inline ``style``
attributes. After removing the style information, a summary of all the changes
made is displayed so you can see exactly what was changed. The tool can be
accessed via :guilabel:`Tools->Filter style information`.
.. _checkpoints:
Checkpoints

View File

@ -14,6 +14,7 @@ from cssselect.xpath import XPathExpr, is_safe_name
from calibre import force_unicode
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS, XPNSMAP, XHTML_NS
from calibre.ebooks.oeb.normalize_css import normalize_filter_css, normalizers
from calibre.ebooks.oeb.stylizer import MIN_SPACE_RE, is_non_whitespace, xpath_lower_case, fix_namespace
from calibre.ebooks.oeb.polish.pretty import pretty_script_or_style
@ -167,3 +168,80 @@ def remove_unused_css(container, report):
else:
report(_('No unused CSS style rules found'))
return num_of_removed_rules > 0
def filter_declaration(style, properties):
changed = False
for prop in properties:
if style.removeProperty(prop) != '':
changed = True
all_props = set(style.keys())
for prop in style.getProperties():
n = normalizers.get(prop.name, None)
if n is not None:
normalized = n(prop.name, prop.propertyValue)
removed = properties.intersection(set(normalized))
if removed:
changed = True
style.removeProperty(prop.name)
for prop in set(normalized) - removed - all_props:
style.setProperty(prop, normalized[prop])
return changed
def filter_sheet(sheet, properties):
from cssutils.css import CSSRule
changed = False
remove = []
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
if filter_declaration(rule.style, properties):
changed = True
if rule.style.length == 0:
remove.append(rule)
for rule in remove:
sheet.cssRules.remove(rule)
return changed
def filter_css(container, properties, names=()):
if not names:
types = OEB_STYLES | OEB_DOCS
names = []
for name, mt in container.mime_map.iteritems():
if mt in types:
names.append(name)
properties = normalize_filter_css(properties)
doc_changed = False
for name in names:
mt = container.mime_map[name]
if mt in OEB_STYLES:
sheet = container.parsed(name)
filtered = filter_sheet(sheet, properties)
if filtered:
container.dirty(name)
doc_changed = True
elif mt in OEB_DOCS:
root = container.parsed(name)
changed = False
for style in root.xpath('//*[local-name()="style"]'):
if style.text and style.get('type', 'text/css') in {None, '', 'text/css'}:
sheet = container.parse_css(style.text)
if filter_sheet(sheet, properties):
changed = True
style.text = force_unicode(sheet.cssText, 'utf-8')
pretty_script_or_style(container, style)
for elem in root.xpath('//*[@style]'):
text = elem.get('style', None)
if text:
style = container.parse_css(text, is_declaration=True)
if filter_declaration(style, properties):
changed = True
if style.length == 0:
del elem.attrib['style']
else:
elem.set('style', force_unicode(style.getCssText(separator=' '), 'utf-8'))
if changed:
container.dirty(name)
doc_changed = True
return doc_changed

View File

@ -20,6 +20,7 @@ from calibre.ebooks.oeb.base import urlnormalize
from calibre.ebooks.oeb.polish.main import SUPPORTED, tweak_polish
from calibre.ebooks.oeb.polish.container import get_container as _gc, clone_container, guess_type, OEB_FONTS
from calibre.ebooks.oeb.polish.cover import mark_as_cover, mark_as_titlepage
from calibre.ebooks.oeb.polish.css import filter_css
from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all
from calibre.ebooks.oeb.polish.replace import rename_files, replace_file, get_recommended_folders, rationalize_folders
from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit
@ -41,7 +42,7 @@ from calibre.gui2.tweak_book.search import validate_search_request, run_search
from calibre.gui2.tweak_book.spell import find_next as find_next_word
from calibre.gui2.tweak_book.widgets import (
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
InsertSemantics, BusyCursor, InsertTag)
InsertSemantics, BusyCursor, InsertTag, FilterCSS)
_diff_dialogs = []
@ -674,6 +675,21 @@ class Boss(QObject):
d.apply_changes(current_container())
self.apply_container_update_to_gui()
def filter_css(self):
self.commit_all_editors_to_container()
d = FilterCSS(parent=self.gui)
if d.exec_() == d.Accepted and d.filtered_properties:
self.add_savepoint(_('Before: Filter style information'))
with BusyCursor():
changed = filter_css(current_container(), d.filtered_properties)
if changed:
self.apply_container_update_to_gui()
self.show_current_diff()
else:
self.rewind_savepoint()
return info_dialog(self.gui, _('No matches'), _(
'No matching style rules were found'), show=True)
def show_find(self):
self.gui.central.show_find()
ed = self.gui.central.current_editor

View File

@ -349,6 +349,8 @@ class Main(MainWindow):
_('Arrange into folders'))
self.action_set_semantics = reg('tags.png', _('Set &Semantics'), self.boss.set_semantics, 'set-semantics', (),
_('Set Semantics'))
self.action_filter_css = reg('filter.png', _('&Filter style information'), self.boss.filter_css, 'filter-css', (),
_('Filter style information'))
# Polish actions
group = _('Polish Book')
@ -482,6 +484,7 @@ class Main(MainWindow):
e.addAction(self.action_pretty_all)
e.addAction(self.action_rationalize_folders)
e.addAction(self.action_set_semantics)
e.addAction(self.action_filter_css)
e.addAction(self.action_spell_check_book)
e.addAction(self.action_check_book)

View File

@ -15,7 +15,7 @@ from PyQt4.Qt import (
QFormLayout, QHBoxLayout, QToolButton, QIcon, QApplication, Qt, QWidget,
QPoint, QSizePolicy, QPainter, QStaticText, pyqtSignal, QTextOption,
QAbstractListModel, QModelIndex, QVariant, QStyledItemDelegate, QStyle,
QListView, QTextDocument, QSize, QComboBox, QFrame, QCursor)
QListView, QTextDocument, QSize, QComboBox, QFrame, QCursor, QCheckBox)
from calibre import prepare_string_for_xml
from calibre.ebooks.oeb.polish.utils import lead_text
@ -689,7 +689,7 @@ class InsertLink(Dialog):
frag = item.get('id', None) or item.get('name')
text = lead_text(item, num_words=4)
ac.append((text, frag))
ac.sort(key=lambda (text, frag): primary_sort_key(text))
ac.sort(key=lambda text_frag: primary_sort_key(text_frag[0]))
self.anchor_names.model().set_names(self.anchor_cache[name])
self.update_target()
@ -924,6 +924,64 @@ class InsertSemantics(Dialog):
# }}}
class FilterCSS(Dialog): # {{{
def __init__(self, parent=None):
Dialog.__init__(self, _('Filter Style Information'), 'filter-css', parent=parent)
def setup_ui(self):
from calibre.gui2.convert.look_and_feel_ui import Ui_Form
f, w = Ui_Form(), QWidget()
f.setupUi(w)
self.l = l = QFormLayout(self)
self.setLayout(l)
l.addRow(QLabel(_('Select what style information you want completely removed:')))
self.h = h = QHBoxLayout()
for name, text in {
'fonts':_('&Fonts'), 'margins':_('&Margins'), 'padding':_('&Padding'), 'floats':_('Flo&ats'), 'colors':_('&Colors')}.iteritems():
c = QCheckBox(text)
setattr(self, 'opt_' + name, c)
h.addWidget(c)
c.setToolTip(getattr(f, 'filter_css_' + name).toolTip())
l.addRow(h)
self.others = o = QLineEdit(self)
l.addRow(_('&Other CSS properties:'), o)
o.setToolTip(f.filter_css_others.toolTip())
l.addRow(self.bb)
@property
def filtered_properties(self):
ans = set()
a = ans.add
if self.opt_fonts.isChecked():
a('font-family')
if self.opt_margins.isChecked():
a('margin')
if self.opt_padding.isChecked():
a('padding')
if self.opt_floats.isChecked():
a('float'), a('clear')
if self.opt_colors.isChecked():
a('color'), a('background-color')
for x in unicode(self.others.text()).split(','):
x = x.strip()
if x:
a(x)
return ans
@classmethod
def test(cls):
d = cls()
if d.exec_() == d.Accepted:
print (d.filtered_properties)
# }}}
if __name__ == '__main__':
app = QApplication([])
InsertLink.test()
FilterCSS.test()