mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Added CLI support search_replace by changing serialization and deserialization of option.
This commit is contained in:
parent
7ea167eec1
commit
f0708f779e
@ -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')),
|
||||
]
|
||||
# }}}
|
||||
|
||||
|
@ -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:
|
||||
|
50
src/calibre/ebooks/conversion/search_replace_option.py
Normal file
50
src/calibre/ebooks/conversion/search_replace_option.py
Normal 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')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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><p>Search and replace uses <i>regular expressions</i>. See the <a href="http://manual.calibre-ebook.com/regexp.html">regular expressions tutorial</a> 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><p>Search and replace uses <i>regular expressions</i>. See the <a href="http://manual.calibre-ebook.com/regexp.html">regular expressions tutorial</a> 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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user