Fix various bugs in column coloring. Fix the NOT template function. Migrate is_multiple to a dict

This commit is contained in:
Kovid Goyal 2011-06-04 11:48:39 -06:00
commit 06de23b162
23 changed files with 227 additions and 116 deletions

View File

@ -204,7 +204,8 @@ class CollectionsBookList(BookList):
elif fm['datatype'] == 'text' and fm['is_multiple']:
val = orig_val
elif fm['datatype'] == 'composite' and fm['is_multiple']:
val = [v.strip() for v in val.split(fm['is_multiple'])]
val = [v.strip() for v in
val.split(fm['is_multiple']['ui_to_list'])]
else:
val = [val]

View File

@ -621,10 +621,7 @@ class Metadata(object):
orig_res = res
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
if cmeta['display'].get('is_names', False):
res = u' & '.join(res)
else:
res = u', '.join(sorted(res, key=sort_key))
res = cmeta['is_multiple']['list_to_ui'].join(res)
elif datatype == 'series' and series_with_index:
if self.get_extra(key) is not None:
res = res + \
@ -668,7 +665,7 @@ class Metadata(object):
elif datatype == 'text' and fmeta['is_multiple']:
if isinstance(res, dict):
res = [k + ':' + v for k,v in res.items()]
res = u', '.join(sorted(res, key=sort_key))
res = fmeta['is_multiple']['list_to_ui'].join(sorted(res, key=sort_key))
elif datatype == 'series' and series_with_index:
res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
lxml based OPF parser.
'''
import re, sys, unittest, functools, os, uuid, glob, cStringIO, json
import re, sys, unittest, functools, os, uuid, glob, cStringIO, json, copy
from urllib import unquote
from urlparse import urlparse
@ -457,6 +457,14 @@ def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8))
for name, fm in all_user_metadata.items():
try:
if fm.get('is_multiple'):
# migrate is_multiple back to a character
fm = copy.copy(fm)
dt = fm.get('datatype', None)
if dt == 'composite':
fm['is_multiple'] = ','
else:
fm['is_multiple'] = '|'
fm = object_to_unicode(fm)
fm = json.dumps(fm, default=to_json, ensure_ascii=False)
except:
@ -585,6 +593,17 @@ class OPF(object): # {{{
fm = elem.get('content')
try:
fm = json.loads(fm, object_hook=from_json)
im = fm.get('is_multiple', None)
if im and not isinstance(im, dict):
# Must migrate the is_multiple from char to dict
dt = fm.get('datatype', None)
if dt == 'composite':
im = {'cache_to_list': ',', 'ui_to_list': ',', 'list_to_ui': ', '}
elif fm.get('display', {}).get('is_names', False):
im = {'cache_to_list': '|', 'ui_to_list': '&', 'list_to_ui': ', '}
else:
im = {'cache_to_list': '|', 'ui_to_list': ',', 'list_to_ui': ', '}
fm['is_multiple'] = im
temp.set_user_metadata(name, fm)
except:
prints('Failed to read user metadata:', name)

View File

@ -226,16 +226,14 @@ class Comments(Base):
class Text(Base):
def setup_ui(self, parent):
if self.col_metadata['display'].get('is_names', False):
self.sep = u' & '
else:
self.sep = u', '
self.sep = self.col_metadata['multiple_seps']
values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key)
if self.col_metadata['is_multiple']:
w = MultiCompleteLineEdit(parent)
w.set_separator(self.sep.strip())
if self.sep == u' & ':
w.set_separator(self.sep['ui_to_list'])
if self.sep['ui_to_list'] == '&':
w.set_space_before_sep(True)
w.set_add_separator(tweaks['authors_completer_append_separator'])
w.update_items_cache(values)
@ -269,12 +267,12 @@ class Text(Base):
if self.col_metadata['is_multiple']:
if not val:
val = []
self.widgets[1].setText(self.sep.join(val))
self.widgets[1].setText(self.sep['list_to_ui'].join(val))
def getter(self):
if self.col_metadata['is_multiple']:
val = unicode(self.widgets[1].text()).strip()
ans = [x.strip() for x in val.split(self.sep.strip()) if x.strip()]
ans = [x.strip() for x in val.split(self.sep['ui_to_list']) if x.strip()]
if not ans:
ans = None
return ans
@ -899,9 +897,10 @@ class BulkText(BulkBase):
if not self.a_c_checkbox.isChecked():
return
if self.col_metadata['is_multiple']:
ism = self.col_metadata['multiple_seps']
if self.col_metadata['display'].get('is_names', False):
val = self.gui_val
add = [v.strip() for v in val.split('&') if v.strip()]
add = [v.strip() for v in val.split(ism['ui_to_list']) if v.strip()]
self.db.set_custom_bulk(book_ids, add, num=self.col_id)
else:
remove_all, adding, rtext = self.gui_val
@ -911,10 +910,10 @@ class BulkText(BulkBase):
else:
txt = rtext
if txt:
remove = set([v.strip() for v in txt.split(',')])
remove = set([v.strip() for v in txt.split(ism['ui_to_list'])])
txt = adding
if txt:
add = set([v.strip() for v in txt.split(',')])
add = set([v.strip() for v in txt.split(ism['ui_to_list'])])
else:
add = set()
self.db.set_custom_bulk_multiple(book_ids, add=add,

View File

@ -520,7 +520,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
elif not fm['is_multiple']:
val = [val]
elif fm['datatype'] == 'composite':
val = [v.strip() for v in val.split(fm['is_multiple'])]
val = [v.strip() for v in val.split(fm['is_multiple']['ui_to_list'])]
elif field == 'authors':
val = [v.replace('|', ',') for v in val]
else:
@ -655,19 +655,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if self.destination_field_fm['is_multiple']:
if self.comma_separated.isChecked():
if dest == 'authors' or \
(self.destination_field_fm['is_custom'] and
self.destination_field_fm['datatype'] == 'text' and
self.destination_field_fm['display'].get('is_names', False)):
splitter = ' & '
else:
splitter = ','
splitter = self.destination_field_fm['is_multiple']['ui_to_list']
res = []
for v in val:
for x in v.split(splitter):
if x.strip():
res.append(x.strip())
res.extend([x.strip() for x in v.split(splitter) if x.strip()])
val = res
else:
val = [v.replace(',', '') for v in val]

View File

@ -254,6 +254,15 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.textbox_changed()
self.rule = (None, '')
tt = _('Template language tutorial')
self.template_tutorial.setText(
'<a href="http://manual.calibre-ebook.com/template_lang.html">'
'%s</a>'%tt)
tt = _('Template function reference')
self.template_func_reference.setText(
'<a href="http://manual.calibre-ebook.com/template_ref.html">'
'%s</a>'%tt)
def textbox_changed(self):
cur_text = unicode(self.textbox.toPlainText())
if self.last_text != cur_text:
@ -299,4 +308,4 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
return
self.rule = (unicode(self.colored_field.currentText()), txt)
QDialog.accept(self)
QDialog.accept(self)

View File

@ -125,6 +125,20 @@
<item row="9" column="1">
<widget class="QPlainTextEdit" name="source_code"/>
</item>
<item row="10" column="1">
<widget class="QLabel" name="template_tutorial">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="template_func_reference">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -608,10 +608,11 @@ class BooksModel(QAbstractTableModel): # {{{
def text_type(r, mult=None, idx=-1):
text = self.db.data[r][idx]
if text and mult is not None:
if mult:
return QVariant(u' & '.join(text.split('|')))
return QVariant(u', '.join(sorted(text.split('|'),key=sort_key)))
if text and mult:
jv = mult['list_to_ui']
sv = mult['cache_to_list']
return QVariant(jv.join(
sorted([t.strip() for t in text.split(sv)], key=sort_key)))
return QVariant(text)
def decorated_text_type(r, idx=-1):
@ -665,8 +666,6 @@ class BooksModel(QAbstractTableModel): # {{{
datatype = self.custom_columns[col]['datatype']
if datatype in ('text', 'comments', 'composite', 'enumeration'):
mult=self.custom_columns[col]['is_multiple']
if mult is not None:
mult = self.custom_columns[col]['display'].get('is_names', False)
self.dc[col] = functools.partial(text_type, idx=idx, mult=mult)
if datatype in ['text', 'composite', 'enumeration'] and not mult:
if self.custom_columns[col]['display'].get('use_decorations', False):
@ -722,9 +721,9 @@ class BooksModel(QAbstractTableModel): # {{{
if id_ in self.color_cache:
if key in self.color_cache[id_]:
return self.color_cache[id_][key]
if mi is None:
mi = self.db.get_metadata(id_, index_is_id=True)
try:
if mi is None:
mi = self.db.get_metadata(id_, index_is_id=True)
color = composite_formatter.safe_format(fmt, mi, '', mi)
if color in self.colors:
color = QColor(color)

View File

@ -192,6 +192,8 @@ class ConditionEditor(QWidget): # {{{
action = self.current_action
if not action:
return
m = self.fm[col]
dt = m['datatype']
tt = ''
if col == 'identifiers':
tt = _('Enter either an identifier type or an '
@ -209,7 +211,7 @@ class ConditionEditor(QWidget): # {{{
tt = _('Enter a regular expression')
elif m.get('is_multiple', False):
tt += '\n' + _('You can match multiple values by separating'
' them with %s')%m['is_multiple']
' them with %s')%m['is_multiple']['ui_to_list']
self.value_box.setToolTip(tt)
if action in ('is set', 'is not set', 'is true', 'is false',
'is undefined'):

View File

@ -13,6 +13,9 @@ from calibre.gui2 import error_dialog
class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
# Note: in this class, we are treating is_multiple as the boolean that
# custom_columns expects to find in its structure. It does not use the dict
column_types = {
0:{'datatype':'text',
'text':_('Text, column shown in the tag browser'),

View File

@ -509,7 +509,8 @@ class ResultCache(SearchQueryParser): # {{{
valq_mkind, valq = self._matchkind(query)
loc = self.field_metadata[location]['rec_index']
split_char = self.field_metadata[location]['is_multiple']
split_char = self.field_metadata[location]['is_multiple'].get(
'cache_to_list', ',')
for id_ in candidates:
item = self._data[id_]
if item is None:
@ -665,7 +666,8 @@ class ResultCache(SearchQueryParser): # {{{
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']:\
vf = lambda item, loc=fm['rec_index'], \
ms=fm['is_multiple']['cache_to_list']:\
len(item[loc].split(ms)) if item[loc] is not None else 0
return self.get_numeric_matches(location, query[1:],
candidates, val_func=vf)
@ -703,7 +705,8 @@ class ResultCache(SearchQueryParser): # {{{
['composite', 'text', 'comments', 'series', 'enumeration']:
exclude_fields.append(db_col[x])
col_datatype[db_col[x]] = self.field_metadata[x]['datatype']
is_multiple_cols[db_col[x]] = self.field_metadata[x]['is_multiple']
is_multiple_cols[db_col[x]] = \
self.field_metadata[x]['is_multiple'].get('cache_to_list', None)
try:
rating_query = int(query) * 2
@ -1045,13 +1048,14 @@ class SortKeyGenerator(object):
elif dt in ('text', 'comments', 'composite', 'enumeration'):
if val:
sep = fm['is_multiple']
if sep:
if fm['display'].get('is_names', False):
val = sep.join(
[author_to_author_sort(v) for v in val.split(sep)])
if fm['is_multiple']:
jv = fm['is_multiple']['list_to_ui']
sv = fm['is_multiple']['cache_to_list']
if '&' in jv:
val = jv.join(
[author_to_author_sort(v) for v in val.split(sv)])
else:
val = sep.join(sorted(val.split(sep),
val = jv.join(sorted(val.split(sv),
key=self.string_sort_key))
val = self.string_sort_key(val)

View File

@ -79,16 +79,19 @@ class Rule(object): # {{{
if dt == 'bool':
return self.bool_condition(col, action, val)
if dt in ('int', 'float', 'rating'):
if dt in ('int', 'float'):
return self.number_condition(col, action, val)
if dt == 'rating':
return self.rating_condition(col, action, val)
if dt == 'datetime':
return self.date_condition(col, action, val)
if dt in ('comments', 'series', 'text', 'enumeration', 'composite'):
ism = m.get('is_multiple', False)
if ism:
return self.multiple_condition(col, action, val, ',' if ism == '|' else ism)
return self.multiple_condition(col, action, val, ism['ui_to_list'])
return self.text_condition(col, action, val)
def identifiers_condition(self, col, action, val):
@ -114,9 +117,16 @@ class Rule(object): # {{{
'lt': ('1', '', ''),
'gt': ('', '', '1')
}[action]
lt, eq, gt = '', '1', ''
return "cmp(raw_field('%s'), %s, '%s', '%s', '%s')" % (col, val, lt, eq, gt)
def rating_condition(self, col, action, val):
lt, eq, gt = {
'eq': ('', '1', ''),
'lt': ('1', '', ''),
'gt': ('', '', '1')
}[action]
return "cmp(field('%s'), %s, '%s', '%s', '%s')" % (col, val, lt, eq, gt)
def date_condition(self, col, action, val):
lt, eq, gt = {
'eq': ('', '1', ''),

View File

@ -78,6 +78,18 @@ class CustomColumns(object):
}
if data['display'] is None:
data['display'] = {}
# set up the is_multiple separator dict
if data['is_multiple']:
if data['display'].get('is_names', False):
seps = {'cache_to_list': '|', 'ui_to_list': '&', 'list_to_ui': ' & '}
elif data['datatype'] == 'composite':
seps = {'cache_to_list': ',', 'ui_to_list': ',', 'list_to_ui': ', '}
else:
seps = {'cache_to_list': '|', 'ui_to_list': ',', 'list_to_ui': ', '}
else:
seps = {}
data['multiple_seps'] = seps
table, lt = self.custom_table_names(data['num'])
if table not in custom_tables or (data['normalized'] and lt not in
custom_tables):
@ -119,7 +131,7 @@ class CustomColumns(object):
if x is None:
return []
if isinstance(x, (str, unicode, bytes)):
x = x.split('&' if d['display'].get('is_names', False) else',')
x = x.split(d['multiple_seps']['ui_to_list'])
x = [y.strip() for y in x if y.strip()]
x = [y.decode(preferred_encoding, 'replace') if not isinstance(y,
unicode) else y for y in x]
@ -181,10 +193,7 @@ class CustomColumns(object):
is_category = True
else:
is_category = False
if v['is_multiple']:
is_m = ',' if v['datatype'] == 'composite' else '|'
else:
is_m = None
is_m = v['multiple_seps']
tn = 'custom_column_{0}'.format(v['num'])
self.field_metadata.add_custom_field(label=v['label'],
table=tn, column='value', datatype=v['datatype'],
@ -200,7 +209,7 @@ class CustomColumns(object):
row = self.data._data[idx] if index_is_id else self.data[idx]
ans = row[self.FIELD_MAP[data['num']]]
if data['is_multiple'] and data['datatype'] == 'text':
ans = ans.split('|') if ans else []
ans = ans.split(data['multiple_seps']['cache_to_list']) if ans else []
if data['display'].get('sort_alpha', False):
ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower()))
return ans
@ -571,9 +580,17 @@ class CustomColumns(object):
if data['normalized']:
query = '%s.value'
if data['is_multiple']:
query = 'group_concat(%s.value, "|")'
if not display.get('sort_alpha', False):
query = 'sort_concat(link.id, %s.value)'
# query = 'group_concat(%s.value, "{0}")'.format(
# data['multiple_seps']['cache_to_list'])
# if not display.get('sort_alpha', False):
if data['multiple_seps']['cache_to_list'] == '|':
query = 'sortconcat_bar(link.id, %s.value)'
elif data['multiple_seps']['cache_to_list'] == '&':
query = 'sortconcat_amper(link.id, %s.value)'
else:
prints('WARNING: unknown value in multiple_seps',
data['multiple_seps']['cache_to_list'])
query = 'sortconcat_bar(link.id, %s.value)'
line = '''(SELECT {query} FROM {lt} AS link INNER JOIN
{table} ON(link.value={table}.id) WHERE link.book=books.id)
custom_{num}

