Example #1
0
    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())
Example #2
0
    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())
Example #3
0
    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())
Example #4
0
 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))
Example #5
0
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
Example #6
0
    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()
Example #7
0
 def title_case(self):
     from calibre.utils.titlecase import titlecase
     self.context_item.setText(titlecase(unicode(self.context_item.text())))
Example #8
0
 def title_case(self):
     from calibre.utils.titlecase import titlecase
     self.setText(titlecase(unicode_type(self.text())))
Example #9
0
def fixcase(x):
    if x:
        from calibre.utils.titlecase import titlecase
        x = titlecase(x)
    return x
Example #10
0
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(" ::: ")
Example #11
0
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
Example #12
0
 def evaluate(self, formatter, kwargs, mi, locals, val):
     return titlecase(val)
Example #13
0
 def title_case(self):
     from calibre.utils.titlecase import titlecase
     self.setText(titlecase(str(self.text())))
Example #14
0
 def evaluate(self, formatter, kwargs, mi, locals, val):
     return titlecase(val)
Example #15
0
    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))
Example #16
0
 def new_title(authors):
     ans = authors_to_string(authors)
     return titlecase(ans) if args.do_title_case else ans
Example #17
0
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
Example #18
0
def fixcase(x):
    if x:
        from calibre.utils.titlecase import titlecase
        x = titlecase(x)
    return x
Example #19
0
def fixcase(x):
    if x:
        x = titlecase(x)
    return x
Example #20
0
 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))
Example #21
0
 def title_case(self):
     from calibre.utils.titlecase import titlecase
     self.setText(titlecase(unicode_type(self.text())))
Example #22
0
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
Example #23
0
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
Example #24
0
 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))
Example #25
0
def fixcase(x):
    if x:
        x = titlecase(x)
    return x