diff --git a/resources/recipes/new_yorker.recipe b/resources/recipes/new_yorker.recipe index 605b34e79b..1a2091cd52 100644 --- a/resources/recipes/new_yorker.recipe +++ b/resources/recipes/new_yorker.recipe @@ -32,15 +32,15 @@ class NewYorker(BasicNewsRecipe): , 'publisher' : publisher , 'language' : language } - + keep_only_tags = [ dict(name='div', attrs={'class':'headers'}) ,dict(name='div', attrs={'id':['articleheads','items-container','articleRail','articletext','photocredits']}) ] remove_tags = [ dict(name=['meta','iframe','base','link','embed','object']) - ,dict(attrs={'class':['utils','articleRailLinks','icons'] }) - ,dict(attrs={'id':['show-header','show-footer'] }) + ,dict(attrs={'class':['utils','articleRailLinks','icons'] }) + ,dict(attrs={'id':['show-header','show-footer'] }) ] remove_attributes = ['lang'] feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')] @@ -58,4 +58,4 @@ class NewYorker(BasicNewsRecipe): if cover_item: cover_url = 'http://www.newyorker.com' + cover_item['src'].strip() return cover_url - \ No newline at end of file + diff --git a/resources/recipes/rusiahoy.recipe b/resources/recipes/rusiahoy.recipe index 326c1695b0..8dfa55eece 100644 --- a/resources/recipes/rusiahoy.recipe +++ b/resources/recipes/rusiahoy.recipe @@ -19,7 +19,7 @@ class RusiaHoy(BasicNewsRecipe): use_embedded_content = False language = 'es' remove_empty_feeds = True - extra_css = """ + extra_css = """ body{font-family: Arial,sans-serif } .article_article_title{font-size: xx-large; font-weight: bold} .article_date{color: black; font-size: small} @@ -44,4 +44,4 @@ class RusiaHoy(BasicNewsRecipe): for item in soup.findAll(style=True): del item['style'] return soup - \ No newline at end of file + diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 3103d7c459..053dd7a743 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -437,7 +437,7 @@ class BulkBool(BulkBase, Bool): if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: val = False if value is not None and value != val: - return None + return 'nochange' value = val return value @@ -445,19 +445,23 @@ class BulkBool(BulkBase, Bool): self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), QComboBox(parent)] w = self.widgets[1] - items = [_('Yes'), _('No'), _('Undefined')] - icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] + items = [_('Yes'), _('No'), _('Undefined'), _('Do not change')] + icons = [I('ok.png'), I('list_remove.png'), I('blank.png'), I('blank.png')] for icon, text in zip(icons, items): w.addItem(QIcon(icon), text) + def getter(self): + val = self.widgets[1].currentIndex() + return {3: 'nochange', 2: None, 1: False, 0: True}[val] + def setter(self, val): - val = {None: 2, False: 1, True: 0}[val] + val = {'nochange': 3, None: 2, False: 1, True: 0}[val] self.widgets[1].setCurrentIndex(val) def commit(self, book_ids, notify=False): val = self.gui_val val = self.normalize_ui_val(val) - if val != self.initial_val: + if val != self.initial_val and val != 'nochange': if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: val = False self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 075fbe664a..6148019906 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -403,7 +403,7 @@ class ResultCache(SearchQueryParser): # {{{ '<=':[2, lambda r, q: r <= q] } - def get_numeric_matches(self, location, query): + def get_numeric_matches(self, location, query, val_func = None): matches = set([]) if len(query) == 0: return matches @@ -419,7 +419,10 @@ class ResultCache(SearchQueryParser): # {{{ if relop is None: (p, relop) = self.numeric_search_relops['='] - loc = self.field_metadata[location]['rec_index'] + if val_func is None: + loc = self.field_metadata[location]['rec_index'] + val_func = lambda item, loc=loc: item[loc] + dt = self.field_metadata[location]['datatype'] if dt == 'int': cast = (lambda x: int (x)) @@ -430,6 +433,9 @@ class ResultCache(SearchQueryParser): # {{{ elif dt == 'float': cast = lambda x : float (x) adjust = lambda x: x + else: # count operation + cast = (lambda x: int (x)) + adjust = lambda x: x if len(query) > 1: mult = query[-1:].lower() @@ -446,10 +452,11 @@ class ResultCache(SearchQueryParser): # {{{ for item in self._data: if item is None: continue - if not item[loc]: + v = val_func(item) + if not v: i = 0 else: - i = adjust(item[loc]) + i = adjust(v) if relop(i, q): matches.add(item[0]) return matches @@ -467,15 +474,23 @@ class ResultCache(SearchQueryParser): # {{{ return matches raise ParseException(query, len(query), 'Recursive query group detected', self) - # take care of dates special case - if location in self.field_metadata and \ - self.field_metadata[location]['datatype'] == 'datetime': - return self.get_dates_matches(location, query.lower()) + if location in self.field_metadata: + fm = self.field_metadata[location] + # take care of dates special case + if fm['datatype'] == 'datetime': + return self.get_dates_matches(location, query.lower()) - # take care of numbers special case - if location in self.field_metadata and \ - self.field_metadata[location]['datatype'] in ('rating', 'int', 'float'): - return self.get_numeric_matches(location, query.lower()) + # take care of numbers special case + if fm['datatype'] in ('rating', 'int', 'float'): + return self.get_numeric_matches(location, query.lower()) + + # take care of the 'count' operator for is_multiples + if fm['is_multiple'] and \ + len(query) > 1 and query.startswith('#') and \ + query[1:1] in '=<>!': + vf = lambda item, loc=fm['rec_index'], ms=fm['is_multiple']:\ + len(item[loc].split(ms)) if item[loc] is not None else 0 + return self.get_numeric_matches(location, query[1:], val_func=vf) # everything else, or 'all' matches matchkind = CONTAINS_MATCH diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index ffb97e481f..47c575386b 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -268,8 +268,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): base, prefer_custom=True) - self.field_metadata.set_field_record_index('cover', - self.FIELD_MAP['cover'], prefer_custom=False) self.FIELD_MAP['ondevice'] = base+1 self.field_metadata.set_field_record_index('ondevice', base+1, prefer_custom=False) self.FIELD_MAP['all_metadata'] = base+2 diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index dbc871026e..d10dc5da71 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -3,6 +3,7 @@ Created on 25 May 2010 @author: charles ''' +import copy from calibre.utils.ordered_dict import OrderedDict from calibre.utils.config import tweaks @@ -86,7 +87,7 @@ class FieldMetadata(dict): # Builtin metadata {{{ - _field_metadata = [ + _field_metadata_prototype = [ ('authors', {'table':'authors', 'column':'name', 'link_column':'author', @@ -161,6 +162,15 @@ class FieldMetadata(dict): 'search_terms':['tags', 'tag'], 'is_custom':False, 'is_category':True}), + ('all_metadata',{'table':None, + 'column':None, + 'datatype':None, + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), ('author_sort',{'table':None, 'column':None, 'datatype':'text', @@ -180,7 +190,7 @@ class FieldMetadata(dict): 'is_custom':False, 'is_category':False}), ('cover', {'table':None, 'column':None, - 'datatype':None, + 'datatype':'int', 'is_multiple':None, 'kind':'field', 'name':None, @@ -223,15 +233,6 @@ class FieldMetadata(dict): 'search_terms':[], 'is_custom':False, 'is_category':False}), - ('all_metadata',{'table':None, - 'column':None, - 'datatype':None, - 'is_multiple':None, - 'kind':'field', - 'name':None, - 'search_terms':[], - 'is_custom':False, - 'is_category':False}), ('ondevice', {'table':None, 'column':None, 'datatype':'text', @@ -322,6 +323,7 @@ class FieldMetadata(dict): ] def __init__(self): + self._field_metadata = copy.deepcopy(self._field_metadata_prototype) self._tb_cats = OrderedDict() self._search_term_map = {} self.custom_label_to_key_map = {} diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index c81688ba8c..a91124b214 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -274,6 +274,14 @@ Searching for ``no`` or ``unchecked`` will find all books with ``No`` in the col :guilabel:`Advanced Search Dialog` +You can test for the number of items in multiple-value columns, such as tags, formats, authors, and tags-like custom columns. This is done using a syntax very similar to numeric tests (discussed above), except that the relational operator begins with a ``#`` character. For example:: + + tags:#>3 will give you books with more than three tags + tags:#!=3 will give you books that do not have three tags + authors:#=1 will give you books with exactly one author + #cust:#<5 will give you books with less than five items in custom column #cust + formats:#>1 will give you books with more than one format + Saving searches -----------------