mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
418cb87f13
commit
1767c90680
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user