View File

@ -1250,7 +1250,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
dex = field['rec_index']
for book in self.data.iterall():
if field['is_multiple']:
vals = [v.strip() for v in book[dex].split(field['is_multiple'])
vals = [v.strip() for v in
book[dex].split(field['is_multiple']['cache_to_list'])
if v.strip()]
if id_ in vals:
ans.add(book[0])
@ -1378,7 +1379,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tcategories[category] = {}
# create a list of category/field_index for the books scan to use.
# This saves iterating through field_metadata for each book
md.append((category, cat['rec_index'], cat['is_multiple'], False))
md.append((category, cat['rec_index'],
cat['is_multiple'].get('cache_to_list', None), False))
for category in tb_cats.iterkeys():
cat = tb_cats[category]
@ -1386,7 +1388,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
cat['display'].get('make_category', False):
tids[category] = {}
tcategories[category] = {}
md.append((category, cat['rec_index'], cat['is_multiple'],
md.append((category, cat['rec_index'],
cat['is_multiple'].get('cache_to_list', None),
cat['datatype'] == 'composite'))
#print 'end phase "collection":', time.clock() - last, 'seconds'
#last = time.clock()

View File

@ -50,9 +50,16 @@ class FieldMetadata(dict):
datatype: the type of information in the field. Valid values are listed in
VALID_DATA_TYPES below.
is_multiple: valid for the text datatype. If None, the field is to be
treated as a single term. If not None, it contains a string, and the field
is assumed to contain a list of terms separated by that string
is_multiple: valid for the text datatype. If {}, the field is to be
treated as a single term. If not None, it contains a dict of the form
{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '}
where the cache_to_list contains the character used to split the value in
the meta2 table, ui_to_list contains the character used to create a list
from a value shown in the ui (each resulting value must be strip()ed and
empty values removed), and list_to_ui contains the string used in join()
to create a displayable string from the list.
kind == field: is a db field.
kind == category: standard tag category that isn't a field. see news.
@ -97,7 +104,9 @@ class FieldMetadata(dict):
'link_column':'author',
'category_sort':'sort',
'datatype':'text',
'is_multiple':',',
'is_multiple':{'cache_to_list': ',',
'ui_to_list': '&',
'list_to_ui': ' & '},
'kind':'field',
'name':_('Authors'),
'search_terms':['authors', 'author'],
@ -109,7 +118,7 @@ class FieldMetadata(dict):
'link_column':'series',
'category_sort':'(title_sort(name))',
'datatype':'series',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Series'),
'search_terms':['series'],
@ -119,7 +128,9 @@ class FieldMetadata(dict):
('formats', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':',',
'is_multiple':{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '},
'kind':'field',
'name':_('Formats'),
'search_terms':['formats', 'format'],
@ -131,7 +142,7 @@ class FieldMetadata(dict):
'link_column':'publisher',
'category_sort':'name',
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Publishers'),
'search_terms':['publisher'],
@ -143,7 +154,7 @@ class FieldMetadata(dict):
'link_column':'rating',
'category_sort':'rating',
'datatype':'rating',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Ratings'),
'search_terms':['rating'],
@ -154,7 +165,7 @@ class FieldMetadata(dict):
'column':'name',
'category_sort':'name',
'datatype':None,
'is_multiple':None,
'is_multiple':{},
'kind':'category',
'name':_('News'),
'search_terms':[],
@ -166,7 +177,9 @@ class FieldMetadata(dict):
'link_column': 'tag',
'category_sort':'name',
'datatype':'text',
'is_multiple':',',
'is_multiple':{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '},
'kind':'field',
'name':_('Tags'),
'search_terms':['tags', 'tag'],
@ -176,7 +189,9 @@ class FieldMetadata(dict):
('identifiers', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':',',
'is_multiple':{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '},
'kind':'field',
'name':_('Identifiers'),
'search_terms':['identifiers', 'identifier', 'isbn'],
@ -186,7 +201,7 @@ class FieldMetadata(dict):
('author_sort',{'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Author Sort'),
'search_terms':['author_sort'],
@ -196,7 +211,9 @@ class FieldMetadata(dict):
('au_map', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':',',
'is_multiple':{'cache_to_list': ',',
'ui_to_list': None,
'list_to_ui': None},
'kind':'field',
'name':None,
'search_terms':[],
@ -206,7 +223,7 @@ class FieldMetadata(dict):
('comments', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Comments'),
'search_terms':['comments', 'comment'],
@ -216,7 +233,7 @@ class FieldMetadata(dict):
('cover', {'table':None,
'column':None,
'datatype':'int',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':None,
'search_terms':['cover'],
@ -226,7 +243,7 @@ class FieldMetadata(dict):
('id', {'table':None,
'column':None,
'datatype':'int',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':None,
'search_terms':[],
@ -236,7 +253,7 @@ class FieldMetadata(dict):
('last_modified', {'table':None,
'column':None,
'datatype':'datetime',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Modified'),
'search_terms':['last_modified'],
@ -246,7 +263,7 @@ class FieldMetadata(dict):
('ondevice', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('On Device'),
'search_terms':['ondevice'],
@ -256,7 +273,7 @@ class FieldMetadata(dict):
('path', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Path'),
'search_terms':[],
@ -266,7 +283,7 @@ class FieldMetadata(dict):
('pubdate', {'table':None,
'column':None,
'datatype':'datetime',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Published'),
'search_terms':['pubdate'],
@ -276,7 +293,7 @@ class FieldMetadata(dict):
('marked', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name': None,
'search_terms':['marked'],
@ -286,7 +303,7 @@ class FieldMetadata(dict):
('series_index',{'table':None,
'column':None,
'datatype':'float',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':None,
'search_terms':['series_index'],
@ -296,7 +313,7 @@ class FieldMetadata(dict):
('sort', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Title Sort'),
'search_terms':['title_sort'],
@ -306,7 +323,7 @@ class FieldMetadata(dict):
('size', {'table':None,
'column':None,
'datatype':'float',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Size'),
'search_terms':['size'],
@ -316,7 +333,7 @@ class FieldMetadata(dict):
('timestamp', {'table':None,
'column':None,
'datatype':'datetime',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Date'),
'search_terms':['date'],
@ -326,7 +343,7 @@ class FieldMetadata(dict):
('title', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':_('Title'),
'search_terms':['title'],
@ -336,7 +353,7 @@ class FieldMetadata(dict):
('uuid', {'table':None,
'column':None,
'datatype':'text',
'is_multiple':None,
'is_multiple':{},
'kind':'field',
'name':None,
'search_terms':[],
@ -508,7 +525,7 @@ class FieldMetadata(dict):
if datatype == 'series':
key += '_index'
self._tb_cats[key] = {'table':None, 'column':None,
'datatype':'float', 'is_multiple':None,
'datatype':'float', 'is_multiple':{},
'kind':'field', 'name':'',
'search_terms':[key], 'label':label+'_index',
'colnum':None, 'display':{},
@ -560,7 +577,7 @@ class FieldMetadata(dict):
if icu_lower(label) != label:
st.append(icu_lower(label))
self._tb_cats[label] = {'table':None, 'column':None,
'datatype':None, 'is_multiple':None,
'datatype':None, 'is_multiple':{},
'kind':'user', 'name':name,
'search_terms':st, 'is_custom':False,
'is_category':True, 'is_csp': False}
@ -570,7 +587,7 @@ class FieldMetadata(dict):
if label in self._tb_cats:
raise ValueError('Duplicate user field [%s]'%(label))
self._tb_cats[label] = {'table':None, 'column':None,
'datatype':None, 'is_multiple':None,
'datatype':None, 'is_multiple':{},
'kind':'search', 'name':name,
'search_terms':[], 'is_custom':False,
'is_category':True, 'is_csp': False}

View File

@ -171,7 +171,7 @@ class Restore(Thread):
for x in fields:
if x in cfm:
if x == 'is_multiple':
args.append(cfm[x] is not None)
args.append(bool(cfm[x]))
else:
args.append(cfm[x])
if len(args) == len(fields):

View File

@ -231,7 +231,8 @@ class MobileServer(object):
book['size'] = human_readable(book['size'])
aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown')
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
aut_is = CFM['authors']['is_multiple']
authors = aut_is['list_to_ui'].join([i.replace('|', ',') for i in aus.split(',')])
book['authors'] = authors
book['series_index'] = fmt_sidx(float(record[FM['series_index']]))
book['series'] = record[FM['series']]
@ -254,8 +255,10 @@ class MobileServer(object):
continue
if datatype == 'text' and CFM[key]['is_multiple']:
book[key] = concat(name,
format_tag_string(val, ',',
no_tag_count=True))
format_tag_string(val,
CFM[key]['is_multiple']['ui_to_list'],
no_tag_count=True,
joinval=CFM[key]['is_multiple']['list_to_ui']))
else:
book[key] = concat(name, val)

View File

@ -180,9 +180,12 @@ def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix):
if val:
datatype = CFM[key]['datatype']
if datatype == 'text' and CFM[key]['is_multiple']:
extra.append('%s: %s<br />'%(xml(name), xml(format_tag_string(val, ',',
ignore_max=True,
no_tag_count=True))))
extra.append('%s: %s<br />'%
(xml(name),
xml(format_tag_string(val,
CFM[key]['is_multiple']['ui_to_list'],
ignore_max=True, no_tag_count=True,
joinval=CFM[key]['is_multiple']['list_to_ui']))))
elif datatype == 'comments':
extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val))))
else:

View File

@ -68,7 +68,7 @@ def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
except:
return _strftime(fmt, nowf().timetuple())
def format_tag_string(tags, sep, ignore_max=False, no_tag_count=False):
def format_tag_string(tags, sep, ignore_max=False, no_tag_count=False, joinval=', '):
MAX = sys.maxint if ignore_max else tweaks['max_content_server_tags_shown']
if tags:
tlist = [t.strip() for t in tags.split(sep)]
@ -78,10 +78,10 @@ def format_tag_string(tags, sep, ignore_max=False, no_tag_count=False):
if len(tlist) > MAX:
tlist = tlist[:MAX]+['...']
if no_tag_count:
return ', '.join(tlist) if tlist else ''
return joinval.join(tlist) if tlist else ''
else:
return u'%s:&:%s'%(tweaks['max_content_server_tags_shown'],
', '.join(tlist)) if tlist else ''
joinval.join(tlist)) if tlist else ''
def quote(s):
if isinstance(s, unicode):

View File

@ -121,8 +121,12 @@ class XMLServer(object):
name = CFM[key]['name']
custcols.append(k)
if datatype == 'text' and CFM[key]['is_multiple']:
kwargs[k] = concat('#T#'+name, format_tag_string(val,',',
ignore_max=True))
kwargs[k] = \
concat('#T#'+name,
format_tag_string(val,
CFM[key]['is_multiple']['ui_to_list'],
ignore_max=True,
joinval=CFM[key]['is_multiple']['list_to_ui']))
else:
kwargs[k] = concat(name, val)
kwargs['custcols'] = ','.join(custcols)

View File

@ -121,9 +121,12 @@ class SortedConcatenate(object):
return None
return self.sep.join(map(self.ans.get, sorted(self.ans.keys())))
class SafeSortedConcatenate(SortedConcatenate):
class SortedConcatenateBar(SortedConcatenate):
sep = '|'
class SortedConcatenateAmper(SortedConcatenate):
sep = '&'
class IdentifiersConcat(object):
'''String concatenation aggregator for the identifiers map'''
def __init__(self):
@ -220,7 +223,8 @@ class DBThread(Thread):
self.conn.execute('pragma cache_size=5000')
encoding = self.conn.execute('pragma encoding').fetchone()[0]
self.conn.create_aggregate('sortconcat', 2, SortedConcatenate)
self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate)
self.conn.create_aggregate('sortconcat_bar', 2, SortedConcatenateBar)
self.conn.create_aggregate('sortconcat_amper', 2, SortedConcatenateAmper)
self.conn.create_aggregate('identifiers_concat', 2, IdentifiersConcat)
load_c_extensions(self.conn)
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)

View File

@ -141,6 +141,22 @@ static void sort_concat_finalize2(sqlite3_context *context) {
}
static void sort_concat_finalize3(sqlite3_context *context) {
SortConcatList *list;
unsigned char *ans;
list = (SortConcatList*) sqlite3_aggregate_context(context, sizeof(*list));
if (list != NULL && list->vals != NULL && list->count > 0) {
qsort(list->vals, list->count, sizeof(list->vals[0]), sort_concat_cmp);
ans = sort_concat_do_finalize(list, '&');
if (ans != NULL) sqlite3_result_text(context, (char*)ans, -1, SQLITE_TRANSIENT);
free(ans);
sort_concat_free(list);
}
}
// }}}
// identifiers_concat {{{
@ -237,7 +253,8 @@ MYEXPORT int sqlite3_extension_init(
sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi){
SQLITE_EXTENSION_INIT2(pApi);
sqlite3_create_function(db, "sortconcat", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize);
sqlite3_create_function(db, "sort_concat", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize2);
sqlite3_create_function(db, "sortconcat_bar", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize2);
sqlite3_create_function(db, "sortconcat_amper", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize3);
sqlite3_create_function(db, "identifiers_concat", 2, SQLITE_UTF8, NULL, NULL, identifiers_concat_step, identifiers_concat_finalize);
return 0;
}

View File

@ -727,13 +727,8 @@ class BuiltinNot(BuiltinFormatterFunction):
'returns the empty string. This function works well with test or '
'first_non_empty. You can have as many values as you want.')
def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0
while i < len(args):
if args[i]:
return '1'
i += 1
return ''
def evaluate(self, formatter, kwargs, mi, locals, val):
return '' if val else '1'
class BuiltinMergeLists(BuiltinFormatterFunction):
name = 'merge_lists'