Added CLI support search_replace by changing serialization and deserialization of option.

This commit is contained in:
Eli Algranti 2012-04-13 14:04:26 +10:00
parent 7ea167eec1
commit f0708f779e
5 changed files with 120 additions and 24 deletions

View File

@ -628,7 +628,17 @@ OptionRecommendation(name='sr3_replace',
OptionRecommendation(name='search_replace',
recommended_value='[]', level=OptionRecommendation.LOW,
help=_('Modify the document text and structure using user defined patterns.')),
help=_('Modify the document text and structure using user defined patterns.'
'This option accepts parameters in two forms:\n'
'1.file:<path to search/replace definitions file>\n'
'The file should contain alternating lines or search/replace strings:\n'
' <search>\n'
' <replace>\n'
' <search>\n'
' <replace>\n'
'Files saved through the user interface dialog can be used with this option.\n'
'2.json:<json encoded list containg [search, replace] touples:\n'
' json:[["search","replace"],["search","replace"]]\n')),
]
# }}}

View File

@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import functools, re, json
import functools, re, search_replace_option
from calibre import entity_to_unicode, as_unicode
@ -515,8 +515,8 @@ class HTMLPreProcessor(object):
if not getattr(self.extra_opts, 'keep_ligatures', False):
html = _ligpat.sub(lambda m:LIGATURES[m.group()], html)
search_replace = json.loads(getattr(self.extra_opts, 'search_replace', '[]'))
for search_pattern, replace_txt in search_replace:
# Function for processing search and replace
def do_search_replace(search_pattern, replace_txt):
if search_pattern:
try:
search_re = re.compile(search_pattern)
@ -528,6 +528,17 @@ class HTMLPreProcessor(object):
self.log.error('Failed to parse %r regexp because %s' %
(search, as_unicode(e)))
#search / replace using the sr?_search / sr?_replace options
for search, replace in [['sr3_search', 'sr3_replace'], ['sr2_search', 'sr2_replace'], ['sr1_search', 'sr1_replace']]:
search_pattern = getattr(self.extra_opts, search, '')
replace_txt = getattr(self.extra_opts, replace, '')
do_search_replace(search_pattern, replace_txt)
# multi-search / replace using the search_replace option
search_replace = search_replace_option.decode(getattr(self.extra_opts, 'search_replace', '[]'))
for search_pattern, replace_txt in search_replace:
do_search_replace(search_pattern, replace_txt)
end_rules = []
# delete soft hyphens - moved here so it's executed after header/footer removal
if is_pdftohtml:

View File

@ -0,0 +1,50 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2012, Eli Algranti <idea00@hotmail.com>'
__docformat__ = 'restructuredtext en'
import json
from itertools import izip
def encodeJson(definition):
'''
Encode a search/replace definition using json.
'''
return 'json:' + json.dumps(definition)
def encodeFile(definition, filename):
'''
Encode a search/replace definition into a file
'''
with open(filename, 'w') as f:
for search,replace in definition:
f.write(search + '\n')
f.write(replace + '\n')
return 'file:'+filename
def decode(definition):
'''
Decodes a search/replace definition
'''
if definition.startswith('json:'):
return json.loads(definition[len('json:'):])
elif definition.startswith('file:'):
with open(definition[len('file:'):], 'r') as f:
ans = []
for search, replace in izip(f, f):
ans.append([search.rstrip('\n\r'), replace.rstrip('\n\r')])
return ans
raise Exception('Invalid definition')

View File

