diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index 559402ca1c..a80ddfb839 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -156,9 +156,7 @@ def add_pipeline_options(parser, plumber): 'SEARCH AND REPLACE' : ( _('Modify the document text and structure using user defined patterns.'), [ - 'sr1_search', 'sr1_replace', - 'sr2_search', 'sr2_replace', - 'sr3_search', 'sr3_replace', + 'search_replace', ] ), diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 8bb4fdd891..62abc30fbd 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -600,32 +600,9 @@ OptionRecommendation(name='renumber_headings', 'The tags are renumbered to prevent splitting in the middle ' 'of chapter headings.')), -OptionRecommendation(name='sr1_search', - recommended_value='', level=OptionRecommendation.LOW, - help=_('Search pattern (regular expression) to be replaced with ' - 'sr1-replace.')), - -OptionRecommendation(name='sr1_replace', - recommended_value='', level=OptionRecommendation.LOW, - help=_('Replacement to replace the text found with sr1-search.')), - -OptionRecommendation(name='sr2_search', - recommended_value='', level=OptionRecommendation.LOW, - help=_('Search pattern (regular expression) to be replaced with ' - 'sr2-replace.')), - -OptionRecommendation(name='sr2_replace', - recommended_value='', level=OptionRecommendation.LOW, - help=_('Replacement to replace the text found with sr2-search.')), - -OptionRecommendation(name='sr3_search', - recommended_value='', level=OptionRecommendation.LOW, - help=_('Search pattern (regular expression) to be replaced with ' - 'sr3-replace.')), - -OptionRecommendation(name='sr3_replace', - recommended_value='', level=OptionRecommendation.LOW, - help=_('Replacement to replace the text found with sr3-search.')), +OptionRecommendation(name='search_replace', + recommended_value='[]', level=OptionRecommendation.LOW, + help=_('Modify the document text and structure using user defined patterns.')), ] # }}} diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index 617de18555..f232cd3a92 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import functools, re +import functools, re, json from calibre import entity_to_unicode, as_unicode @@ -515,14 +515,14 @@ class HTMLPreProcessor(object): if not getattr(self.extra_opts, 'keep_ligatures', False): html = _ligpat.sub(lambda m:LIGATURES[m.group()], html) - for search, replace in [['sr3_search', 'sr3_replace'], ['sr2_search', 'sr2_replace'], ['sr1_search', 'sr1_replace']]: - search_pattern = getattr(self.extra_opts, search, '') + search_replace = json.loads(getattr(self.extra_opts, 'search_replace', '[]')) + for search_pattern, replace_txt in search_replace: if search_pattern: try: search_re = re.compile(search_pattern) - replace_txt = getattr(self.extra_opts, replace, '') if not replace_txt: replace_txt = '' + print 'Replacing pattern \'{0}\' with text \'{1}\''.format(search_pattern, replace_txt) rules.insert(0, (search_re, replace_txt)) except Exception as e: self.log.error('Failed to parse %r regexp because %s' % diff --git a/src/calibre/gui2/convert/__init__.py b/src/calibre/gui2/convert/__init__.py index 73b478ac47..fe5a6330a5 100644 --- a/src/calibre/gui2/convert/__init__.py +++ b/src/calibre/gui2/convert/__init__.py @@ -10,7 +10,7 @@ import textwrap, codecs, importlib from functools import partial from PyQt4.Qt import QWidget, QSpinBox, QDoubleSpinBox, QLineEdit, QTextEdit, \ - QCheckBox, QComboBox, Qt, QIcon, pyqtSignal, QLabel + QCheckBox, QComboBox, Qt, QIcon, pyqtSignal, QLabel, QTableWidget from calibre.customize.conversion import OptionRecommendation from calibre.ebooks.conversion.config import load_defaults, \ @@ -160,6 +160,15 @@ class Widget(QWidget): return g.xpath if g.xpath else None elif isinstance(g, RegexEdit): return g.regex if g.regex else None + elif isinstance(g, QTableWidget): + import json + ans = [] + for row in xrange(0, g.rowCount()): + colItems = [] + for col in xrange(0, g.columnCount()): + colItems.append(unicode(g.item(row, col).text())) + ans.append(colItems) + return json.dumps(ans) else: raise Exception('Can\'t get value from %s'%type(g)) @@ -187,6 +196,8 @@ class Widget(QWidget): elif isinstance(g, (XPathEdit, RegexEdit)): g.edit.editTextChanged.connect(f) g.edit.currentIndexChanged.connect(f) + elif isinstance(g, QTableWidget): + g.cellChanged.connect(f) else: raise Exception('Can\'t connect %s'%type(g)) @@ -220,6 +231,23 @@ class Widget(QWidget): g.setCheckState(Qt.Checked if bool(val) else Qt.Unchecked) elif isinstance(g, (XPathEdit, RegexEdit)): g.edit.setText(val if val else '') + elif isinstance(g, (QTableWidget)): + import json + try: + rowItems = json.loads(val) + if not isinstance(rowItems, list): + rowItems = [] + except: + rowItems = [] + + + g.setRowCount(len(rowItems)) + + for row, colItems in enumerate(rowItems): + for col, cellValue in enumerate(colItems): + newItem = g.itemPrototype().clone() + newItem.setText(cellValue) + g.setItem(row,col, newItem) else: raise Exception('Can\'t set value %s in %s'%(repr(val), unicode(g.objectName()))) diff --git a/src/calibre/gui2/convert/search_and_replace.py b/src/calibre/gui2/convert/search_and_replace.py index 4e0bfdf020..710c4719a8 100644 --- a/src/calibre/gui2/convert/search_and_replace.py +++ b/src/calibre/gui2/convert/search_and_replace.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' import re from PyQt4.QtCore import SIGNAL, Qt -from PyQt4.QtGui import QTableWidgetItem +from PyQt4.QtGui import QTableWidgetItem, QFileDialog from calibre.gui2.convert.search_and_replace_ui import Ui_Form from calibre.gui2.convert import Widget from calibre.gui2 import error_dialog @@ -22,66 +22,75 @@ class SearchAndReplaceWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, - ['sr1_search', 'sr1_replace', - 'sr2_search', 'sr2_replace', - 'sr3_search', 'sr3_replace'] + ['search_replace'] ) self.db, self.book_id = db, book_id - self.initialize_options(get_option, get_help, db, book_id) - self.opt_sr1_search.set_msg(_('&Search Regular Expression')) - self.opt_sr1_search.set_book_id(book_id) - self.opt_sr1_search.set_db(db) - self.opt_sr1_search.set_regex('test.*') - self.opt_sr2_search.set_msg(_('&Search Regular Expression')) - self.opt_sr2_search.set_book_id(book_id) - self.opt_sr2_search.set_db(db) - self.opt_sr3_search.set_msg(_('&Search Regular Expression')) - self.opt_sr3_search.set_book_id(book_id) - self.opt_sr3_search.set_db(db) - self.opt_sr1_search.doc_update.connect(self.update_doc) - self.opt_sr2_search.doc_update.connect(self.update_doc) - self.opt_sr3_search.doc_update.connect(self.update_doc) + self.sr_search.set_msg(_('&Search Regular Expression')) + self.sr_search.set_book_id(book_id) + self.sr_search.set_db(db) + + self.sr_search.doc_update.connect(self.update_doc) + + proto = QTableWidgetItem() + proto.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable + Qt.ItemIsEnabled)) + self.opt_search_replace.setItemPrototype(proto) + self.opt_search_replace.setColumnCount(2) + self.opt_search_replace.setHorizontalHeaderLabels(['Search Expression', 'Replacement']) - self.opt_sr.setColumnCount(2) - self.opt_sr.setHorizontalHeaderLabels(['Search Expression', 'Replacement']) self.connect(self.sr_add, SIGNAL('clicked()'), self.sr_add_clicked) self.connect(self.sr_change, SIGNAL('clicked()'), self.sr_change_clicked) self.connect(self.sr_remove, SIGNAL('clicked()'), self.sr_remove_clicked) - self.connect(self.opt_sr, SIGNAL('currentCellChanged(int, int, int, int)'), self.sr_currentCellChanged) + self.connect(self.sr_load, SIGNAL('clicked()'), self.sr_load_clicked) + self.connect(self.sr_save, SIGNAL('clicked()'), self.sr_save_clicked) + self.connect(self.opt_search_replace, SIGNAL('currentCellChanged(int, int, int, int)'), self.sr_currentCellChanged) + + self.initialize_options(get_option, get_help, db, book_id) def sr_add_clicked(self): - if self.opt_sr1_search.regex: - self.opt_sr.insertRow(0) - newItem = QTableWidgetItem() - newItem.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable + Qt.ItemIsEnabled)) - newItem.setText(self.opt_sr1_search.regex) - self.opt_sr.setItem(0,0, newItem) - newItem = QTableWidgetItem() - newItem.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable + Qt.ItemIsEnabled)) - newItem.setText(self.opt_sr1_replace.text()) - self.opt_sr.setItem(0,1, newItem) - self.opt_sr.setCurrentCell(0, 0) + if self.sr_search.regex: + self.opt_search_replace.insertRow(0) + newItem = self.opt_search_replace.itemPrototype().clone() + newItem.setText(self.sr_search.regex) + self.opt_search_replace.setItem(0,0, newItem) + newItem = self.opt_search_replace.itemPrototype().clone() + newItem.setText(self.sr_replace.text()) + self.opt_search_replace.setItem(0,1, newItem) + self.opt_search_replace.setCurrentCell(0, 0) def sr_change_clicked(self): - row = self.opt_sr.currentRow() + row = self.opt_search_replace.currentRow() if row >= 0: - self.opt_sr.item(row, 0).setText(self.opt_sr1_search.regex) - self.opt_sr.item(row, 1).setText(self.opt_sr1_replace.text()) - self.opt_sr.setCurrentCell(row, 0) + self.opt_search_replace.item(row, 0).setText(self.sr_search.regex) + self.opt_search_replace.item(row, 1).setText(self.sr_replace.text()) + self.opt_search_replace.setCurrentCell(row, 0) def sr_remove_clicked(self): - row = self.opt_sr.currentRow() + row = self.opt_search_replace.currentRow() if row >= 0: - self.opt_sr.removeRow(row) - self.opt_sr.setCurrentCell(row-1, 0) + self.opt_search_replace.removeRow(row) + self.opt_search_replace.setCurrentCell(row-1, 0) + + def sr_load_clicked(self): + filename = QFileDialog.getOpenFileName(self, 'Load Calibre Search-Replace definitions file', '.', 'Calibre Search-Replace definitions file (*.csr)') + if filename: + with open(filename, 'r') as f: + val = f.read() + self.set_value(self.opt_search_replace, val) + + def sr_save_clicked(self): + filename = QFileDialog.getSaveFileName(self, 'Save Calibre Search-Replace definitions file', '.', 'Calibre Search-Replace definitions file (*.csr)') + if filename: + with open(filename, 'w') as f: + val = self.get_value(self.opt_search_replace) + f.write(val) def sr_currentCellChanged(self, row, column, previousRow, previousColumn) : if row >= 0: self.sr_change.setEnabled(True) self.sr_remove.setEnabled(True) - self.opt_sr1_search.set_regex(self.opt_sr.item(row, 0).text()) - self.opt_sr1_replace.setText(self.opt_sr.item(row, 1).text()) + self.sr_search.set_regex(self.opt_search_replace.item(row, 0).text()) + self.sr_replace.setText(self.opt_search_replace.item(row, 1).text()) else: self.sr_change.setEnabled(False) self.sr_remove.setEnabled(False) @@ -95,34 +104,20 @@ class SearchAndReplaceWidget(Widget, Ui_Form): except: pass - d(self.opt_sr1_search) - d(self.opt_sr2_search) - d(self.opt_sr3_search) + d(self.sr_search) - self.opt_sr1_search.break_cycles() - self.opt_sr2_search.break_cycles() - self.opt_sr3_search.break_cycles() + self.sr_search.break_cycles() def update_doc(self, doc): - self.opt_sr1_search.set_doc(doc) - self.opt_sr2_search.set_doc(doc) - self.opt_sr3_search.set_doc(doc) + self.sr_search.set_doc(doc) def pre_commit_check(self): - for x in ('sr1_search', 'sr2_search', 'sr3_search'): - x = getattr(self, 'opt_'+x) + for row in xrange(0, self.opt_search_replace.rowCount()): try: - pat = unicode(x.regex) + pat = unicode(self.opt_search_replace.item(row,0).text()) re.compile(pat) except Exception as err: error_dialog(self, _('Invalid regular expression'), _('Invalid regular expression: %s')%err, show=True) return False return True - - @property - def opt_sr_items(self): - items = [] - for row in xrange(0, self.opt_sr.rowCount()): - items.append([self.opt_sr.getItem(row,0).text(), self.opt_sr.getItem(row,1).text()]) - return items diff --git a/src/calibre/gui2/convert/search_and_replace.ui b/src/calibre/gui2/convert/search_and_replace.ui index d55b5cd0a6..af276d7992 100644 --- a/src/calibre/gui2/convert/search_and_replace.ui +++ b/src/calibre/gui2/convert/search_and_replace.ui @@ -39,7 +39,7 @@ QLayout::SetMinimumSize - + 0 @@ -60,12 +60,12 @@ &Replacement Text - opt_sr1_replace + sr_replace - + 0 @@ -78,114 +78,6 @@ - - - - 0 - 0 - - - - Second Expression - - - - QLayout::SetMinimumSize - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - &Replacement Text - - - opt_sr2_replace - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Third expression - - - - QLayout::SetMinimumSize - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - &Replacement Text - - - opt_sr3_replace - - - - - - - - 0 - 0 - - - - - - - - -1 @@ -220,10 +112,24 @@ + + + + Load + + + + + + + Save + + + - - + + 0