diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py
index 1e13ce63a4..d400b25dea 100644
--- a/src/calibre/ebooks/metadata/sources/identify.py
+++ b/src/calibre/ebooks/metadata/sources/identify.py
@@ -501,7 +501,8 @@ def identify(log, abort, # {{{
log('We have %d merged results, merging took: %.2f seconds' %
(len(results), time.time() - start_time))
tm_rules = msprefs['tag_map_rules']
- if tm_rules:
+ pm_rules = msprefs['publisher_map_rules']
+ if tm_rules or pm_rules:
from calibre.ebooks.metadata.tag_mapper import map_tags
am_rules = msprefs['author_map_rules']
if am_rules:
@@ -531,6 +532,9 @@ def identify(log, abort, # {{{
r.tags = r.tags[:max_tags]
if getattr(r.pubdate, 'year', 2000) <= UNDEFINED_DATE.year:
r.pubdate = None
+ if pm_rules and r.publisher:
+ pubs = map_tags([r.publisher], pm_rules)
+ r.publisher = pubs[0] if pubs else ''
if msprefs['swap_author_names']:
for r in results:
diff --git a/src/calibre/ebooks/metadata/sources/prefs.py b/src/calibre/ebooks/metadata/sources/prefs.py
index 5725a87d8c..956494ef34 100644
--- a/src/calibre/ebooks/metadata/sources/prefs.py
+++ b/src/calibre/ebooks/metadata/sources/prefs.py
@@ -18,8 +18,9 @@ msprefs.defaults['swap_author_names'] = False
msprefs.defaults['fewer_tags'] = True
msprefs.defaults['find_first_edition_date'] = False
msprefs.defaults['append_comments'] = False
-msprefs.defaults['tag_map_rules'] = []
-msprefs.defaults['author_map_rules'] = []
+msprefs.defaults['tag_map_rules'] = ()
+msprefs.defaults['author_map_rules'] = ()
+msprefs.defaults['publisher_map_rules'] = ()
msprefs.defaults['id_link_rules'] = {}
msprefs.defaults['keep_dups'] = False
diff --git a/src/calibre/gui2/author_mapper.py b/src/calibre/gui2/author_mapper.py
index 5d26d08a9c..3fe9395361 100644
--- a/src/calibre/gui2/author_mapper.py
+++ b/src/calibre/gui2/author_mapper.py
@@ -34,7 +34,7 @@ class RuleEdit(RuleEditBase):
('not_matches', _('does not match regex pattern')),
))
- MSG = _('Create the rule below, the rule can be used to add or ignore files')
+ MSG = _('Create the rule below, the rule can be used to add or ignore authors')
SUBJECT = _('the author, if the author name')
VALUE_ERROR = _('You must provide a value for the author name to match')
REPLACE_TEXT = _('with the name:')
diff --git a/src/calibre/gui2/preferences/metadata_sources.py b/src/calibre/gui2/preferences/metadata_sources.py
index 0ded5efd2d..cb9bfe4e97 100644
--- a/src/calibre/gui2/preferences/metadata_sources.py
+++ b/src/calibre/gui2/preferences/metadata_sources.py
@@ -332,10 +332,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.select_default_button.clicked.connect(self.fields_model.select_user_defaults)
self.select_default_button.clicked.connect(self.changed_signal)
self.set_as_default_button.clicked.connect(self.fields_model.commit_user_defaults)
- self.tag_map_rules = self.author_map_rules = None
+ self.tag_map_rules = self.author_map_rules = self.publisher_map_rules = None
m = QMenu(self)
m.addAction(_('Tags')).triggered.connect(self.change_tag_map_rules)
m.addAction(_('Authors')).triggered.connect(self.change_author_map_rules)
+ m.addAction(_('Publisher')).triggered.connect(self.change_publisher_map_rules)
self.map_rules_button.setMenu(m)
l = self.page.layout()
l.setStretch(0, 1)
@@ -376,16 +377,25 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
from calibre.gui2.tag_mapper import RulesDialog
d = RulesDialog(self)
if msprefs.get('tag_map_rules'):
- d.rules = msprefs['tag_map_rules']
+ d.rules = list(msprefs['tag_map_rules'])
if d.exec() == QDialog.DialogCode.Accepted:
self.tag_map_rules = d.rules
self.changed_signal.emit()
+ def change_publisher_map_rules(self):
+ from calibre.gui2.publisher_mapper import RulesDialog
+ d = RulesDialog(self)
+ if msprefs.get('publisher_map_rules'):
+ d.rules = list(msprefs['publisher_map_rules'])
+ if d.exec() == QDialog.DialogCode.Accepted:
+ self.publisher_map_rules = d.rules
+ self.changed_signal.emit()
+
def change_author_map_rules(self):
from calibre.gui2.author_mapper import RulesDialog
d = RulesDialog(self)
if msprefs.get('author_map_rules'):
- d.rules = msprefs['author_map_rules']
+ d.rules = list(msprefs['author_map_rules'])
if d.exec() == QDialog.DialogCode.Accepted:
self.author_map_rules = d.rules
self.changed_signal.emit()
@@ -395,7 +405,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.sources_model.initialize()
self.sources_view.resizeColumnsToContents()
self.fields_model.initialize()
- self.tag_map_rules = self.author_map_rules = None
+ self.tag_map_rules = self.author_map_rules = self.publisher_map_rules = None
def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self)
@@ -410,6 +420,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
msprefs['tag_map_rules'] = self.tag_map_rules or []
if self.author_map_rules is not None:
msprefs['author_map_rules'] = self.author_map_rules or []
+ if self.publisher_map_rules is not None:
+ msprefs['publisher_map_rules'] = self.publisher_map_rules or []
return ConfigWidgetBase.commit(self)
diff --git a/src/calibre/gui2/preferences/metadata_sources.ui b/src/calibre/gui2/preferences/metadata_sources.ui
index dfc98a8844..b978aacdb2 100644
--- a/src/calibre/gui2/preferences/metadata_sources.ui
+++ b/src/calibre/gui2/preferences/metadata_sources.ui
@@ -234,7 +234,7 @@
- Create &rules to transform tags/authors
+ Create &rules to transform tags/authors/publishers
QToolButton::InstantPopup
diff --git a/src/calibre/gui2/publisher_mapper.py b/src/calibre/gui2/publisher_mapper.py
new file mode 100644
index 0000000000..d678dded0f
--- /dev/null
+++ b/src/calibre/gui2/publisher_mapper.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# License: GPL v3 Copyright: 2018, Kovid Goyal
+
+
+from collections import OrderedDict
+
+from calibre.ebooks.metadata.tag_mapper import map_tags
+from calibre.gui2 import Application, elided_text
+from calibre.gui2.tag_mapper import (
+ RuleEdit as RuleEditBase, RuleEditDialog as RuleEditDialogBase,
+ RuleItem as RuleItemBase, Rules as RulesBase, RulesDialog as RulesDialogBase,
+ Tester as TesterBase,
+)
+from calibre.utils.config import JSONConfig
+
+publisher_maps = JSONConfig('publisher-mapping-rules')
+
+
+class RuleEdit(RuleEditBase):
+
+ ACTION_MAP = OrderedDict((
+ ('replace', _('Change')),
+ ('capitalize', _('Capitalize')),
+ ('titlecase', _('Title-case')),
+ ('lower', _('Lower-case')),
+ ('upper', _('Upper-case')),
+ ))
+
+ MATCH_TYPE_MAP = OrderedDict((
+ ('one_of', _('is one of')),
+ ('not_one_of', _('is not one of')),
+ ('has', _('contains')),
+ ('matches', _('matches regex pattern')),
+ ('not_matches', _('does not match regex pattern')),
+ ))
+
+ MSG = _('Create the rule below, the rule can be used to modify publishers')
+ SUBJECT = _('the publisher, if the publisher name')
+ VALUE_ERROR = _('You must provide a value for the publisher name to match')
+ REPLACE_TEXT = _('with the name:')
+ SINGLE_EDIT_FIELD_NAME = 'publisher'
+
+ @property
+ def can_use_tag_editor(self):
+ return False
+
+ def update_state(self):
+ a = self.action.currentData()
+ replace = a == 'replace'
+ self.la3.setVisible(replace), self.replace.setVisible(replace)
+ m = self.match_type.currentData()
+ is_match = 'matches' in m
+ self.regex_help.setVisible(is_match)
+
+ @property
+ def rule(self):
+ return {
+ 'action': self.action.currentData(),
+ 'match_type': self.match_type.currentData(),
+ 'query': self.query.text().strip(),
+ 'replace': self.replace.text().strip(),
+ }
+
+ @rule.setter
+ def rule(self, rule):
+ def sc(name):
+ c = getattr(self, name)
+ idx = c.findData(str(rule.get(name, '')))
+ if idx < 0:
+ idx = 0
+ c.setCurrentIndex(idx)
+ sc('match_type'), sc('action')
+ self.query.setText(str(rule.get('query', '')).strip())
+ self.replace.setText(str(rule.get('replace', '')).strip())
+
+
+class RuleEditDialog(RuleEditDialogBase):
+
+ PREFS_NAME = 'edit-publisher-mapping-rule'
+ RuleEditClass = RuleEdit
+
+
+class RuleItem(RuleItemBase):
+
+ @staticmethod
+ def text_from_rule(rule, parent):
+ query = elided_text(rule['query'], font=parent.font(), width=200, pos='right')
+ text = _(
+ '{action} the publisher name, if it {match_type}: {query}').format(
+ action=RuleEdit.ACTION_MAP[rule['action']], match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query)
+ if rule['action'] == 'replace':
+ text += '
' + _('to the name') + ' %s' % rule['replace']
+ return '' + text + '
'
+
+
+class Rules(RulesBase):
+
+ RuleItemClass = RuleItem
+ RuleEditDialogClass = RuleEditDialog
+ MSG = _('You can specify rules to manipulate publisher names here.'
+ ' Click the "Add Rule" button'
+ ' below to get started. The rules will be processed in order for every publisher.')
+
+
+class Tester(TesterBase):
+
+ DIALOG_TITLE = _('Test publisher mapping rules')
+ PREFS_NAME = 'test-publisher-mapping-rules'
+ LABEL = _('Enter an publisher name to test:')
+ PLACEHOLDER = _('Enter publisher and click the "Test" button')
+ EMPTY_RESULT = '
'
+
+ def do_test(self):
+ publisher = self.value.strip()
+ ans = map_tags([publisher], self.rules)
+ self.result.setText((ans or ('',))[0])
+
+
+class RulesDialog(RulesDialogBase):
+
+ DIALOG_TITLE = _('Edit publisher mapping rules')
+ PREFS_NAME = 'edit-publisher-mapping-rules'
+ RulesClass = Rules
+ TesterClass = Tester
+ PREFS_OBJECT = publisher_maps
+
+
+if __name__ == '__main__':
+ app = Application([])
+ d = RulesDialog()
+ d.rules = [
+ {'action':'replace', 'query':'alice Bob', 'match_type':'one_of', 'replace':'Alice Bob'},
+ ]
+ d.exec()
+ from pprint import pprint
+ pprint(d.rules)
+ del d, app