@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__copyright__ = '2011, John Schember <john@nachtimwald.com>, 2012 Eli Algranti <idea00@hotmail.com>'
__docformat__ = 'restructuredtext en'
import re, json
import re
from calibre.ebooks.conversion import search_replace_option
from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import QTableWidget, QTableWidgetItem, QFileDialog
from PyQt4.QtGui import QTableWidget, QTableWidgetItem, QFileDialog, QMessageBox
from calibre.gui2.convert.search_and_replace_ui import Ui_Form
from calibre.gui2.convert import Widget
from calibre.gui2 import error_dialog
@ -41,7 +42,7 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
self.search_replace.setColumnCount(2)
self.search_replace.setColumnWidth(0, 300)
self.search_replace.setColumnWidth(1, 300)
self.search_replace.setHorizontalHeaderLabels(['Search Expression', 'Replacement'])
self.search_replace.setHorizontalHeaderLabels([_('Search Regular Expression'), _('Replacement Text')])
self.connect(self.sr_add, SIGNAL('clicked()'), self.sr_add_clicked)
self.connect(self.sr_change, SIGNAL('clicked()'), self.sr_change_clicked)
@ -82,18 +83,14 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
self.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)')
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.search_replace, val)
self.set_value_handler(self.opt_search_replace, 'file:'+unicode(filename))
def sr_save_clicked(self):
filename = QFileDialog.getSaveFileName(self, 'Save Calibre Search-Replace definitions file', '.', 'Calibre Search-Replace definitions file (*.csr)')
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.search_replace)
f.write(val)
search_replace_option.encodeFile(self.get_definitions(), unicode(filename))
def sr_currentCellChanged(self, row, column, previousRow, previousColumn) :
if row >= 0:
@ -122,14 +119,40 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
self.sr_search.set_doc(doc)
def pre_commit_check(self):
for row in xrange(0, self.search_replace.rowCount()):
definitions = self.get_definitions()
# Verify the search/replace in the edit widgets has been
# included to the list of search/replace definitions
edit_search = self.sr_search.regex
if edit_search:
edit_replace = unicode(self.sr_replace.text())
found = False
for search, replace in definitions:
if search == edit_search and replace == edit_replace:
found = True
break
if not found:
msgBox = QMessageBox(self)
msgBox.setText(_('The search / replace definition being edited has not been added to the list of definitions'))
msgBox.setInformativeText(_('Do you wish to continue with the conversion (the definition will not be used)?'))
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
msgBox.setDefaultButton(QMessageBox.No)
if msgBox.exec_() != QMessageBox.Yes:
return False
# Verify all search expressions are valid
for search, replace in definitions:
try:
pat = unicode(self.search_replace.item(row,0).text())
re.compile(pat)
re.compile(search)
except Exception as err:
error_dialog(self, _('Invalid regular expression'),
_('Invalid regular expression: %s')%err, show=True)
return False
return True
# Options
@ -171,14 +194,16 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
def get_value_handler(self, g):
if g != self.opt_search_replace:
return None
return search_replace_option.encodeJson(self.get_definitions())
def get_definitions(self):
ans = []
for row in xrange(0, self.search_replace.rowCount()):
colItems = []
for col in xrange(0, self.search_replace.columnCount()):
colItems.append(unicode(self.search_replace.item(row, col).text()))
ans.append(colItems)
return json.dumps(ans)
return ans
def set_value_handler(self, g, val):
if g != self.opt_search_replace:
@ -186,7 +211,7 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
return True
try:
rowItems = json.loads(val)
rowItems = search_replace_option.decode(val)
if not isinstance(rowItems, list):
rowItems = []
except:

View File

@ -32,7 +32,7 @@
</sizepolicy>
</property>
<property name="title">
<string>First expression</string>
<string>Search/Replace Definition Edit</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
@ -147,7 +147,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;p&gt;Search and replace uses &lt;i&gt;regular expressions&lt;/i&gt;. See the &lt;a href=&quot;http://manual.calibre-ebook.com/regexp.html&quot;&gt;regular expressions tutorial&lt;/a&gt; to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document.</string>
<string>&lt;p&gt;Search and replace uses &lt;i&gt;regular expressions&lt;/i&gt;. See the &lt;a href=&quot;http://manual.calibre-ebook.com/regexp.html&quot;&gt;regular expressions tutorial&lt;/a&gt; to get started with regular expressions. Also clicking the wizard button below will allow you to test your regular expression against the current input document.</string>
</property>
<property name="wordWrap">
<bool>true</bool>