%PDF- %PDF-
Direktori : /usr/lib/calibre/calibre/gui2/convert/ |
Current File : //usr/lib/calibre/calibre/gui2/convert/regex_builder.py |
#!/usr/bin/env python3 # License: GPLv3 Copyright: 2009, John Schember <john@nachtimwald.com> import os from contextlib import suppress from qt.core import ( QApplication, QBrush, QByteArray, QDialog, QDialogButtonBox, Qt, QTextCursor, QTextEdit, pyqtSignal ) from calibre.constants import iswindows from calibre.ebooks.conversion.search_replace import compile_regular_expression from calibre.gui2 import choose_files, error_dialog, gprefs from calibre.gui2.convert.regex_builder_ui import Ui_RegexBuilder from calibre.gui2.convert.xpath_wizard import XPathEdit from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.ptempfile import TemporaryFile from calibre.utils.icu import utf16_length from calibre.utils.ipc.simple_worker import WorkerError, fork_job from polyglot.builtins import native_string_type class RegexBuilder(QDialog, Ui_RegexBuilder): def __init__(self, db, book_id, regex, doc=None, parent=None): QDialog.__init__(self, parent) self.setupUi(self) self.regex.setText(regex) self.regex_valid() if not db or not book_id: button = self.button_box.addButton(QDialogButtonBox.StandardButton.Open) button.clicked.connect(self.open_clicked) elif not doc and not self.select_format(db, book_id): self.cancelled = True return if doc: self.preview.setPlainText(doc) self.cancelled = False self.button_box.accepted.connect(self.accept) self.regex.textChanged[native_string_type].connect(self.regex_valid) for src, slot in (('test', 'do'), ('previous', 'goto'), ('next', 'goto')): getattr(self, src).clicked.connect(getattr(self, '%s_%s'%(slot, src))) self.test.setDefault(True) self.match_locs = [] geom = gprefs.get('regex_builder_geometry', None) if geom is not None: QApplication.instance().safe_restore_geometry(self, QByteArray(geom)) self.finished.connect(self.save_state) def save_state(self, result): geom = bytearray(self.saveGeometry()) gprefs['regex_builder_geometry'] = geom def regex_valid(self): regex = str(self.regex.text()) if regex: try: compile_regular_expression(regex) self.regex.setStyleSheet('QLineEdit { color: black; background-color: rgba(0,255,0,20%); }') return True except: self.regex.setStyleSheet('QLineEdit { color: black; background-color: rgba(255,0,0,20%); }') else: self.regex.setStyleSheet('QLineEdit { color: black; background-color: white; }') self.preview.setExtraSelections([]) self.match_locs = [] self.next.setEnabled(False) self.previous.setEnabled(False) self.occurrences.setText('0') return False def do_test(self): selections = [] self.match_locs = [] class Pos: python: int = 0 qt: int = 0 if self.regex_valid(): text = str(self.preview.toPlainText()) regex = str(self.regex.text()) cursor = QTextCursor(self.preview.document()) extsel = QTextEdit.ExtraSelection() extsel.cursor = cursor extsel.format.setBackground(QBrush(Qt.GlobalColor.yellow)) with suppress(Exception): prev = Pos() for match in compile_regular_expression(regex).finditer(text): es = QTextEdit.ExtraSelection(extsel) qtchars_to_start = utf16_length(text[prev.python:match.start()]) qt_pos = prev.qt + qtchars_to_start prev.python = match.end() prev.qt = qt_pos + utf16_length(match.group()) es.cursor.setPosition(qt_pos, QTextCursor.MoveMode.MoveAnchor) es.cursor.setPosition(prev.qt, QTextCursor.MoveMode.KeepAnchor) selections.append(es) self.match_locs.append((qt_pos, prev.qt)) self.preview.setExtraSelections(selections) if self.match_locs: self.next.setEnabled(True) self.previous.setEnabled(True) self.occurrences.setText(str(len(self.match_locs))) def goto_previous(self): pos = self.preview.textCursor().position() if self.match_locs: match_loc = len(self.match_locs) - 1 for i in range(len(self.match_locs) - 1, -1, -1): loc = self.match_locs[i][1] if pos > loc: match_loc = i break self.goto_loc( self.match_locs[match_loc][1], operation=QTextCursor.MoveOperation.Left, n=self.match_locs[match_loc][1] - self.match_locs[match_loc][0]) def goto_next(self): pos = self.preview.textCursor().position() if self.match_locs: match_loc = 0 for i in range(len(self.match_locs)): loc = self.match_locs[i][0] if pos < loc: match_loc = i break self.goto_loc(self.match_locs[match_loc][0], n=self.match_locs[match_loc][1] - self.match_locs[match_loc][0]) def goto_loc(self, loc, operation=QTextCursor.MoveOperation.Right, mode=QTextCursor.MoveMode.KeepAnchor, n=0): cursor = QTextCursor(self.preview.document()) cursor.setPosition(loc) if n: cursor.movePosition(operation, mode, n) self.preview.setTextCursor(cursor) def select_format(self, db, book_id): format = None formats = db.formats(book_id, index_is_id=True).upper().split(',') if len(formats) == 1: format = formats[0] elif len(formats) > 1: d = ChooseFormatDialog(self, _('Choose the format to view'), formats) d.exec() if d.result() == QDialog.DialogCode.Accepted: format = d.format() else: return False if not format: error_dialog(self, _('No formats available'), _('Cannot build regex using the GUI builder without a book.'), show=True) return False try: fpath = db.format(book_id, format, index_is_id=True, as_path=True) except OSError: if iswindows: import traceback error_dialog(self, _('Could not open file'), _('Could not open the file, do you have it open in' ' another program?'), show=True, det_msg=traceback.format_exc()) return False raise try: self.open_book(fpath) finally: try: os.remove(fpath) except: # Fails on windows if the input plugin for this format keeps the file open # Happens for LIT files pass return True def open_book(self, pathtoebook): with TemporaryFile('_prepprocess_gui') as tf: err_msg = _('Failed to generate markup for testing. Click ' '"Show details" to learn more.') try: fork_job('calibre.ebooks.oeb.iterator', 'get_preprocess_html', (pathtoebook, tf)) except WorkerError as e: return error_dialog(self, _('Failed to generate preview'), err_msg, det_msg=e.orig_tb, show=True) except: import traceback return error_dialog(self, _('Failed to generate preview'), err_msg, det_msg=traceback.format_exc(), show=True) with open(tf, 'rb') as f: self.preview.setPlainText(f.read().decode('utf-8')) def open_clicked(self): files = choose_files(self, 'regexp tester dialog', _('Open book'), select_only_single_file=True) if files: self.open_book(files[0]) def doc(self): return str(self.preview.toPlainText()) class RegexEdit(XPathEdit): doc_update = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent) self.edit.completer().setCaseSensitivity(Qt.CaseSensitivity.CaseSensitive) self.book_id = None self.db = None self.doc_cache = None def wizard(self): return self.builder() def builder(self): if self.db is None: self.doc_cache = _('Click the "Open" button below to open a ' 'e-book to use for testing.') bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self.doc_cache, self) if bld.cancelled: return if not self.doc_cache: self.doc_cache = bld.doc() self.doc_update.emit(self.doc_cache) if bld.exec() == QDialog.DialogCode.Accepted: self.edit.setText(bld.regex.text()) def doc(self): return self.doc_cache def setObjectName(self, *args): super().setObjectName(*args) if hasattr(self, 'edit'): self.edit.initialize('regex_edit_'+str(self.objectName())) def set_msg(self, msg): self.msg.setText(msg) def set_book_id(self, book_id): self.book_id = book_id def set_db(self, db): self.db = db def set_doc(self, doc): self.doc_cache = doc def set_regex(self, regex): self.edit.setText(regex) def break_cycles(self): self.db = self.doc_cache = None @property def text(self): return str(self.edit.text()) @property def regex(self): return self.text def clear(self): self.edit.clear() def check(self): return True if __name__ == '__main__': from calibre.gui2 import Application app = Application([]) d = RegexBuilder(None, None, 'a', doc='😉123abc XYZabc') d.do_test() d.exec() del d del app