def urls_from_identifiers(identifiers): # {{{ identifiers = {k.lower(): v for k, v in identifiers.iteritems()} ans = [] rules = msprefs['id_link_rules'] if rules: formatter = EvalFormatter() for k, val in identifiers.iteritems(): vals = { 'id': quote(val if isinstance(val, bytes) else val.encode('utf-8')). decode('ascii') } items = rules.get(k) or () for name, template in items: try: url = formatter.safe_format(template, vals, '', vals) except Exception: import traceback traceback.format_exc() continue ans.append((name, k, val, url)) for plugin in all_metadata_plugins(): try: for id_type, id_val, url in plugin.get_book_urls(identifiers): ans.append( (plugin.get_book_url_name(id_type, id_val, url), id_type, id_val, url)) except: pass isbn = identifiers.get('isbn', None) if isbn: ans.append( (isbn, 'isbn', isbn, 'http://www.worldcat.org/isbn/' + isbn)) doi = identifiers.get('doi', None) if doi: ans.append(('DOI', 'doi', doi, 'http://dx.doi.org/' + doi)) arxiv = identifiers.get('arxiv', None) if arxiv: ans.append(('arXiv', 'arxiv', arxiv, 'http://arxiv.org/abs/' + arxiv)) oclc = identifiers.get('oclc', None) if oclc: ans.append( ('OCLC', 'oclc', oclc, 'http://www.worldcat.org/oclc/' + oclc)) issn = check_issn(identifiers.get('issn', None)) if issn: ans.append( (issn, 'issn', issn, 'http://www.worldcat.org/issn/' + issn)) for k, url in identifiers.iteritems(): if url and re.match(r'ur[il]\d*$', k) is not None: url = url[:8].replace('|', ':') + url[8:].replace('|', ',') if url.partition(':')[0].lower() in {'http', 'file', 'https'}: parts = urlparse(url) name = parts.netloc or parts.path ans.append((name, k, url, url)) return ans
def compute_category_name(self, field_key, field_value, field_meta): renames = tweaks['sony_collection_renaming_rules'] field_name = renames.get(field_key, None) if field_name is None: if field_meta['is_custom']: field_name = field_meta['name'] else: field_name = '' cat_name = EvalFormatter().safe_format( fmt=tweaks['sony_collection_name_template'], kwargs={'category':field_name, 'value':field_value}, error_value='GET_CATEGORY', book=None) return cat_name.strip()
def urls_from_identifiers(identifiers): # {{{ identifiers = {k.lower():v for k, v in identifiers.iteritems()} ans = [] rules = msprefs['id_link_rules'] if rules: formatter = EvalFormatter() for k, val in identifiers.iteritems(): vals = {'id':quote(val if isinstance(val, bytes) else val.encode('utf-8')).decode('ascii')} items = rules.get(k) or () for name, template in items: try: url = formatter.safe_format(template, vals, '', vals) except Exception: import traceback traceback.format_exc() continue ans.append((name, k, val, url)) for plugin in all_metadata_plugins(): try: for id_type, id_val, url in plugin.get_book_urls(identifiers): ans.append((plugin.get_book_url_name(id_type, id_val, url), id_type, id_val, url)) except: pass isbn = identifiers.get('isbn', None) if isbn: ans.append((isbn, 'isbn', isbn, 'http://www.worldcat.org/isbn/'+isbn)) doi = identifiers.get('doi', None) if doi: ans.append(('DOI', 'doi', doi, 'http://dx.doi.org/'+doi)) arxiv = identifiers.get('arxiv', None) if arxiv: ans.append(('arXiv', 'arxiv', arxiv, 'http://arxiv.org/abs/'+arxiv)) oclc = identifiers.get('oclc', None) if oclc: ans.append(('OCLC', 'oclc', oclc, 'http://www.worldcat.org/oclc/'+oclc)) issn = check_issn(identifiers.get('issn', None)) if issn: ans.append((issn, 'issn', issn, 'http://www.worldcat.org/issn/'+issn)) for k, url in identifiers.iteritems(): if url and re.match(r'ur[il]\d*$', k) is not None: url = url[:8].replace('|', ':') + url[8:].replace('|', ',') if url.partition(':')[0].lower() in {'http', 'file', 'https'}: parts = urlparse(url) name = parts.netloc or parts.path ans.append((name, k, url, url)) return ans
def compute_category_name(self, field_key, field_value, field_meta): from calibre.utils.formatter import EvalFormatter renames = tweaks['sony_collection_renaming_rules'] field_name = renames.get(field_key, None) if field_name is None: if field_meta['is_custom']: field_name = field_meta['name'] else: field_name = '' cat_name = EvalFormatter().safe_format( fmt=tweaks['sony_collection_name_template'], kwargs={'category':field_name, 'value':field_value}, error_value='GET_CATEGORY', book=None) return cat_name.strip()
def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_metadata, opts, book_rating_map): eval_formatter = EvalFormatter() tag_map, hierarchical_tags, node_to_tag_map = {}, set(), {} first, later, collapse_nodes, intermediate_nodes, hierarchical_items = [], [], [], {}, set() # User categories have to be processed after normal categories as they can # reference hierarchical nodes that were created only during processing of # normal categories for category_node_id in category_nodes: cnode = items[category_node_id] coll = later if cnode.get('is_user_category', False) else first coll.append(node_id_map[category_node_id]) for coll in (first, later): for cnode in coll: process_category_node(cnode, items, category_data, eval_formatter, field_metadata, opts, tag_map, hierarchical_tags, node_to_tag_map, collapse_nodes, intermediate_nodes, hierarchical_items) # Do not store id_set in the tag items as it is a lot of data, with not # much use. Instead only update the ratings and counts based on id_set for item_id in hierarchical_items: item = items[item_id] total = count = 0 for book_id in item['id_set']: rating = book_rating_map.get(book_id, 0) if rating: total += rating / 2.0 count += 1 item['avg_rating'] = float(total) / count if count else 0 for item_id, item in itervalues(tag_map): id_len = len(item.pop('id_set', ())) if id_len: item['count'] = id_len for node in collapse_nodes: item = items[node['id']] item['count'] = sum(1 for _ in iternode_descendants(node))
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif', rtl=False): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) book_id = getattr(mi, 'id', 0) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['is_custom'] and metadata['datatype'] in {'bool', 'int', 'float'}: isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val/2.0 ans.append((field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>'%( name, rating_font, u'\u2605'*int(val)))) elif metadata['datatype'] == 'composite': val = getattr(mi, field) if val: val = force_unicode(val) if metadata['display'].get('contains_html', False): ans.append((field, row % (name, comments_to_html(val)))) else: if not metadata['is_multiple']: val = '<a href="%s" title="%s">%s</a>' % ( search_href(field, val), _('Click to see books with {0}: {1}').format(metadata['name'], a(val)), p(val)) else: all_vals = [v.strip() for v in val.split(metadata['is_multiple']['list_to_ui']) if v.strip()] links = ['<a href="%s" title="%s">%s</a>' % ( search_href(field, x), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals] val = metadata['is_multiple']['list_to_ui'].join(links) ans.append((field, row % (name, val))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml(path if isdevice else unicode(book_id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>'%( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = mi.path or '' bpath = '' if path: h, t = os.path.split(path) bpath = os.sep.join((os.path.basename(h), t)) data = ({ 'fmt':x, 'path':a(path or ''), 'fname':a(mi.format_files.get(x, '')), 'ext':x.lower(), 'id':book_id, 'bpath':bpath, 'sep':os.sep } for x in mi.formats) fmts = [u'<a data-full-path="{path}{sep}{fname}.{ext}" title="{bpath}{sep}{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>'.format(**x) for x in data] ans.append((field, row % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [u'<a href="%s" title="%s:%s" data-item="%s">%s</a>' % (a(url), a(id_typ), a(id_val), a(item_data(field, id_typ, book_id)), p(namel)) for namel, id_typ, id_val, url in urls] links = u', '.join(links) if links: ans.append((field, row % (_('Ids')+':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = lt = mi.author_link_map[aut] elif default_author_link: if default_author_link == 'search-calibre': link = search_href('authors', aut) lt = a(_('Search the calibre library for books by %s') % aut) else: vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = lt = a(formatter.safe_format(default_author_link, vals, '', vals)) aut = p(aut) if link: authors.append(u'<a calibre-data="authors" title="%s" href="%s">%s</a>'%(lt, link, aut)) else: authors.append(aut) ans.append((field, row % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u', '.join(names)))) elif field == 'publisher': if not mi.publisher: continue val = '<a href="%s" title="%s" data-item="%s">%s</a>' % ( search_href('publisher', mi.publisher), _('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)), a(item_data('publisher', mi.publisher, book_id)), p(mi.publisher)) ans.append((field, row % (name, val))) elif field == 'title': # otherwise title gets metadata['datatype'] == 'text' # treatment below with a click to search link (which isn't # too bad), and a right-click 'Delete' option to delete # the title (which is bad). val = mi.format_field(field)[-1] ans.append((field, row % (name, val))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 try: st = metadata['search_terms'][0] except Exception: st = field series = getattr(mi, field) val = _( '%(sidx)s of <a href="%(href)s" title="%(tt)s" data-item="%(data)s">' '<span class="%(cls)s">%(series)s</span></a>') % dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name", series=p(series), href=search_href(st, series), data=a(item_data(field, series, book_id)), tt=p(_('Click to see books in this series'))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue elif metadata['datatype'] == 'text' and metadata['is_multiple']: try: st = metadata['search_terms'][0] except Exception: st = field all_vals = mi.get(field) if field == 'tags': all_vals = sorted(all_vals, key=sort_key) links = ['<a href="%s" title="%s" data-item="%s">%s</a>' % ( search_href(st, x), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), a(item_data(field, x, book_id)), p(x)) for x in all_vals] val = metadata['is_multiple']['list_to_ui'].join(links) elif metadata['datatype'] == 'text' or metadata['datatype'] == 'enumeration': # text/is_multiple handled above so no need to add the test to the if try: st = metadata['search_terms'][0] except Exception: st = field val = '<a href="%s" title="%s" data-item="%s">%s</a>' % ( search_href(st, val), a(_('Click to see books with {0}: {1}').format(metadata['name'], val)), a(item_data(field, val, book_id)), p(val)) ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections')+':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s'%dt ans = [u'<tr id="%s" class="%s">%s</tr>'%(fieldl.replace('#', '_'), classname(fieldl), html) for fieldl, html in ans] # print '\n'.join(ans) direction = 'rtl' if rtl else 'ltr' margin = 'left' if rtl else 'right' return u'<table class="fields" style="direction: %s; margin-%s:auto">%s</table>'%(direction, margin, u'\n'.join(ans)), comment_fields
def render_data(mi, use_roman_numbers=True, all_fields=False): ans = [] isdevice = not hasattr(mi, 'id') fm = getattr(mi, 'field_metadata', field_metadata) for field, display in get_field_list(fm): metadata = fm.get(field, None) if field == 'sort': field = 'title_sort' if all_fields: display = True if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if (not display or not metadata or isnull or field == 'comments'): continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, u'<td class="comments" colspan="2">%s</td>'%comments_to_html(val))) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val/2.0 ans.append((field, u'<td class="title">%s</td><td class="rating" ' 'style=\'font-family:"%s"\'>%s</td>'%( name, rating_font(), u'\u2605'*int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, u'<td class="title">%s</td><td>%s</td>'% (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml(path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>'%( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, link))) elif field == 'formats': if isdevice: continue fmts = [u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x in mi.formats] ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name) for name, id_typ, id_val, url in urls] links = u', '.join(links) if links: ans.append((field, u'<td class="title">%s</td><td>%s</td>'%( _('Ids')+':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif gprefs.get('default_author_link'): vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format( gprefs.get('default_author_link'), vals, '', vals) if link: link = prepare_string_for_xml(link) authors.append(u'<a calibre-data="authors" href="%s">%s</a>'%(link, aut)) else: authors.append(aut) ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = prepare_string_for_xml(val) if metadata['datatype'] == 'series': sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=prepare_string_for_xml(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', u'<td class="title">%s</td><td>%s</td>'%( _('Collections')+':', dc))) def classname(field): try: dt = fm[field]['datatype'] except: dt = 'text' return 'datatype_%s'%dt ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'), classname(field), html) for field, html in ans] # print '\n'.join(ans) return u'<table class="fields">%s</table>'%(u'\n'.join(ans))
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif', rtl=False): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) book_id = getattr(mi, 'id', 0) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['is_custom'] and metadata['datatype'] in { 'bool', 'int', 'float' }: isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' disp = metadata['display'] if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: ctype = disp.get('interpret_as') or 'html' val = force_unicode(val) if ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % p(val) elif ctype == 'short-text': val = '<span>%s</span>' % p(val) elif ctype == 'markdown': val = markdown(val) else: val = comments_to_html(val) if disp.get('heading_position', 'hide') == 'side': ans.append((field, row % (name, val))) else: if disp.get('heading_position', 'hide') == 'above': val = '<h3 class="comments-heading">%s</h3>%s' % ( p(name), val) comment_fields.append( '<div id="%s" class="comments">%s</div>' % (field.replace('#', '_'), val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: star_string = rating_to_stars( val, disp.get('allow_half_stars', False)) ans.append( (field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>' % (name, rating_font, star_string))) elif metadata['datatype'] == 'composite': val = getattr(mi, field) if val: val = force_unicode(val) if disp.get('contains_html', False): ans.append((field, row % (name, comments_to_html(val)))) else: if not metadata['is_multiple']: val = '<a href="%s" title="%s">%s</a>' % ( search_action(field, val), _('Click to see books with {0}: {1}').format( metadata['name'], a(val)), p(val)) else: all_vals = [ v.strip() for v in val.split( metadata['is_multiple']['list_to_ui']) if v.strip() ] links = [ '<a href="%s" title="%s">%s</a>' % (search_action(field, x), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals ] val = metadata['is_multiple']['list_to_ui'].join(links) ans.append((field, row % (name, val))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' loc = path if isdevice else book_id pathstr = _('Click to open') extra = '' if isdevice: durl = path if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % ( prepare_string_for_xml(durl)) link = '<a href="%s" title="%s">%s</a>%s' % (action( scheme, loc=loc), prepare_string_for_xml( path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = mi.path or '' bpath = '' if path: h, t = os.path.split(path) bpath = os.sep.join((os.path.basename(h), t)) data = ({ 'fmt': x, 'path': a(path or ''), 'fname': a(mi.format_files.get(x, '')), 'ext': x.lower(), 'id': book_id, 'bpath': bpath, 'sep': os.sep, 'action': action('format', book_id=book_id, fmt=x, path=path or '', fname=mi.format_files.get(x, '')) } for x in mi.formats) fmts = [ '<a title="{bpath}{sep}{fname}.{ext}" href="{action}">{fmt}</a>' .format(**x) for x in data ] ans.append((field, row % (name, ', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [ '<a href="%s" title="%s:%s">%s</a>' % (action('identifier', url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers', book_id=book_id), a(id_typ), a(id_val), p(namel)) for namel, id_typ, id_val, url in urls ] links = u', '.join(links) if links: ans.append((field, row % (_('Ids') + ':', links))) elif field == 'authors': authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map.get(aut): link = lt = mi.author_link_map[aut] elif default_author_link: if isdevice and default_author_link == 'search-calibre': default_author_link = DEFAULT_AUTHOR_LINK if default_author_link.startswith('search-'): which_src = default_author_link.partition('-')[2] link, lt = author_search_href(which_src, title=mi.title, author=aut) else: vals = { 'author': qquote(aut), 'title': qquote(mi.title) } try: vals['author_sort'] = qquote( mi.author_sort_map[aut]) except KeyError: vals['author_sort'] = qquote(aut) link = lt = formatter.safe_format( default_author_link, vals, '', vals) aut = p(aut) if link: authors.append( '<a title="%s" href="%s">%s</a>' % (a(lt), action('author', url=link, name=aut, title=lt), aut)) else: authors.append(aut) ans.append((field, row % (name, ' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) names = [ '<a href="%s" title="%s">%s</a>' % (search_action('languages', n), _('Search calibre for books with the language: {}').format(n), n) for n in names ] ans.append((field, row % (name, u', '.join(names)))) elif field == 'publisher': if not mi.publisher: continue val = '<a href="%s" title="%s">%s</a>' % (search_action_with_data( 'publisher', mi.publisher, book_id), _('Click to see books with {0}: {1}').format( metadata['name'], a(mi.publisher)), p(mi.publisher)) ans.append((field, row % (name, val))) elif field == 'title': # otherwise title gets metadata['datatype'] == 'text' # treatment below with a click to search link (which isn't # too bad), and a right-click 'Delete' option to delete # the title (which is bad). val = mi.format_field(field)[-1] ans.append((field, row % (name, val))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field + '_index') if sidx is None: sidx = 1.0 try: st = metadata['search_terms'][0] except Exception: st = field series = getattr(mi, field) val = _('%(sidx)s of <a href="%(href)s" title="%(tt)s">' '<span class="%(cls)s">%(series)s</span></a>') % dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name", series=p(series), href=search_action_with_data( st, series, book_id, field), tt=p(_('Click to see books in this series'))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue elif metadata['datatype'] == 'text' and metadata['is_multiple']: try: st = metadata['search_terms'][0] except Exception: st = field all_vals = mi.get(field) if not metadata.get('display', {}).get('is_names', False): all_vals = sorted(all_vals, key=sort_key) links = [ '<a href="%s" title="%s">%s</a>' % (search_action_with_data(st, x, book_id, field), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals ] val = metadata['is_multiple']['list_to_ui'].join(links) elif metadata['datatype'] == 'text' or metadata[ 'datatype'] == 'enumeration': # text/is_multiple handled above so no need to add the test to the if try: st = metadata['search_terms'][0] except Exception: st = field val = '<a href="%s" title="%s">%s</a>' % ( search_action_with_data(st, val, book_id, field), a( _('Click to see books with {0}: {1}').format( metadata['name'], val)), p(val)) ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections') + ':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s' % dt ans = [ u'<tr id="%s" class="%s">%s</tr>' % (fieldl.replace('#', '_'), classname(fieldl), html) for fieldl, html in ans ] # print '\n'.join(ans) direction = 'rtl' if rtl else 'ltr' margin = 'left' if rtl else 'right' return u'<style>table.fields td { vertical-align:top}</style>' + \ u'<table class="fields" style="direction: %s; margin-%s:auto">%s</table>'%( direction, margin, u'\n'.join(ans)), comment_fields
def render_data(mi, use_roman_numbers=True, all_fields=False): ans = [] isdevice = not hasattr(mi, 'id') fm = getattr(mi, 'field_metadata', field_metadata) for field, display in get_field_list(fm): metadata = fm.get(field, None) if field == 'sort': field = 'title_sort' if all_fields: display = True if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if (not display or not metadata or isnull or field == 'comments'): continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) ans.append( (field, u'<td class="comments" colspan="2">%s</td>' % comments_to_html(val))) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val / 2.0 ans.append( (field, u'<td class="title">%s</td><td class="rating" ' 'style=\'font-family:"%s"\'>%s</td>' % (name, rating_font(), u'\u2605' * int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml( path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % ( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % ( scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append( (field, u'<td class="title">%s</td><td>%s</td>' % (name, link))) elif field == 'formats': if isdevice: continue fmts = [ u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x in mi.formats ] ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [ u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name) for name, id_typ, id_val, url in urls ] links = u', '.join(links) if links: ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (_('Ids') + ':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif gprefs.get('default_author_link'): vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace( ' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format( gprefs.get('default_author_link'), vals, '', vals) if link: link = prepare_string_for_xml(link) authors.append(u'<a href="%s">%s</a>' % (link, aut)) else: authors.append(aut) ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = prepare_string_for_xml(val) if metadata['datatype'] == 'series': sidx = mi.get(field + '_index') if sidx is None: sidx = 1.0 val = _( 'Book %(sidx)s of <span class="series_name">%(series)s</span>' ) % dict(sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=prepare_string_for_xml(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append( (field, u'<td class="title">%s</td><td>%s</td>' % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append( ('device_collections', u'<td class="title">%s</td><td>%s</td>' % (_('Collections') + ':', dc))) def classname(field): try: dt = fm[field]['datatype'] except: dt = 'text' return 'datatype_%s' % dt ans = [ u'<tr id="%s" class="%s">%s</tr>' % (field.replace('#', '_'), classname(field), html) for field, html in ans ] # print '\n'.join(ans) return u'<table class="fields">%s</table>' % (u'\n'.join(ans))
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif'): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val/2.0 ans.append((field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>'%( name, rating_font, u'\u2605'*int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, row % (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml(path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>'%( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = '' if mi.path: h, t = os.path.split(mi.path) path = '/'.join((os.path.basename(h), t)) data = ({ 'fmt':x, 'path':a(path or ''), 'fname':a(mi.format_files.get(x, '')), 'ext':x.lower(), 'id':mi.id } for x in mi.formats) fmts = [u'<a title="{path}/{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>'.format(**x) for x in data] ans.append((field, row % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [u'<a href="%s" title="%s:%s">%s</a>' % (a(url), a(id_typ), a(id_val), p(name)) for name, id_typ, id_val, url in urls] links = u', '.join(links) if links: ans.append((field, row % (_('Ids')+':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif default_author_link: vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format( default_author_link, vals, '', vals) aut = p(aut) if link: authors.append(u'<a calibre-data="authors" href="%s">%s</a>'%(a(link), aut)) else: authors.append(aut) ans.append((field, row % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=p(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections')+':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s'%dt ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'), classname(field), html) for field, html in ans] # print '\n'.join(ans) return u'<table class="fields">%s</table>'%(u'\n'.join(ans)), comment_fields
def urls_from_identifiers(identifiers, sort_results=False): # {{{ identifiers = {k.lower(): v for k, v in iteritems(identifiers)} ans = [] keys_left = set(identifiers) def add(name, k, val, url): ans.append((name, k, val, url)) keys_left.discard(k) rules = msprefs['id_link_rules'] if rules: formatter = EvalFormatter() for k, val in iteritems(identifiers): val = val.replace('|', ',') vals = { 'id': unicode_type( quote(val if isinstance(val, bytes) else val.encode('utf-8' ))), 'id_unquoted': str(val), } items = rules.get(k) or () for name, template in items: try: url = formatter.safe_format(template, vals, '', vals) except Exception: import traceback traceback.format_exc() continue add(name, k, val, url) for plugin in all_metadata_plugins(): try: for id_type, id_val, url in plugin.get_book_urls(identifiers): add(plugin.get_book_url_name(id_type, id_val, url), id_type, id_val, url) except Exception: pass isbn = identifiers.get('isbn', None) if isbn: add(isbn, 'isbn', isbn, 'https://www.worldcat.org/isbn/' + isbn) doi = identifiers.get('doi', None) if doi: add('DOI', 'doi', doi, 'https://dx.doi.org/' + doi) arxiv = identifiers.get('arxiv', None) if arxiv: add('arXiv', 'arxiv', arxiv, 'https://arxiv.org/abs/' + arxiv) oclc = identifiers.get('oclc', None) if oclc: add('OCLC', 'oclc', oclc, 'https://www.worldcat.org/oclc/' + oclc) issn = check_issn(identifiers.get('issn', None)) if issn: add(issn, 'issn', issn, 'https://www.worldcat.org/issn/' + issn) q = {'http', 'https', 'file'} for k, url in iteritems(identifiers): if url and re.match(r'ur[il]\d*$', k) is not None: url = url[:8].replace('|', ':') + url[8:].replace('|', ',') if url.partition(':')[0].lower() in q: parts = urlparse(url) name = parts.netloc or parts.path add(name, k, url, url) for k in tuple(keys_left): val = identifiers.get(k) if val: url = val[:8].replace('|', ':') + val[8:].replace('|', ',') if url.partition(':')[0].lower() in q: parts = urlparse(url) name = parts.netloc or parts.path add(name, k, url, url) if sort_results: def url_key(x): return primary_sort_key(str(x[0])) ans = sorted(ans, key=url_key) return ans
def render_data(mi, use_roman_numbers=True, all_fields=False): ans = [] comment_fields = [] isdevice = not hasattr(mi, "id") fm = getattr(mi, "field_metadata", field_metadata) row = u'<td class="title">%s</td><td class="value">%s</td>' for field, display in get_field_list(fm): metadata = fm.get(field, None) if field == "sort": field = "title_sort" if all_fields: display = True if metadata["datatype"] == "bool": isnull = mi.get(field) is None else: isnull = mi.is_null(field) if not display or not metadata or isnull: continue name = metadata["name"] if not name: name = field name += ":" if metadata["datatype"] == "comments" or field == "comments": val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata["datatype"] == "rating": val = getattr(mi, field) if val: val = val / 2.0 ans.append( ( field, u'<td class="title">%s</td><td class="rating value" ' "style='font-family:\"%s\"'>%s</td>" % (name, rating_font(), u"\u2605" * int(val)), ) ) elif metadata["datatype"] == "composite" and metadata["display"].get("contains_html", False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, row % (name, comments_to_html(val)))) elif field == "path": if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u"devpath" if isdevice else u"path" url = prepare_string_for_xml(path if isdevice else unicode(mi.id), True) pathstr = _("Click to open") extra = "" if isdevice: durl = url if durl.startswith("mtp:::"): durl = ":::".join((durl.split(":::"))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % (prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % ( scheme, url, prepare_string_for_xml(path, True), pathstr, extra, ) ans.append((field, row % (name, link))) elif field == "formats": if isdevice: continue fmts = [u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x in mi.formats] ans.append((field, row % (name, u", ".join(fmts)))) elif field == "identifiers": urls = urls_from_identifiers(mi.identifiers) links = [ u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name) for name, id_typ, id_val, url in urls ] links = u", ".join(links) if links: ans.append((field, row % (_("Ids") + ":", links))) elif field == "authors" and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = "" if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif gprefs.get("default_author_link"): vals = {"author": aut.replace(" ", "+")} try: vals["author_sort"] = mi.author_sort_map[aut].replace(" ", "+") except: vals["author_sort"] = aut.replace(" ", "+") link = formatter.safe_format(gprefs.get("default_author_link"), vals, "", vals) if link: link = prepare_string_for_xml(link) authors.append(u'<a calibre-data="authors" href="%s">%s</a>' % (link, aut)) else: authors.append(aut) ans.append((field, row % (name, u" & ".join(authors)))) elif field == "languages": if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u", ".join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = prepare_string_for_xml(val) if metadata["datatype"] == "series": sidx = mi.get(field + "_index") if sidx is None: sidx = 1.0 val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>') % dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=prepare_string_for_xml(getattr(mi, field)) ) elif metadata["datatype"] == "datetime": aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, row % (name, val))) dc = getattr(mi, "device_collections", []) if dc: dc = u", ".join(sorted(dc, key=sort_key)) ans.append(("device_collections", row % (_("Collections") + ":", dc))) def classname(field): try: dt = fm[field]["datatype"] except: dt = "text" return "datatype_%s" % dt ans = [u'<tr id="%s" class="%s">%s</tr>' % (field.replace("#", "_"), classname(field), html) for field, html in ans] # print '\n'.join(ans) return u'<table class="fields">%s</table>' % (u"\n".join(ans)), comment_fields
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif'): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val / 2.0 ans.append( (field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>' % (name, rating_font, u'\u2605' * int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, row % (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml( path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % ( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % ( scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = '' if mi.path: h, t = os.path.split(mi.path) path = '/'.join((os.path.basename(h), t)) data = ({ 'fmt': x, 'path': a(path or ''), 'fname': a(mi.format_files.get(x, '')), 'ext': x.lower(), 'id': mi.id } for x in mi.formats) fmts = [ u'<a title="{path}/{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>' .format(**x) for x in data ] ans.append((field, row % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [ u'<a href="%s" title="%s:%s">%s</a>' % (a(url), a(id_typ), a(id_val), p(namel)) for namel, id_typ, id_val, url in urls ] links = u', '.join(links) if links: ans.append((field, row % (_('Ids') + ':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif default_author_link: vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace( ' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format(default_author_link, vals, '', vals) aut = p(aut) if link: authors.append( u'<a calibre-data="authors" title="%s" href="%s">%s</a>' % (a(link), a(link), aut)) else: authors.append(aut) ans.append((field, row % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field + '_index') if sidx is None: sidx = 1.0 val = _( 'Book %(sidx)s of <span class="series_name">%(series)s</span>' ) % dict(sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=p(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections') + ':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s' % dt ans = [ u'<tr id="%s" class="%s">%s</tr>' % (fieldl.replace('#', '_'), classname(fieldl), html) for fieldl, html in ans ] # print '\n'.join(ans) return u'<table class="fields">%s</table>' % ( u'\n'.join(ans)), comment_fields