mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Start work on CSS rule transformer
This commit is contained in:
parent
b1664c7417
commit
5ca8ccf78a
16
src/calibre/ebooks/css_transform_rules.py
Normal file
16
src/calibre/ebooks/css_transform_rules.py
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
import regex
|
||||
|
||||
REGEX_FLAGS = regex.VERSION1 | regex.UNICODE
|
||||
|
||||
def compile_pat(pat):
|
||||
return regex.compile(pat, flags=REGEX_FLAGS)
|
||||
|
||||
def parse_length(raw):
|
||||
raise NotImplementedError('TODO: implement this')
|
206
src/calibre/gui2/css_transform_rules.py
Normal file
206
src/calibre/gui2/css_transform_rules.py
Normal file
@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QLineEdit, QListWidgetItem
|
||||
)
|
||||
|
||||
from calibre.ebooks.css_transform_rules import compile_pat, parse_length
|
||||
from calibre.gui2 import error_dialog, elided_text
|
||||
from calibre.gui2.tag_mapper import RuleEditDialog as RuleEditDialogBase, Rules as RulesBase
|
||||
|
||||
class RuleEdit(QWidget): # {{{
|
||||
|
||||
ACTION_MAP = OrderedDict((
|
||||
('remove', _('Remove the property')),
|
||||
('append', _('Add extra properties')),
|
||||
('change', _('Change the value to')),
|
||||
('*', _('Multiply the value by')),
|
||||
('/', _('Divide the value by')),
|
||||
('+', _('Add to the value')),
|
||||
('-', _('Subtract from the value')),
|
||||
))
|
||||
|
||||
MATCH_TYPE_MAP = OrderedDict((
|
||||
('==', _('is')),
|
||||
('<', _('is less than')),
|
||||
('>', _('is greater than')),
|
||||
('<=', _('is less than or equal to')),
|
||||
('>=', _('is greater than or equal to')),
|
||||
('matches', _('matches pattern')),
|
||||
('not_matches', _('does not match pattern'))
|
||||
))
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.l = l = QVBoxLayout(self)
|
||||
self.h = h = QHBoxLayout()
|
||||
|
||||
self.la = la = QLabel(self.MSG)
|
||||
la.setWordWrap(True)
|
||||
l.addWidget(la)
|
||||
l.addLayout(h)
|
||||
english_sentence = '{preamble} {property} {match_type} {query}'
|
||||
sentence = _('{preamble} {property} {match_type} {query}')
|
||||
if set(sentence.split()) != set(english_sentence.split()):
|
||||
sentence = english_sentence
|
||||
parts = sentence.split()
|
||||
for clause in parts:
|
||||
if clause == '{preamble}':
|
||||
self.preamble = w = QLabel(_('If the &property:'))
|
||||
elif clause == '{property}':
|
||||
self.property = w = QLineEdit(self)
|
||||
w.setToolTip(_('The name of a CSS property, for example: font-size\n'))
|
||||
elif clause == '{match_type}':
|
||||
self.match_type = w = QComboBox(self)
|
||||
for action, text in self.MATCH_TYPE_MAP.iteritems():
|
||||
w.addItem(text, action)
|
||||
elif clause == '{query}':
|
||||
self.query = w = QLineEdit(self)
|
||||
h.addWidget(w)
|
||||
if clause is not parts[-1]:
|
||||
h.addWidget(QLabel('\xa0'))
|
||||
|
||||
self.h2 = h = QHBoxLayout()
|
||||
l.addLayout(h)
|
||||
english_sentence = '{action} {action_data}'
|
||||
sentence = _('{action} {action_data}')
|
||||
if set(sentence.split()) != set(english_sentence.split()):
|
||||
sentence = english_sentence
|
||||
parts = sentence.split()
|
||||
for clause in parts:
|
||||
if clause == '{action}':
|
||||
self.action = w = QComboBox(self)
|
||||
for action, text in self.ACTION_MAP.iteritems():
|
||||
w.addItem(text, action)
|
||||
elif clause == '{action_data}':
|
||||
self.action_data = w = QLineEdit(self)
|
||||
h.addWidget(w)
|
||||
if clause is not parts[-1]:
|
||||
h.addWidget(QLabel('\xa0'))
|
||||
|
||||
self.update_state()
|
||||
|
||||
def sizeHint(self):
|
||||
a = QWidget.sizeHint(self)
|
||||
a.setHeight(a.height() + 75)
|
||||
a.setWidth(a.width() + 100)
|
||||
return a
|
||||
|
||||
def update_state(self):
|
||||
r = self.rule
|
||||
self.action_data.setVisible(r['action'] != 'remove')
|
||||
tt = _('The CSS property value')
|
||||
mt = r['match_type']
|
||||
if 'matches' in mt:
|
||||
tt = _('A regular expression')
|
||||
elif mt in '< > <= >='.split():
|
||||
tt = _('Either a CSS length, such as 10pt or a unit less number. If a unitless'
|
||||
' number is used it will compared with the CSS value using whatever unit'
|
||||
' the value has.')
|
||||
self.query.setToolTip(tt)
|
||||
tt = ''
|
||||
ac = r['action']
|
||||
if ac == 'append':
|
||||
tt = _('CSS properties for to add to the rule that contains the matching style. You'
|
||||
' can specify more than one property, separated by semi-colons, for example:'
|
||||
' color:red; font-weight: bold')
|
||||
elif ac in '+=*/':
|
||||
tt = _('A number')
|
||||
self.action_data.setToolTip(tt)
|
||||
|
||||
@property
|
||||
def rule(self):
|
||||
return {
|
||||
'property':self.property.text().strip(),
|
||||
'match_type': self.match_type.currentData(),
|
||||
'query': self.query.text().strip(),
|
||||
'action': self.action.currentData(),
|
||||
'action_data': self.action_data.text().strip(),
|
||||
}
|
||||
|
||||
@rule.setter
|
||||
def rule(self, rule):
|
||||
def sc(name):
|
||||
c = getattr(self, name)
|
||||
idx = c.findData(unicode(rule.get(name, '')))
|
||||
if idx < 0:
|
||||
idx = 0
|
||||
c.setCurrentIndex(idx)
|
||||
sc('action'), sc('match_type')
|
||||
self.query.setText(unicode(rule.get('query', '')).strip())
|
||||
self.action_data.setText(unicode(rule.get('action_data', '')).strip())
|
||||
|
||||
def validate(self):
|
||||
rule = self.rule
|
||||
if not rule['query']:
|
||||
error_dialog(self, _('Query required'), _(
|
||||
'You must specify a value for the CSS property to match'), show=True)
|
||||
return False
|
||||
mt = rule['match_type']
|
||||
if 'matches' in mt:
|
||||
try:
|
||||
compile_pat(rule['query'])
|
||||
except Exception:
|
||||
error_dialog(self, _('Query invalid'), _(
|
||||
'%s is not a valid regular expression') % rule['query'], show=True)
|
||||
return False
|
||||
elif mt in '< > <= >='.split():
|
||||
try:
|
||||
parse_length(rule['query'])
|
||||
except Exception:
|
||||
error_dialog(self, _('Query invalid'), _(
|
||||
'%s is not a valid length or number') % rule['query'], show=True)
|
||||
return False
|
||||
ac, ad = rule['action'], rule['action_data']
|
||||
if not ad and ac != 'remove':
|
||||
msg = _('You must specify a number')
|
||||
if ac == 'append':
|
||||
msg = _('You must specify at least one CSS property to add')
|
||||
elif ac == 'change':
|
||||
msg = _('You must specify a value to change the property to')
|
||||
error_dialog(self, _('No data'), msg, show=True)
|
||||
return False
|
||||
if ac in '+-*/':
|
||||
try:
|
||||
float(ad)
|
||||
except Exception:
|
||||
error_dialog(self, _('Invalid number'), _('%s is not a number') % ad, show=True)
|
||||
return False
|
||||
return True
|
||||
# }}}
|
||||
|
||||
class RuleEditDialog(RuleEditDialogBase): # {{{
|
||||
|
||||
PREFS_NAME = 'edit-css-transform-rule'
|
||||
DIALOG_TITLE = _('Edit rule')
|
||||
RuleEditClass = RuleEdit
|
||||
# }}}
|
||||
|
||||
class RuleItem(QListWidgetItem): # {{{
|
||||
|
||||
@staticmethod
|
||||
def text_from_rule(rule, parent):
|
||||
query = elided_text(rule['query'], font=parent.font(), width=200, pos='right')
|
||||
text = _(
|
||||
'If the property <i>{property}</i> <b>{match_type}</b> <b>{query}</b><br>{action}').format(
|
||||
property=rule['property'], action=RuleEdit.ACTION_MAP[rule['action']],
|
||||
match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query)
|
||||
if rule['action_data']:
|
||||
ad = elided_text(rule['action_data'], font=parent.font(), width=200, pos='right')
|
||||
text += ' <code>%s</code>' % ad
|
||||
return text
|
||||
# }}}
|
||||
|
||||
class Rules(RulesBase):
|
||||
|
||||
RuleItemClass = RuleItem
|
||||
RuleEditDialogClass = RuleEditDialog
|
||||
|
||||
MSG = _('You can specify rules to transform styles here. Click the "Add Rule" button'
|
||||
' below to get started.')
|
Loading…
x
Reference in New Issue
Block a user