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
|
created inline Table of Contents to be replaced. The tool can be accessed via
|
||||||
:guilabel:`Tools->Table of Contents->Insert inline Table of Contents`.
|
: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:
|
||||||
|
|
||||||
Checkpoints
|
Checkpoints
|
||||||
|
@ -14,6 +14,7 @@ from cssselect.xpath import XPathExpr, is_safe_name
|
|||||||
|
|
||||||
from calibre import force_unicode
|
from calibre import force_unicode
|
||||||
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS, XPNSMAP, XHTML_NS
|
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.stylizer import MIN_SPACE_RE, is_non_whitespace, xpath_lower_case, fix_namespace
|
||||||
from calibre.ebooks.oeb.polish.pretty import pretty_script_or_style
|
from calibre.ebooks.oeb.polish.pretty import pretty_script_or_style
|
||||||
|
|
||||||
@ -167,3 +168,80 @@ def remove_unused_css(container, report):
|
|||||||
else:
|
else:
|
||||||
report(_('No unused CSS style rules found'))
|
report(_('No unused CSS style rules found'))
|
||||||
return num_of_removed_rules > 0
|
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.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.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.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.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.replace import rename_files, replace_file, get_recommended_folders, rationalize_folders
|
||||||
from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit
|
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.spell import find_next as find_next_word
|
||||||
from calibre.gui2.tweak_book.widgets import (
|
from calibre.gui2.tweak_book.widgets import (
|
||||||
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
|
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
|
||||||
InsertSemantics, BusyCursor, InsertTag)
|
InsertSemantics, BusyCursor, InsertTag, FilterCSS)
|
||||||
|
|
||||||
_diff_dialogs = []
|
_diff_dialogs = []
|
||||||
|
|
||||||
@ -674,6 +675,21 @@ class Boss(QObject):
|
|||||||
d.apply_changes(current_container())
|
d.apply_changes(current_container())
|
||||||
self.apply_container_update_to_gui()
|
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):
|
def show_find(self):
|
||||||
self.gui.central.show_find()
|
self.gui.central.show_find()
|
||||||
ed = self.gui.central.current_editor
|
ed = self.gui.central.current_editor
|
||||||
|
@ -349,6 +349,8 @@ class Main(MainWindow):
|
|||||||
_('Arrange into folders'))
|
_('Arrange into folders'))
|
||||||
self.action_set_semantics = reg('tags.png', _('Set &Semantics'), self.boss.set_semantics, 'set-semantics', (),
|
self.action_set_semantics = reg('tags.png', _('Set &Semantics'), self.boss.set_semantics, '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
|
# Polish actions
|
||||||
group = _('Polish Book')
|
group = _('Polish Book')
|
||||||
@ -482,6 +484,7 @@ class Main(MainWindow):
|
|||||||
e.addAction(self.action_pretty_all)
|
e.addAction(self.action_pretty_all)
|
||||||
e.addAction(self.action_rationalize_folders)
|
e.addAction(self.action_rationalize_folders)
|
||||||
e.addAction(self.action_set_semantics)
|
e.addAction(self.action_set_semantics)
|
||||||
|
e.addAction(self.action_filter_css)
|
||||||
e.addAction(self.action_spell_check_book)
|
e.addAction(self.action_spell_check_book)
|
||||||
e.addAction(self.action_check_book)
|
e.addAction(self.action_check_book)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from PyQt4.Qt import (
|
|||||||
QFormLayout, QHBoxLayout, QToolButton, QIcon, QApplication, Qt, QWidget,
|
QFormLayout, QHBoxLayout, QToolButton, QIcon, QApplication, Qt, QWidget,
|
||||||
QPoint, QSizePolicy, QPainter, QStaticText, pyqtSignal, QTextOption,
|
QPoint, QSizePolicy, QPainter, QStaticText, pyqtSignal, QTextOption,
|
||||||
QAbstractListModel, QModelIndex, QVariant, QStyledItemDelegate, QStyle,
|
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 import prepare_string_for_xml
|
||||||
from calibre.ebooks.oeb.polish.utils import lead_text
|
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')
|
frag = item.get('id', None) or item.get('name')
|
||||||
text = lead_text(item, num_words=4)
|
text = lead_text(item, num_words=4)
|
||||||
ac.append((text, frag))
|
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.anchor_names.model().set_names(self.anchor_cache[name])
|
||||||
self.update_target()
|
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__':
|
if __name__ == '__main__':
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
InsertLink.test()
|
FilterCSS.test()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user