def test_change_case(self): ' Test the various ways of changing the case ' from calibre.utils.titlecase import titlecase # Test corner cases self.ae('A', icu.upper(b'a')) for x in ('a', 'Alice\'s code', 'macdonald\'s machIne', '02 the wars'): self.ae(icu.upper(x), x.upper()) self.ae(icu.lower(x), x.lower()) # ICU's title case algorithm is different from ours, when there are # capitals inside words self.ae(icu.title_case(x), titlecase(x).replace('machIne', 'Machine')) self.ae(icu.capitalize(x), x[0].upper() + x[1:].lower())
def test_change_case(self): ' Test the various ways of changing the case ' from calibre.utils.titlecase import titlecase # Test corner cases self.ae('A', icu.upper(b'a')) for x in ('', None, False, 1): self.ae(x, icu.capitalize(x)) for x in ('a', 'Alice\'s code', 'macdonald\'s machIne', '02 the wars'): self.ae(icu.upper(x), x.upper()) self.ae(icu.lower(x), x.lower()) # ICU's title case algorithm is different from ours, when there are # capitals inside words self.ae(icu.title_case(x), titlecase(x).replace('machIne', 'Machine')) self.ae(icu.capitalize(x), x[0].upper() + x[1:].lower())
def test_change_case(self): " Test the various ways of changing the case " from calibre.utils.titlecase import titlecase # Test corner cases self.ae("A", icu.upper(b"a")) for x in ("", None, False, 1): self.ae(x, icu.capitalize(x)) for x in ("a", "Alice's code", "macdonald's machIne", "02 the wars"): self.ae(icu.upper(x), x.upper()) self.ae(icu.lower(x), x.lower()) # ICU's title case algorithm is different from ours, when there are # capitals inside words self.ae(icu.title_case(x), titlecase(x).replace("machIne", "Machine")) self.ae(icu.capitalize(x), x[0].upper() + x[1:].lower())
def title_case(self): from calibre.utils.titlecase import titlecase for item in self.selectedItems(): t = unicode(item.data(0, Qt.DisplayRole) or '') item.setData(0, Qt.DisplayRole, titlecase(t))
def apply_rules(tag, rules): ans = [] tags = deque() tags.append(tag) maxiter = 20 while tags and maxiter > 0: tag = tags.popleft() ltag = icu_lower(tag) maxiter -= 1 for rule, matches in rules: if matches(ltag): ac = rule['action'] if ac == 'remove': break if ac == 'keep': ans.append(tag) break if ac == 'replace': if 'matches' in rule['match_type']: tag = compile_pat(rule['query']).sub(rule['replace'], tag) else: tag = rule['replace'] if ',' in tag: replacement_tags = [] self_added = False for rtag in (x.strip() for x in tag.split(',')): if icu_lower(rtag) == ltag: if not self_added: ans.append(rtag) self_added = True else: replacement_tags.append(rtag) tags.extendleft(reversed(replacement_tags)) else: if icu_lower(tag) == ltag: # Case change or self replacement ans.append(tag) break tags.appendleft(tag) break if ac == 'capitalize': ans.append(tag.capitalize()) break if ac == 'titlecase': from calibre.utils.titlecase import titlecase ans.append(titlecase(tag)) break if ac == 'lower': ans.append(icu_lower(tag)) break if ac == 'upper': ans.append(icu_upper(tag)) break if ac == 'split': stags = list(filter(None, (x.strip() for x in tag.split(rule['replace'])))) if stags: if stags[0] == tag: ans.append(tag) else: tags.extendleft(reversed(stags)) break else: # no rule matched, default keep ans.append(tag) ans.extend(tags) return ans
def do_one(self, id): remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, \ do_autonumber, do_remove_format, remove_format, do_swap_ta, \ do_remove_conv, do_auto_author, series, do_series_restart, \ series_start_value, do_title_case, cover_action, clear_series, \ pubdate, adddate, do_title_sort, languages, clear_languages, \ restore_original = self.args # first loop: All changes that modify the filesystem and commit # immediately. We want to # try hard to keep the DB and the file system in sync, even in the face # of exceptions or forced exits. if self.current_phase == 1: title_set = False if do_swap_ta: title = self.db.title(id, index_is_id=True) aum = self.db.authors(id, index_is_id=True) if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')] new_title = authors_to_string(aum) if do_title_case: new_title = titlecase(new_title) self.db.set_title(id, new_title, notify=False) title_set = True if title: new_authors = string_to_authors(title) self.db.set_authors(id, new_authors, notify=False) if do_title_case and not title_set: title = self.db.title(id, index_is_id=True) self.db.set_title(id, titlecase(title), notify=False) if do_title_sort: title = self.db.title(id, index_is_id=True) if languages: lang = languages[0] else: lang = self.db.languages(id, index_is_id=True) if lang: lang = lang.partition(',')[0] self.db.set_title_sort(id, title_sort(title, lang=lang), notify=False) if au: self.db.set_authors(id, string_to_authors(au), notify=False) if cover_action == 'remove': self.db.remove_cover(id) elif cover_action == 'generate': from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx from calibre.gui2 import config mi = self.db.get_metadata(id, index_is_id=True) series_string = None if mi.series: series_string = _('Book %(sidx)s of %(series)s')%dict( sidx=fmt_sidx(mi.series_index, use_roman=config['use_roman_numerals_for_series_number']), series=mi.series) cdata = calibre_cover(mi.title, mi.format_field('authors')[-1], series_string=series_string) self.db.set_cover(id, cdata) elif cover_action == 'fromfmt': fmts = self.db.formats(id, index_is_id=True, verify_formats=False) if fmts: covers = [] for fmt in fmts.split(','): fmtf = self.db.format(id, fmt, index_is_id=True, as_file=True) if fmtf is None: continue cdata, area = get_cover_data(fmtf, fmt) if cdata: covers.append((cdata, area)) covers.sort(key=lambda x: x[1]) if covers: self.db.set_cover(id, covers[-1][0]) covers = [] if do_remove_format: self.db.remove_format(id, remove_format, index_is_id=True, notify=False, commit=True) if restore_original: formats = self.db.formats(id, index_is_id=True) formats = formats.split(',') if formats else [] originals = [x.upper() for x in formats if x.upper().startswith('ORIGINAL_')] for ofmt in originals: fmt = ofmt.replace('ORIGINAL_', '') with SpooledTemporaryFile(SPOOL_SIZE) as stream: self.db.copy_format_to(id, ofmt, stream, index_is_id=True) stream.seek(0) self.db.add_format(id, fmt, stream, index_is_id=True, notify=False) self.db.remove_format(id, ofmt, index_is_id=True, notify=False, commit=True) elif self.current_phase == 2: # All of these just affect the DB, so we can tolerate a total rollback if do_auto_author: x = self.db.author_sort_from_book(id, index_is_id=True) if x: self.db.set_author_sort(id, x, notify=False, commit=False) if aus and do_aus: self.db.set_author_sort(id, aus, notify=False, commit=False) if rating != -1: self.db.set_rating(id, 2*rating, notify=False, commit=False) if pub: self.db.set_publisher(id, pub, notify=False, commit=False) if clear_series: self.db.set_series(id, '', notify=False, commit=False) if pubdate is not None: self.db.set_pubdate(id, pubdate, notify=False, commit=False) if adddate is not None: self.db.set_timestamp(id, adddate, notify=False, commit=False) if do_series: if do_series_restart: if self.series_start_value is None: self.series_start_value = series_start_value next = self.series_start_value self.series_start_value += 1 else: next = self.db.get_next_series_num_for(series) self.db.set_series(id, series, notify=False, commit=False) if not series: self.db.set_series_index(id, 1.0, notify=False, commit=False) elif do_autonumber: # is True if do_series_restart is True self.db.set_series_index(id, next, notify=False, commit=False) elif tweaks['series_index_auto_increment'] != 'no_change': self.db.set_series_index(id, 1.0, notify=False, commit=False) if do_remove_conv: self.db.delete_conversion_options(id, 'PIPE', commit=False) if clear_languages: self.db.set_languages(id, [], notify=False, commit=False) elif languages: self.db.set_languages(id, languages, notify=False, commit=False) elif self.current_phase == 3: # both of these are fast enough to just do them all for w in self.cc_widgets: w.commit(self.ids) if remove_all: self.db.remove_all_tags(self.ids) self.db.bulk_modify_tags(self.ids, add=add, remove=remove, notify=False) self.current_index = len(self.ids) elif self.current_phase == 4: self.s_r_func(id) # do the next one self.current_index += 1 self.do_one_signal.emit()
def title_case(self): from calibre.utils.titlecase import titlecase self.context_item.setText(titlecase(unicode(self.context_item.text())))
def title_case(self): from calibre.utils.titlecase import titlecase self.setText(titlecase(unicode_type(self.text())))
def fixcase(x): if x: from calibre.utils.titlecase import titlecase x = titlecase(x) return x
class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): s_r_functions = {'' : lambda x: x, _('Lower Case') : lambda x: icu_lower(x), _('Upper Case') : lambda x: icu_upper(x), _('Title Case') : lambda x: titlecase(x), _('Capitalize') : lambda x: capitalize(x), } s_r_match_modes = [_('Character match'), _('Regular Expression'), ] s_r_replace_modes = [_('Replace field'), _('Prepend to field'), _('Append to field'), ] def __init__(self, window, rows, model, tab, refresh_books): ResizableDialog.__init__(self, window) Ui_MetadataBulkDialog.__init__(self) self.model = model self.db = model.db self.refresh_book_list.setChecked(gprefs['refresh_book_list_on_bulk_edit']) self.refresh_book_list.toggled.connect(self.save_refresh_booklist) self.ids = [self.db.id(r) for r in rows] self.first_title = self.db.title(self.ids[0], index_is_id=True) self.cover_clone.setToolTip(unicode(self.cover_clone.toolTip()) + ' (%s)' % self.first_title) self.box_title.setText('<p>' + _('Editing meta information for <b>%d books</b>') % len(rows)) self.write_series = False self.changed = False self.refresh_books = refresh_books self.comments = null self.comments_button.clicked.connect(self.set_comments) all_tags = self.db.all_tags() self.tags.update_items_cache(all_tags) self.remove_tags.update_items_cache(all_tags) self.initialize_combos() for f in sorted(self.db.all_formats()): self.remove_format.addItem(f) self.remove_format.setCurrentIndex(-1) self.series.currentIndexChanged[int].connect(self.series_changed) self.series.editTextChanged.connect(self.series_changed) self.tag_editor_button.clicked.connect(self.tag_editor) self.autonumber_series.stateChanged[int].connect(self.auto_number_changed) self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME) self.pubdate_cw = CalendarWidget(self.pubdate) self.pubdate.setCalendarWidget(self.pubdate_cw) pubdate_format = tweaks['gui_pubdate_display_format'] if pubdate_format is not None: self.pubdate.setDisplayFormat(pubdate_format) self.pubdate.setSpecialValueText(_('Undefined')) self.clear_pubdate_button.clicked.connect(self.clear_pubdate) self.pubdate.dateTimeChanged.connect(self.do_apply_pubdate) self.adddate.setDateTime(QDateTime.currentDateTime()) self.adddate.setMinimumDateTime(UNDEFINED_QDATETIME) adddate_format = tweaks['gui_timestamp_display_format'] if adddate_format is not None: self.adddate.setDisplayFormat(adddate_format) self.adddate.setSpecialValueText(_('Undefined')) self.clear_adddate_button.clicked.connect(self.clear_adddate) self.adddate.dateTimeChanged.connect(self.do_apply_adddate) if len(self.db.custom_field_keys(include_composites=False)) == 0: self.central_widget.removeTab(1) else: self.create_custom_column_editors() self.prepare_search_and_replace() self.button_box.clicked.connect(self.button_clicked) self.button_box.button(QDialogButtonBox.Apply).setToolTip(_( 'Immediately make all changes without closing the dialog. ' 'This operation cannot be canceled or undone')) self.do_again = False self.central_widget.setCurrentIndex(tab) geom = gprefs.get('bulk_metadata_window_geometry', None) if geom is not None: self.restoreGeometry(bytes(geom)) ct = gprefs.get('bulk_metadata_window_tab', 0) self.central_widget.setCurrentIndex(ct) self.languages.init_langs(self.db) self.languages.setEditText('') self.authors.setFocus(Qt.OtherFocusReason) self.exec_() def set_comments(self): from calibre.gui2.dialogs.comments_dialog import CommentsDialog d = CommentsDialog(self, '' if self.comments is null else (self.comments or ''), _('Comments')) if d.exec_() == d.Accepted: self.comments = d.textbox.html b = self.comments_button b.setStyleSheet('QPushButton { font-weight: bold }') if unicode(b.text())[-1] != '*': b.setText(unicode(b.text()) + ' *') def save_refresh_booklist(self, *args): gprefs['refresh_book_list_on_bulk_edit'] = bool(self.refresh_book_list.isChecked()) def save_state(self, *args): gprefs['bulk_metadata_window_geometry'] = \ bytearray(self.saveGeometry()) gprefs['bulk_metadata_window_tab'] = self.central_widget.currentIndex() def do_apply_pubdate(self, *args): self.apply_pubdate.setChecked(True) def clear_pubdate(self, *args): self.pubdate.setDateTime(UNDEFINED_QDATETIME) def do_apply_adddate(self, *args): self.apply_adddate.setChecked(True) def clear_adddate(self, *args): self.adddate.setDateTime(UNDEFINED_QDATETIME) def button_clicked(self, which): if which == self.button_box.button(QDialogButtonBox.Apply): self.do_again = True self.accept() # S&R {{{ def prepare_search_and_replace(self): self.search_for.initialize('bulk_edit_search_for') self.replace_with.initialize('bulk_edit_replace_with') self.s_r_template.initialize('bulk_edit_template') self.test_text.initialize('bulk_edit_test_test') self.all_fields = [''] self.writable_fields = [''] fm = self.db.field_metadata for f in fm: if (f in ['author_sort'] or (fm[f]['datatype'] in ['text', 'series', 'enumeration', 'comments'] and fm[f].get('search_terms', None) and f not in ['formats', 'ondevice', 'series_sort']) or (fm[f]['datatype'] in ['int', 'float', 'bool', 'datetime'] and f not in ['id', 'timestamp'])): self.all_fields.append(f) self.writable_fields.append(f) if fm[f]['datatype'] == 'composite': self.all_fields.append(f) self.all_fields.sort() self.all_fields.insert(1, '{template}') self.writable_fields.sort() self.search_field.setMaxVisibleItems(25) self.destination_field.setMaxVisibleItems(25) self.testgrid.setColumnStretch(1, 1) self.testgrid.setColumnStretch(2, 1) offset = 10 self.s_r_number_of_books = min(10, len(self.ids)) for i in range(1,self.s_r_number_of_books+1): w = QLabel(self.tabWidgetPage3) w.setText(_('Book %d:')%i) self.testgrid.addWidget(w, i+offset, 0, 1, 1) w = QLineEdit(self.tabWidgetPage3) w.setReadOnly(True) name = 'book_%d_text'%i setattr(self, name, w) self.book_1_text.setObjectName(name) self.testgrid.addWidget(w, i+offset, 1, 1, 1) w = QLineEdit(self.tabWidgetPage3) w.setReadOnly(True) name = 'book_%d_result'%i setattr(self, name, w) self.book_1_text.setObjectName(name) self.testgrid.addWidget(w, i+offset, 2, 1, 1) ident_types = sorted(self.db.get_all_identifier_types(), key=sort_key) self.s_r_dst_ident.setCompleter(QCompleter(ident_types)) try: self.s_r_dst_ident.setPlaceholderText(_('Enter an identifier type')) except: pass self.s_r_src_ident.addItems(ident_types) self.main_heading = _( '<b>You can destroy your library using this feature.</b> ' 'Changes are permanent. There is no undo function. ' 'You are strongly encouraged to back up your library ' 'before proceeding.<p>' 'Search and replace in text fields using character matching ' 'or regular expressions. ') self.character_heading = _( 'In character mode, the field is searched for the entered ' 'search text. The text is replaced by the specified replacement ' 'text everywhere it is found in the specified field. After ' 'replacement is finished, the text can be changed to ' 'upper-case, lower-case, or title-case. If the case-sensitive ' 'check box is checked, the search text must match exactly. If ' 'it is unchecked, the search text will match both upper- and ' 'lower-case letters' ) self.regexp_heading = _( 'In regular expression mode, the search text is an ' 'arbitrary python-compatible regular expression. The ' 'replacement text can contain backreferences to parenthesized ' 'expressions in the pattern. The search is not anchored, ' 'and can match and replace multiple times on the same string. ' 'The modification functions (lower-case etc) are applied to the ' 'matched text, not to the field as a whole. ' 'The destination box specifies the field where the result after ' 'matching and replacement is to be assigned. You can replace ' 'the text in the field, or prepend or append the matched text. ' 'See <a href="http://docs.python.org/library/re.html"> ' 'this reference</a> for more information on python\'s regular ' 'expressions, and in particular the \'sub\' function.' ) self.search_mode.addItems(self.s_r_match_modes) self.search_mode.setCurrentIndex(dynamic.get('s_r_search_mode', 0)) self.replace_mode.addItems(self.s_r_replace_modes) self.replace_mode.setCurrentIndex(0) self.s_r_search_mode = 0 self.s_r_error = None self.s_r_obj = None self.replace_func.addItems(sorted(self.s_r_functions.keys())) self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed) self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed) self.destination_field.currentIndexChanged[int].connect(self.s_r_destination_field_changed) self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results) self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results) self.search_for.editTextChanged[str].connect(self.s_r_paint_results) self.replace_with.editTextChanged[str].connect(self.s_r_paint_results) self.test_text.editTextChanged[str].connect(self.s_r_paint_results) self.comma_separated.stateChanged.connect(self.s_r_paint_results) self.case_sensitive.stateChanged.connect(self.s_r_paint_results) self.s_r_src_ident.currentIndexChanged[int].connect(self.s_r_identifier_type_changed) self.s_r_dst_ident.textChanged.connect(self.s_r_paint_results) self.s_r_template.lost_focus.connect(self.s_r_template_changed) self.central_widget.setCurrentIndex(0) self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive) self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive) self.s_r_template.completer().setCaseSensitivity(Qt.CaseSensitive) self.s_r_search_mode_changed(self.search_mode.currentIndex()) self.multiple_separator.setFixedWidth(30) self.multiple_separator.setText(' ::: ') self.multiple_separator.textChanged.connect(self.s_r_separator_changed) self.results_count.valueChanged[int].connect(self.s_r_display_bounds_changed) self.starting_from.valueChanged[int].connect(self.s_r_display_bounds_changed) self.save_button.clicked.connect(self.s_r_save_query) self.remove_button.clicked.connect(self.s_r_remove_query) self.queries = JSONConfig("search_replace_queries") self.saved_search_name = '' self.query_field.addItem("") self.query_field_values = sorted([q for q in self.queries], key=sort_key) self.query_field.addItems(self.query_field_values) self.query_field.currentIndexChanged[str].connect(self.s_r_query_change) self.query_field.setCurrentIndex(0) self.search_field.setCurrentIndex(0) self.s_r_search_field_changed(0) def s_r_sf_itemdata(self, idx): if idx is None: idx = self.search_field.currentIndex() return unicode(self.search_field.itemData(idx).toString()) def s_r_df_itemdata(self, idx): if idx is None: idx = self.destination_field.currentIndex() return unicode(self.destination_field.itemData(idx).toString()) def s_r_get_field(self, mi, field): if field: if field == '{template}': v = SafeFormat().safe_format( unicode(self.s_r_template.text()), mi, _('S/R TEMPLATE ERROR'), mi) return [v] fm = self.db.metadata_for_field(field) if field == 'sort': val = mi.get('title_sort', None) elif fm['datatype'] == 'datetime': val = mi.format_field(field)[1] else: val = mi.get(field, None) if isinstance(val, (int, float, bool)): val = str(val) elif fm['is_csp']: # convert the csp dict into a list id_type = unicode(self.s_r_src_ident.currentText()) if id_type: val = [val.get(id_type, '')] else: val = [u'%s:%s'%(t[0], t[1]) for t in val.iteritems()] if val is None: val = [] if fm['is_multiple'] else [''] elif not fm['is_multiple']: val = [val] elif fm['datatype'] == 'composite': val = [v2.strip() for v2 in val.split(fm['is_multiple']['ui_to_list'])] elif field == 'authors': val = [v2.replace('|', ',') for v2 in val] else: val = [] if not val: val = [''] return val def s_r_display_bounds_changed(self, i): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_template_changed(self): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_identifier_type_changed(self, idx): self.s_r_search_field_changed(self.search_field.currentIndex()) self.s_r_paint_results(idx) def s_r_search_field_changed(self, idx): self.s_r_template.setVisible(False) self.template_label.setVisible(False) self.s_r_src_ident_label.setVisible(False) self.s_r_src_ident.setVisible(False) if idx == 1: # Template self.s_r_template.setVisible(True) self.template_label.setVisible(True) elif self.s_r_sf_itemdata(idx) == 'identifiers': self.s_r_src_ident_label.setVisible(True) self.s_r_src_ident.setVisible(True) for i in range(0, self.s_r_number_of_books): w = getattr(self, 'book_%d_text'%(i+1)) mi = self.db.get_metadata(self.ids[i], index_is_id=True) src = self.s_r_sf_itemdata(idx) t = self.s_r_get_field(mi, src) if len(t) > 1: t = t[self.starting_from.value()-1: self.starting_from.value()-1 + self.results_count.value()] w.setText(unicode(self.multiple_separator.text()).join(t)) if self.search_mode.currentIndex() == 0: self.destination_field.setCurrentIndex(idx) else: self.s_r_destination_field_changed(self.destination_field.currentIndex()) self.s_r_paint_results(None) def s_r_destination_field_changed(self, idx): self.s_r_dst_ident_label.setVisible(False) self.s_r_dst_ident.setVisible(False) txt = self.s_r_df_itemdata(idx) if not txt: txt = self.s_r_sf_itemdata(None) if txt and txt in self.writable_fields: if txt == 'identifiers': self.s_r_dst_ident_label.setVisible(True) self.s_r_dst_ident.setVisible(True) self.destination_field_fm = self.db.metadata_for_field(txt) self.s_r_paint_results(None) def s_r_search_mode_changed(self, val): self.search_field.clear() self.destination_field.clear() if val == 0: for f in self.writable_fields: self.search_field.addItem(f if f != 'sort' else 'title_sort', f) self.destination_field.addItem(f if f != 'sort' else 'title_sort', f) self.destination_field.setCurrentIndex(0) self.destination_field.setVisible(False) self.destination_field_label.setVisible(False) self.replace_mode.setCurrentIndex(0) self.replace_mode.setVisible(False) self.replace_mode_label.setVisible(False) self.comma_separated.setVisible(False) self.s_r_heading.setText('<p>'+self.main_heading + self.character_heading) else: self.search_field.blockSignals(True) self.destination_field.blockSignals(True) for f in self.all_fields: self.search_field.addItem(f if f != 'sort' else 'title_sort', f) for f in self.writable_fields: self.destination_field.addItem(f if f != 'sort' else 'title_sort', f) self.search_field.blockSignals(False) self.destination_field.blockSignals(False) self.destination_field.setVisible(True) self.destination_field_label.setVisible(True) self.replace_mode.setVisible(True) self.replace_mode_label.setVisible(True) self.comma_separated.setVisible(True) self.s_r_heading.setText('<p>'+self.main_heading + self.regexp_heading) self.s_r_paint_results(None) def s_r_separator_changed(self, txt): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_set_colors(self): if self.s_r_error is not None: col = 'rgb(255, 0, 0, 20%)' self.test_result.setText(self.s_r_error.message) else: col = 'rgb(0, 255, 0, 20%)' self.test_result.setStyleSheet('QLineEdit { color: black; ' 'background-color: %s; }'%col) for i in range(0,self.s_r_number_of_books): getattr(self, 'book_%d_result'%(i+1)).setText('') def s_r_func(self, match): rfunc = self.s_r_functions[unicode(self.replace_func.currentText())] rtext = unicode(self.replace_with.text()) rtext = match.expand(rtext) return rfunc(rtext) def s_r_do_regexp(self, mi): src_field = self.s_r_sf_itemdata(None) src = self.s_r_get_field(mi, src_field) result = [] rfunc = self.s_r_functions[unicode(self.replace_func.currentText())] for s in src: t = self.s_r_obj.sub(self.s_r_func, s) if self.search_mode.currentIndex() == 0: t = rfunc(t) result.append(t) return result def s_r_do_destination(self, mi, val): src = self.s_r_sf_itemdata(None) if src == '': return '' dest = self.s_r_df_itemdata(None) if dest == '': if (src == '{template}' or self.db.metadata_for_field(src)['datatype'] == 'composite'): raise Exception(_('You must specify a destination when source is ' 'a composite field or a template')) dest = src dest_mode = self.replace_mode.currentIndex() if self.destination_field_fm['is_csp']: dest_ident = unicode(self.s_r_dst_ident.text()) if not dest_ident or (src == 'identifiers' and dest_ident == '*'): raise Exception(_('You must specify a destination identifier type')) if self.destination_field_fm['is_multiple']: if self.comma_separated.isChecked(): splitter = self.destination_field_fm['is_multiple']['ui_to_list'] res = [] for v in val: res.extend([x.strip() for x in v.split(splitter) if x.strip()]) val = res else: val = [v.replace(',', '') for v in val] if dest_mode != 0: dest_val = mi.get(dest, '') if self.db.metadata_for_field(dest)['is_csp']: dst_id_type = unicode(self.s_r_dst_ident.text()) if dst_id_type: dest_val = [dest_val.get(dst_id_type, '')] else: # convert the csp dict into a list dest_val = [u'%s:%s'%(t[0], t[1]) for t in dest_val.iteritems()] if dest_val is None: dest_val = [] elif not isinstance(dest_val, list): dest_val = [dest_val] else: dest_val = [] if dest_mode == 1: val.extend(dest_val) elif dest_mode == 2: val[0:0] = dest_val return val def s_r_replace_mode_separator(self): if self.comma_separated.isChecked(): return ',' return '' def s_r_paint_results(self, txt): self.s_r_error = None self.s_r_set_colors() if self.case_sensitive.isChecked(): flags = 0 else: flags = re.I flags |= re.UNICODE try: stext = unicode(self.search_for.text()) if not stext: raise Exception(_('You must specify a search expression in the "Search for" field')) if self.search_mode.currentIndex() == 0: self.s_r_obj = re.compile(re.escape(stext), flags) else: self.s_r_obj = re.compile(stext, flags) except Exception as e: self.s_r_obj = None self.s_r_error = e self.s_r_set_colors() return try: self.test_result.setText(self.s_r_obj.sub(self.s_r_func, unicode(self.test_text.text()))) except Exception as e: self.s_r_error = e self.s_r_set_colors() return for i in range(0,self.s_r_number_of_books): mi = self.db.get_metadata(self.ids[i], index_is_id=True) wr = getattr(self, 'book_%d_result'%(i+1)) try: result = self.s_r_do_regexp(mi) t = self.s_r_do_destination(mi, result) if len(t) > 1 and self.destination_field_fm['is_multiple']: t = t[self.starting_from.value()-1: self.starting_from.value()-1 + self.results_count.value()] t = unicode(self.multiple_separator.text()).join(t) else: t = self.s_r_replace_mode_separator().join(t) wr.setText(t) except Exception as e: self.s_r_error = e self.s_r_set_colors() break def do_search_replace(self, book_id): source = self.s_r_sf_itemdata(None) if not source or not self.s_r_obj: return dest = self.s_r_df_itemdata(None) if not dest: dest = source dfm = self.db.field_metadata[dest] mi = self.db.new_api.get_proxy_metadata(book_id) val = self.s_r_do_regexp(mi) val = self.s_r_do_destination(mi, val) if dfm['is_multiple']: if dfm['is_csp']: # convert the colon-separated pair strings back into a dict, # which is what set_identifiers wants dst_id_type = unicode(self.s_r_dst_ident.text()) if dst_id_type and dst_id_type != '*': v = ''.join(val) ids = mi.get(dest) ids[dst_id_type] = v val = ids else: try: val = dict([(t.split(':')) for t in val]) except: raise Exception(_('Invalid identifier string. It must be a ' 'comma-separated list of pairs of ' 'strings separated by a colon')) else: val = self.s_r_replace_mode_separator().join(val) if dest == 'title' and len(val) == 0: val = _('Unknown') self.set_field_calls[dest][book_id] = val # }}} def create_custom_column_editors(self): w = self.central_widget.widget(1) layout = QGridLayout() self.custom_column_widgets, self.__cc_spacers = \ populate_metadata_page(layout, self.db, self.ids, parent=w, two_column=False, bulk=True) w.setLayout(layout) self.__custom_col_layouts = [layout] ans = self.custom_column_widgets for i in range(len(ans)-1): w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1]) for c in range(2, len(ans[i].widgets), 2): w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1]) def initialize_combos(self): self.initalize_authors() self.initialize_series() self.initialize_publisher() for x in ('authors', 'publisher', 'series'): x = getattr(self, x) x.setSizeAdjustPolicy(x.AdjustToMinimumContentsLengthWithIcon) x.setMinimumContentsLength(25) def initalize_authors(self): all_authors = self.db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) self.authors.set_separator('&') self.authors.set_space_before_sep(True) self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.update_items_cache(self.db.all_author_names()) self.authors.show_initial_value('') def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series]) self.series.show_initial_value('') def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers]) self.publisher.show_initial_value('') def tag_editor(self, *args): d = TagEditor(self, self.db, None) d.exec_() if d.result() == QDialog.Accepted: tag_string = ', '.join(d.tags) self.tags.setText(tag_string) self.tags.update_items_cache(self.db.all_tags()) self.remove_tags.update_items_cache(self.db.all_tags()) def auto_number_changed(self, state): if state: self.series_numbering_restarts.setEnabled(True) self.series_start_number.setEnabled(True) else: self.series_numbering_restarts.setEnabled(False) self.series_numbering_restarts.setChecked(False) self.series_start_number.setEnabled(False) self.series_start_number.setValue(1) def reject(self): self.save_state() ResizableDialog.reject(self) def accept(self): self.save_state() if len(self.ids) < 1: return QDialog.accept(self) try: source = self.s_r_sf_itemdata(None) except: source = '' do_sr = source and self.s_r_obj if self.s_r_error is not None and do_sr: error_dialog(self, _('Search/replace invalid'), _('Search pattern is invalid: %s')%self.s_r_error.message, show=True) return False self.changed = bool(self.ids) # Cache values from GUI so that Qt widgets are not used in # non GUI thread for w in getattr(self, 'custom_column_widgets', []): w.gui_val remove_all = self.remove_all_tags.isChecked() remove = [] if not remove_all: remove = unicode(self.remove_tags.text()).strip().split(',') add = unicode(self.tags.text()).strip().split(',') au = unicode(self.authors.text()) aus = unicode(self.author_sort.text()) do_aus = self.author_sort.isEnabled() rating = self.rating.value() pub = unicode(self.publisher.text()) do_series = self.write_series clear_series = self.clear_series.isChecked() clear_pub = self.clear_pub.isChecked() series = unicode(self.series.currentText()).strip() do_autonumber = self.autonumber_series.isChecked() do_series_restart = self.series_numbering_restarts.isChecked() series_start_value = self.series_start_number.value() do_remove_format = self.remove_format.currentIndex() > -1 remove_format = unicode(self.remove_format.currentText()) do_swap_ta = self.swap_title_and_author.isChecked() do_remove_conv = self.remove_conversion_settings.isChecked() do_auto_author = self.auto_author_sort.isChecked() do_title_case = self.change_title_to_title_case.isChecked() do_title_sort = self.update_title_sort.isChecked() clear_languages = self.clear_languages.isChecked() restore_original = self.restore_original.isChecked() languages = self.languages.lang_codes pubdate = adddate = None if self.apply_pubdate.isChecked(): pubdate = qt_to_dt(self.pubdate.dateTime()) if self.apply_adddate.isChecked(): adddate = qt_to_dt(self.adddate.dateTime()) cover_action = None if self.cover_remove.isChecked(): cover_action = 'remove' elif self.cover_generate.isChecked(): cover_action = 'generate' elif self.cover_from_fmt.isChecked(): cover_action = 'fromfmt' elif self.cover_trim.isChecked(): cover_action = 'trim' elif self.cover_clone.isChecked(): cover_action = 'clone' args = Settings(remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, do_autonumber, do_remove_format, remove_format, do_swap_ta, do_remove_conv, do_auto_author, series, do_series_restart, series_start_value, do_title_case, cover_action, clear_series, clear_pub, pubdate, adddate, do_title_sort, languages, clear_languages, restore_original, self.comments) self.set_field_calls = defaultdict(dict) bb = MyBlockingBusy(args, self.ids, self.db, self.refresh_books, getattr(self, 'custom_column_widgets', []), self.do_search_replace, do_sr, self.set_field_calls, parent=self) # The metadata backup thread causes database commits # which can slow down bulk editing of large numbers of books self.model.stop_metadata_backup() try: bb.exec_() finally: self.model.start_metadata_backup() bb.thread = bb.db = bb.cc_widgets = None if bb.error is not None: return error_dialog(self, _('Failed'), bb.error[0], det_msg=bb.error[1], show=True) dynamic['s_r_search_mode'] = self.search_mode.currentIndex() self.db.clean() return QDialog.accept(self) def series_changed(self, *args): self.write_series = bool(unicode(self.series.currentText()).strip()) self.autonumber_series.setEnabled(True) def s_r_remove_query(self, *args): if self.query_field.currentIndex() == 0: return if not question_dialog(self, _("Delete saved search/replace"), _("The selected saved search/replace will be deleted. " "Are you sure?")): return item_id = self.query_field.currentIndex() item_name = unicode(self.query_field.currentText()) self.query_field.blockSignals(True) self.query_field.removeItem(item_id) self.query_field.blockSignals(False) self.query_field.setCurrentIndex(0) if item_name in self.queries.keys(): del(self.queries[item_name]) self.queries.commit() def s_r_save_query(self, *args): names = [''] names.extend(self.query_field_values) try: dex = names.index(self.saved_search_name) except: dex = 0 name = '' while not name: name, ok = QInputDialog.getItem(self, _('Save search/replace'), _('Search/replace name:'), names, dex, True) if not ok: return if not name: error_dialog(self, _("Save search/replace"), _("You must provide a name."), show=True) new = True name = unicode(name) if name in self.queries.keys(): if not question_dialog(self, _("Save search/replace"), _("That saved search/replace already exists and will be overwritten. " "Are you sure?")): return new = False query = {} query['name'] = name query['search_field'] = unicode(self.search_field.currentText()) query['search_mode'] = unicode(self.search_mode.currentText()) query['s_r_template'] = unicode(self.s_r_template.text()) query['s_r_src_ident'] = unicode(self.s_r_src_ident.currentText()) query['search_for'] = unicode(self.search_for.text()) query['case_sensitive'] = self.case_sensitive.isChecked() query['replace_with'] = unicode(self.replace_with.text()) query['replace_func'] = unicode(self.replace_func.currentText()) query['destination_field'] = unicode(self.destination_field.currentText()) query['s_r_dst_ident'] = unicode(self.s_r_dst_ident.text()) query['replace_mode'] = unicode(self.replace_mode.currentText()) query['comma_separated'] = self.comma_separated.isChecked() query['results_count'] = self.results_count.value() query['starting_from'] = self.starting_from.value() query['multiple_separator'] = unicode(self.multiple_separator.text()) self.queries[name] = query self.queries.commit() if new: self.query_field.blockSignals(True) self.query_field.clear() self.query_field.addItem('') self.query_field_values = sorted([q for q in self.queries], key=sort_key) self.query_field.addItems(self.query_field_values) self.query_field.blockSignals(False) self.query_field.setCurrentIndex(self.query_field.findText(name)) def s_r_query_change(self, item_name): if not item_name: self.s_r_reset_query_fields() self.saved_search_name = '' return item = self.queries.get(unicode(item_name), None) if item is None: self.s_r_reset_query_fields() return self.saved_search_name = item_name def set_text(attr, key): try: attr.setText(item[key]) except: pass def set_checked(attr, key): try: attr.setChecked(item[key]) except: attr.setChecked(False) def set_value(attr, key): try: attr.setValue(int(item[key])) except: attr.setValue(0) def set_index(attr, key): try: attr.setCurrentIndex(attr.findText(item[key])) except: attr.setCurrentIndex(0) set_index(self.search_mode, 'search_mode') set_index(self.search_field, 'search_field') set_text(self.s_r_template, 's_r_template') self.s_r_template_changed() # simulate gain/loss of focus set_index(self.s_r_src_ident, 's_r_src_ident') set_text(self.s_r_dst_ident, 's_r_dst_ident') set_text(self.search_for, 'search_for') set_checked(self.case_sensitive, 'case_sensitive') set_text(self.replace_with, 'replace_with') set_index(self.replace_func, 'replace_func') set_index(self.destination_field, 'destination_field') set_index(self.replace_mode, 'replace_mode') set_checked(self.comma_separated, 'comma_separated') set_value(self.results_count, 'results_count') set_value(self.starting_from, 'starting_from') set_text(self.multiple_separator, 'multiple_separator') def s_r_reset_query_fields(self): # Don't reset the search mode. The user will probably want to use it # as it was self.search_field.setCurrentIndex(0) self.s_r_src_ident.setCurrentIndex(0) self.s_r_template.setText("") self.search_for.setText("") self.case_sensitive.setChecked(False) self.replace_with.setText("") self.replace_func.setCurrentIndex(0) self.destination_field.setCurrentIndex(0) self.s_r_dst_ident.setText('') self.replace_mode.setCurrentIndex(0) self.comma_separated.setChecked(True) self.results_count.setValue(999) self.starting_from.setValue(1) self.multiple_separator.setText(" ::: ")
def test(): # {{{ from calibre import prints # Data {{{ german = ''' Sonntag Montag Dienstag Januar Februar März Fuße Fluße Flusse flusse fluße flüße flüsse ''' german_good = ''' Dienstag Februar flusse Flusse fluße Fluße flüsse flüße Fuße Januar März Montag Sonntag''' french = ''' dimanche lundi mardi janvier février mars déjà Meme deja même dejà bpef bœg Boef Mémé bœf boef bnef pêche pèché pêché pêche pêché''' french_good = ''' bnef boef Boef bœf bœg bpef deja dejà déjà dimanche février janvier lundi mardi mars Meme Mémé même pèché pêche pêche pêché pêché''' # }}} def create(l): l = l.decode('utf-8').splitlines() return [x.strip() for x in l if x.strip()] def test_strcmp(entries): for x in entries: for y in entries: if strcmp(x, y) != cmp(sort_key(x), sort_key(y)): print 'strcmp failed for %r, %r' % (x, y) german = create(german) c = _icu.Collator('de') c.numeric = True gs = list(sorted(german, key=c.sort_key)) if gs != create(german_good): print 'German sorting failed' return print french = create(french) c = _icu.Collator('fr') c.numeric = True fs = list(sorted(french, key=c.sort_key)) if fs != create(french_good): print 'French sorting failed (note that French fails with icu < 4.6)' return test_strcmp(german + french) print '\nTesting case transforms in current locale' from calibre.utils.titlecase import titlecase for x in ('a', 'Alice\'s code', 'macdonald\'s machine', '02 the wars'): print 'Upper: ', x, '->', 'py:', x.upper().encode( 'utf-8'), 'icu:', upper(x).encode('utf-8') print 'Lower: ', x, '->', 'py:', x.lower().encode( 'utf-8'), 'icu:', lower(x).encode('utf-8') print 'Title: ', x, '->', 'py:', x.title().encode( 'utf-8'), 'icu:', title_case(x).encode( 'utf-8'), 'titlecase:', titlecase(x).encode('utf-8') print 'Capitalize:', x, '->', 'py:', x.capitalize().encode( 'utf-8'), 'icu:', capitalize(x).encode('utf-8') print print '\nTesting primary collation' for k, v in { u'pèché': u'peche', u'flüße': u'Flusse', u'Štepánek': u'ŠtepaneK' }.iteritems(): if primary_strcmp(k, v) != 0: prints('primary_strcmp() failed with %s != %s' % (k, v)) return if primary_find(v, u' ' + k)[0] != 1: prints('primary_find() failed with %s not in %s' % (v, k)) return global _primary_collator orig = _primary_collator _primary_collator = _icu.Collator('es') if primary_strcmp(u'peña', u'pena') == 0: print 'Primary collation in Spanish locale failed' return _primary_collator = orig print '\nTesting contractions' c = _icu.Collator('cs') if icu_contractions(c) != frozenset([ u'Z\u030c', u'z\u030c', u'Ch', u'C\u030c', u'ch', u'cH', u'c\u030c', u's\u030c', u'r\u030c', u'CH', u'S\u030c', u'R\u030c' ]): print 'Contractions for the Czech language failed' return print '\nTesting startswith' p = primary_startswith if (not p('asd', 'asd') or not p('asd', 'A') or not p('x', '')): print 'startswith() failed' return print '\nTesting collation_order()' for group in [ ('Šaa', 'Smith', 'Solženicyn', 'Štepánek'), ('calibre', 'Charon', 'Collins'), ('01', '1'), ('1', '11', '13'), ]: last = None for x in group: val = icu_collation_order(sort_collator(), x) if val[1] != 1: prints('collation_order() returned incorrect length for', x) if last is None: last = val else: if val != last: prints('collation_order() returned incorrect value for', x) last = val
def evaluate(self, formatter, kwargs, mi, locals, val): return titlecase(val)
def title_case(self): from calibre.utils.titlecase import titlecase self.setText(titlecase(str(self.text())))
def do_all(self): cache = self.db.new_api args = self.args # Title and authors if args.do_swap_ta: title_map = cache.all_field_for('title', self.ids) authors_map = cache.all_field_for('authors', self.ids) def new_title(authors): ans = authors_to_string(authors) return titlecase(ans) if args.do_title_case else ans new_title_map = {bid:new_title(authors) for bid, authors in authors_map.iteritems()} new_authors_map = {bid:string_to_authors(title) for bid, title in title_map.iteritems()} cache.set_field('authors', new_authors_map) cache.set_field('title', new_title_map) if args.do_title_case and not args.do_swap_ta: title_map = cache.all_field_for('title', self.ids) cache.set_field('title', {bid:titlecase(title) for bid, title in title_map.iteritems()}) if args.do_title_sort: lang_map = cache.all_field_for('languages', self.ids) title_map = cache.all_field_for('title', self.ids) def get_sort(book_id): if args.languages: lang = args.languages[0] else: try: lang = lang_map[book_id][0] except (KeyError, IndexError, TypeError, AttributeError): lang = 'eng' return title_sort(title_map[book_id], lang=lang) cache.set_field('sort', {bid:get_sort(bid) for bid in self.ids}) if args.au: authors = string_to_authors(args.au) cache.set_field('authors', {bid:authors for bid in self.ids}) if args.do_auto_author: aus_map = cache.author_sort_strings_for_books(self.ids) cache.set_field('author_sort', {book_id:' & '.join(aus_map[book_id]) for book_id in aus_map}) if args.aus and args.do_aus: cache.set_field('author_sort', {bid:args.aus for bid in self.ids}) # Covers if args.cover_action == 'remove': cache.set_cover({bid:None for bid in self.ids}) elif args.cover_action == 'generate': from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx from calibre.gui2 import config for book_id in self.ids: mi = self.db.get_metadata(book_id, index_is_id=True) series_string = None if mi.series: series_string = _('Book %(sidx)s of %(series)s')%dict( sidx=fmt_sidx(mi.series_index, use_roman=config['use_roman_numerals_for_series_number']), series=mi.series) cdata = calibre_cover(mi.title, mi.format_field('authors')[-1], series_string=series_string) cache.set_cover({book_id:cdata}) elif args.cover_action == 'fromfmt': for book_id in self.ids: fmts = cache.formats(book_id, verify_formats=False) if fmts: covers = [] for fmt in fmts: fmtf = cache.format(book_id, fmt, as_file=True) if fmtf is None: continue cdata, area = get_cover_data(fmtf, fmt) if cdata: covers.append((cdata, area)) covers.sort(key=lambda x: x[1]) if covers: cache.set_cover({book_id:covers[-1][0]}) elif args.cover_action == 'trim': from calibre.utils.magick import Image for book_id in self.ids: cdata = cache.cover(book_id) if cdata: im = Image() im.load(cdata) im.trim(tweaks['cover_trim_fuzz_value']) cdata = im.export('jpg') cache.set_cover({book_id:cdata}) elif args.cover_action == 'clone': cdata = None for book_id in self.ids: cdata = cache.cover(book_id) if cdata: break if cdata: cache.set_cover({bid:cdata for bid in self.ids if bid != book_id}) # Formats if args.do_remove_format: cache.remove_formats({bid:(args.remove_format,) for bid in self.ids}) if args.restore_original: for book_id in self.ids: formats = cache.formats(book_id) originals = tuple(x.upper() for x in formats if x.upper().startswith('ORIGINAL_')) for ofmt in originals: cache.restore_original_format(book_id, ofmt) # Various fields if args.rating != -1: cache.set_field('rating', {bid:args.rating*2 for bid in self.ids}) if args.clear_pub: cache.set_field('publisher', {bid:'' for bid in self.ids}) if args.pub: cache.set_field('publisher', {bid:args.pub for bid in self.ids}) if args.clear_series: cache.set_field('series', {bid:'' for bid in self.ids}) if args.pubdate is not None: cache.set_field('pubdate', {bid:args.pubdate for bid in self.ids}) if args.adddate is not None: cache.set_field('timestamp', {bid:args.adddate for bid in self.ids}) if args.do_series: sval = args.series_start_value if args.do_series_restart else cache.get_next_series_num_for(args.series, current_indices=True) cache.set_field('series', {bid:args.series for bid in self.ids}) if not args.series: cache.set_field('series_index', {bid:1.0 for bid in self.ids}) else: def next_series_num(bid, i): if args.do_series_restart: return sval + i next_num = _get_next_series_num_for_list(sorted(sval.itervalues()), unwrap=False) sval[bid] = next_num return next_num smap = {bid:next_series_num(bid, i) for i, bid in enumerate(self.ids)} if args.do_autonumber: cache.set_field('series_index', smap) elif tweaks['series_index_auto_increment'] != 'no_change': cache.set_field('series_index', {bid:1.0 for bid in self.ids}) if args.comments is not null: cache.set_field('comments', {bid:args.comments for bid in self.ids}) if args.do_remove_conv: cache.delete_conversion_options(self.ids) if args.clear_languages: cache.set_field('languages', {bid:() for bid in self.ids}) elif args.languages: cache.set_field('languages', {bid:args.languages for bid in self.ids}) if args.remove_all: cache.set_field('tags', {bid:() for bid in self.ids}) if args.add or args.remove: self.db.bulk_modify_tags(self.ids, add=args.add, remove=args.remove) if self.do_sr: for book_id in self.ids: self.s_r_func(book_id) if self.sr_calls: for field, book_id_val_map in self.sr_calls.iteritems(): self.refresh_books.update(self.db.new_api.set_field(field, book_id_val_map))
def new_title(authors): ans = authors_to_string(authors) return titlecase(ans) if args.do_title_case else ans
def test(): # {{{ from calibre import prints # Data {{{ german = ''' Sonntag Montag Dienstag Januar Februar März Fuße Fluße Flusse flusse fluße flüße flüsse ''' german_good = ''' Dienstag Februar flusse Flusse fluße Fluße flüsse flüße Fuße Januar März Montag Sonntag''' french = ''' dimanche lundi mardi janvier février mars déjà Meme deja même dejà bpef bœg Boef Mémé bœf boef bnef pêche pèché pêché pêche pêché''' french_good = ''' bnef boef Boef bœf bœg bpef deja dejà déjà dimanche février janvier lundi mardi mars Meme Mémé même pèché pêche pêche pêché pêché''' # }}} def create(l): l = l.decode('utf-8').splitlines() return [x.strip() for x in l if x.strip()] def test_strcmp(entries): for x in entries: for y in entries: if strcmp(x, y) != cmp(sort_key(x), sort_key(y)): print 'strcmp failed for %r, %r'%(x, y) german = create(german) c = _icu.Collator('de') c.numeric = True gs = list(sorted(german, key=c.sort_key)) if gs != create(german_good): print 'German sorting failed' return print french = create(french) c = _icu.Collator('fr') c.numeric = True fs = list(sorted(french, key=c.sort_key)) if fs != create(french_good): print 'French sorting failed (note that French fails with icu < 4.6)' return test_strcmp(german + french) print '\nTesting case transforms in current locale' from calibre.utils.titlecase import titlecase for x in ('a', 'Alice\'s code', 'macdonald\'s machine', '02 the wars'): print 'Upper: ', x, '->', 'py:', x.upper().encode('utf-8'), 'icu:', upper(x).encode('utf-8') print 'Lower: ', x, '->', 'py:', x.lower().encode('utf-8'), 'icu:', lower(x).encode('utf-8') print 'Title: ', x, '->', 'py:', x.title().encode('utf-8'), 'icu:', title_case(x).encode('utf-8'), 'titlecase:', titlecase(x).encode('utf-8') print 'Capitalize:', x, '->', 'py:', x.capitalize().encode('utf-8'), 'icu:', capitalize(x).encode('utf-8') print print '\nTesting primary collation' for k, v in {u'pèché': u'peche', u'flüße':u'Flusse', u'Štepánek':u'ŠtepaneK'}.iteritems(): if primary_strcmp(k, v) != 0: prints('primary_strcmp() failed with %s != %s'%(k, v)) return if primary_find(v, u' '+k)[0] != 1: prints('primary_find() failed with %s not in %s'%(v, k)) return n = character_name(safe_chr(0x1f431)) if n != u'CAT FACE': raise ValueError('Failed to get correct character name for 0x1f431: %r != %r' % n, u'CAT FACE') global _primary_collator orig = _primary_collator _primary_collator = _icu.Collator('es') if primary_strcmp(u'peña', u'pena') == 0: print 'Primary collation in Spanish locale failed' return _primary_collator = orig print '\nTesting contractions' c = _icu.Collator('cs') if icu_contractions(c) != frozenset([u'Z\u030c', u'z\u030c', u'Ch', u'C\u030c', u'ch', u'cH', u'c\u030c', u's\u030c', u'r\u030c', u'CH', u'S\u030c', u'R\u030c']): print 'Contractions for the Czech language failed' return print '\nTesting startswith' p = primary_startswith if (not p('asd', 'asd') or not p('asd', 'A') or not p('x', '')): print 'startswith() failed' return print '\nTesting collation_order()' for group in [ ('Šaa', 'Smith', 'Solženicyn', 'Štepánek'), ('calibre', 'Charon', 'Collins'), ('01', '1'), ('1', '11', '13'), ]: last = None for x in group: val = icu_collation_order(sort_collator(), x) if val[1] != 1: prints('collation_order() returned incorrect length for', x) if last is None: last = val else: if val != last: prints('collation_order() returned incorrect value for', x) last = val
def fixcase(x): if x: x = titlecase(x) return x
def title_case(self): self.push_history() from calibre.utils.titlecase import titlecase for item in self.selectedItems(): t = unicode_type(item.data(0, Qt.ItemDataRole.DisplayRole) or '') item.setData(0, Qt.ItemDataRole.DisplayRole, titlecase(t))
def test(): # {{{ from calibre import prints # Data {{{ german = """ Sonntag Montag Dienstag Januar Februar März Fuße Fluße Flusse flusse fluße flüße flüsse """ german_good = """ Dienstag Februar flusse Flusse fluße Fluße flüsse flüße Fuße Januar März Montag Sonntag""" french = """ dimanche lundi mardi janvier février mars déjà Meme deja même dejà bpef bœg Boef Mémé bœf boef bnef pêche pèché pêché pêche pêché""" french_good = """ bnef boef Boef bœf bœg bpef deja dejà déjà dimanche février janvier lundi mardi mars Meme Mémé même pèché pêche pêche pêché pêché""" # }}} def create(l): l = l.decode("utf-8").splitlines() return [x.strip() for x in l if x.strip()] def test_strcmp(entries): for x in entries: for y in entries: if strcmp(x, y) != cmp(sort_key(x), sort_key(y)): print "strcmp failed for %r, %r" % (x, y) german = create(german) c = _icu.Collator("de") c.numeric = True gs = list(sorted(german, key=c.sort_key)) if gs != create(german_good): print "German sorting failed" return print french = create(french) c = _icu.Collator("fr") c.numeric = True fs = list(sorted(french, key=c.sort_key)) if fs != create(french_good): print "French sorting failed (note that French fails with icu < 4.6)" return test_strcmp(german + french) print "\nTesting case transforms in current locale" from calibre.utils.titlecase import titlecase for x in ("a", "Alice's code", "macdonald's machine", "02 the wars"): print "Upper: ", x, "->", "py:", x.upper().encode("utf-8"), "icu:", upper(x).encode("utf-8") print "Lower: ", x, "->", "py:", x.lower().encode("utf-8"), "icu:", lower(x).encode("utf-8") print "Title: ", x, "->", "py:", x.title().encode("utf-8"), "icu:", title_case(x).encode( "utf-8" ), "titlecase:", titlecase(x).encode("utf-8") print "Capitalize:", x, "->", "py:", x.capitalize().encode("utf-8"), "icu:", capitalize(x).encode("utf-8") print print "\nTesting primary collation" for k, v in {u"pèché": u"peche", u"flüße": u"Flusse", u"Štepánek": u"ŠtepaneK"}.iteritems(): if primary_strcmp(k, v) != 0: prints("primary_strcmp() failed with %s != %s" % (k, v)) return if primary_find(v, u" " + k)[0] != 1: prints("primary_find() failed with %s not in %s" % (v, k)) return n = character_name(safe_chr(0x1F431)) if n != u"CAT FACE": raise ValueError("Failed to get correct character name for 0x1f431: %r != %r" % n, u"CAT FACE") global _primary_collator orig = _primary_collator _primary_collator = _icu.Collator("es") if primary_strcmp(u"peña", u"pena") == 0: print "Primary collation in Spanish locale failed" return _primary_collator = orig print "\nTesting contractions" c = _icu.Collator("cs") if icu_contractions(c) != frozenset( [ u"Z\u030c", u"z\u030c", u"Ch", u"C\u030c", u"ch", u"cH", u"c\u030c", u"s\u030c", u"r\u030c", u"CH", u"S\u030c", u"R\u030c", ] ): print "Contractions for the Czech language failed" return print "\nTesting startswith" p = primary_startswith if not p("asd", "asd") or not p("asd", "A") or not p("x", ""): print "startswith() failed" return print "\nTesting collation_order()" for group in [ ("Šaa", "Smith", "Solženicyn", "Štepánek"), ("calibre", "Charon", "Collins"), ("01", "1"), ("1", "11", "13"), ]: last = None for x in group: val = icu_collation_order(sort_collator(), x) if val[1] != 1: prints("collation_order() returned incorrect length for", x) if last is None: last = val else: if val != last: prints("collation_order() returned incorrect value for", x) last = val