def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if (mi.rating or 0) > 0: rating = rating_to_stars(mi.rating) extra.append(_('RATING: %s<br />')%rating) if mi.tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(mi.tags, None))) if mi.series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')% dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in filter(request_context.ctx.is_field_displayable, field_metadata.ignorable_field_keys()): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append('%s: %s<br />'% (xml(name), xml(format_tag_string(val, fm['is_multiple']['ui_to_list'], joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(str(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(str(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(mi.last_modified), E.published(mi.timestamp.isoformat())) if mi.pubdate and not is_date_undefined(mi.pubdate): ans.append(ans.makeelement('{%s}date' % DC_NS)) ans[-1].text = mi.pubdate.isoformat() if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: fm = mi.format_metadata for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] if mt: link = E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition") ffm = fm.get(fmt.upper()) if ffm: link.set('length', str(ffm['size'])) link.set('mtime', ffm['mtime'].isoformat()) ans.append(link) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/image")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/image/thumbnail")) return ans
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = rating_to_stars(mi.rating) extra.append(_('RATING: %s<br />')%rating) if mi.tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(mi.tags, None))) if mi.series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')% dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in filter(request_context.ctx.is_field_displayable, field_metadata.ignorable_field_keys()): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append('%s: %s<br />'% (xml(name), xml(format_tag_string(val, fm['is_multiple']['ui_to_list'], joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(mi.last_modified), E.published(mi.timestamp.isoformat())) if mi.pubdate and not is_date_undefined(mi.pubdate): ans.append(ans.makeelement('{%s}date' % DC_NS)) ans[-1].text = mi.pubdate.isoformat() if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: fm = mi.format_metadata for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] if mt: link = E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition") ffm = fm.get(fmt.upper()) if ffm: link.set('length', str(ffm['size'])) link.set('mtime', ffm['mtime'].isoformat()) ans.append(link) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) return ans
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = u"".join(repeat(u"\u2605", int(mi.rating / 2.0))) extra.append(_("RATING: %s<br />") % rating) if mi.tags: extra.append(_("TAGS: %s<br />") % xml(format_tag_string(mi.tags, None))) if mi.series: extra.append( _("SERIES: %(series)s [%(sidx)s]<br />") % dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index))) ) for key in filter(request_context.ctx.is_field_displayable, field_metadata.ignorable_field_keys()): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm["datatype"] if datatype == "text" and fm["is_multiple"]: extra.append( "%s: %s<br />" % ( xml(name), xml( format_tag_string( val, fm["is_multiple"]["ui_to_list"], joinval=fm["is_multiple"]["list_to_ui"] ) ), ) ) elif datatype == "comments" or ( fm["datatype"] == "composite" and fm["display"].get("contains_html", False) ): extra.append("%s: %s<br />" % (xml(name), comments_to_html(unicode(val)))) else: extra.append("%s: %s<br />" % (xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml("\n".join(extra)) ans = E.entry( TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID("urn:uuid:" + mi.uuid), UPDATED(updated) ) if len(extra): ans.append(E.content(extra, type="xhtml")) get = partial(request_context.ctx.url_for, "/get", book_id=book_id, library_id=request_context.library_id) if mi.formats: for fmt in mi.formats: fmt = fmt.lower() mt = guess_type("a." + fmt)[0] if mt: ans.append(E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition")) ans.append(E.link(type="image/jpeg", href=get(what="cover"), rel="http://opds-spec.org/cover")) ans.append(E.link(type="image/jpeg", href=get(what="thumb"), rel="http://opds-spec.org/thumbnail")) return ans
def add_field(field, db, book_id, ans, field_metadata): datatype = field_metadata.get('datatype') if datatype is not None: val = db._field_for(field, book_id) if val is not None and val not in empty_val: if datatype == 'datetime': val = encode_datetime(val) if val is None: return elif datatype == 'comments' or field == 'comments': val = comments_to_html(val) elif datatype == 'composite' and field_metadata['display'].get('contains_html'): val = comments_to_html(val) ans[field] = val
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = u''.join(repeat(u'\u2605', int(mi.rating/2.))) extra.append(_('RATING: %s<br />')%rating) if mi.tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(mi.tags, None, no_tag_count=True))) if mi.series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')% dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in field_metadata.ignorable_field_keys(): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append('%s: %s<br />'% (xml(name), xml(format_tag_string(val, fm['is_multiple']['ui_to_list'], no_tag_count=True, joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] if mt: ans.append(E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition")) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) return ans
def __init__(self, parent, text, column_name=None): QDialog.__init__(self, parent) self.setObjectName("CommentsDialog") self.setWindowTitle(_("Edit comments")) self.verticalLayout = l = QVBoxLayout(self) self.textbox = tb = Editor(self) self.buttonBox = bb = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(tb) l.addWidget(bb) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags() & (~Qt.WindowType.WindowContextHelpButtonHint)) self.setWindowIcon(icon) self.textbox.html = comments_to_html(text) if text else '' self.textbox.wyswyg_dirtied() # self.textbox.setTabChangesFocus(True) if column_name: self.setWindowTitle(_('Edit "{0}"').format(column_name)) geom = gprefs.get('comments_dialog_geom', None) if geom is not None: QApplication.instance().safe_restore_geometry(self, geom) else: self.resize(self.sizeHint())
def show_details(self, index): f = rating_font() book = self.model().data(index, Qt.UserRole) parts = [ '<center>', '<h2>%s</h2>'%book.title, '<div><i>%s</i></div>'%authors_to_string(book.authors), ] if not book.is_null('series'): series = book.format_field('series') if series[1]: parts.append('<div>%s: %s</div>'%series) if not book.is_null('rating'): style = 'style=\'font-family:"%s"\''%f parts.append('<div %s>%s</div>'%(style, '\u2605'*int(book.rating))) parts.append('</center>') if book.identifiers: urls = urls_from_identifiers(book.identifiers) ids = ['<a href="%s">%s</a>'%(url, name) for name, ign, ign, url in urls] if ids: parts.append('<div><b>%s:</b> %s</div><br>'%(_('See at'), ', '.join(ids))) if book.tags: parts.append('<div>%s</div><div>\u00a0</div>'%', '.join(book.tags)) if book.comments: parts.append(comments_to_html(book.comments)) self.show_details_signal.emit(''.join(parts))
def initialize_metadata_options(self): self.initialize_combos() self.author.editTextChanged.connect(self.deduce_author_sort) mi = self.db.get_metadata(self.book_id, index_is_id=True) self.title.setText(mi.title) self.publisher.show_initial_value(mi.publisher if mi.publisher else '') self.author_sort.setText(mi.author_sort if mi.author_sort else '') self.tags.setText(', '.join(mi.tags if mi.tags else [])) self.tags.update_items_cache(self.db.all_tags()) self.comment.html = comments_to_html( mi.comments) if mi.comments else '' self.series.show_initial_value(mi.series if mi.series else '') if mi.series_index is not None: try: self.series_index.setValue(mi.series_index) except: self.series_index.setValue(1.0) cover = self.db.cover(self.book_id, index_is_id=True) if cover: pm = QPixmap() pm.loadFromData(cover) if not pm.isNull(): self.cover.setPixmap(pm) self.cover_data = cover self.set_cover_tooltip(pm) else: self.cover.setPixmap(QPixmap(I('default_cover.png'))) self.cover.setToolTip(_('This book has no cover')) for x in ('author', 'series', 'publisher'): x = getattr(self, x) x.lineEdit().deselect() self.series_changed()
def show_details(self, index): f = rating_font() book = self.model().data(index, Qt.UserRole) parts = [ '<center>', '<h2>%s</h2>' % book.title, '<div><i>%s</i></div>' % authors_to_string(book.authors), ] if not book.is_null('series'): series = book.format_field('series') if series[1]: parts.append('<div>%s: %s</div>' % series) if not book.is_null('rating'): style = 'style=\'font-family:"%s"\'' % f parts.append('<div %s>%s</div>' % (style, '\u2605' * int(book.rating))) parts.append('</center>') if book.identifiers: urls = urls_from_identifiers(book.identifiers) ids = [ '<a href="%s">%s</a>' % (url, name) for name, ign, ign, url in urls ] if ids: parts.append('<div><b>%s:</b> %s</div><br>' % (_('See at'), ', '.join(ids))) if book.tags: parts.append('<div>%s</div><div>\u00a0</div>' % ', '.join(book.tags)) if book.comments: parts.append(comments_to_html(book.comments)) self.show_details_signal.emit(''.join(parts))
def setter(self, val): if not val or not val.strip(): val = '' else: val = comments_to_html(val) self._tb.html = val self._tb.wyswyg_dirtied()
def initialize_metadata_options(self): self.initialize_combos() self.author.editTextChanged.connect(self.deduce_author_sort) mi = self.db.get_metadata(self.book_id, index_is_id=True) self.title.setText(mi.title) self.publisher.show_initial_value(mi.publisher if mi.publisher else '') self.author_sort.setText(mi.author_sort if mi.author_sort else '') self.tags.setText(', '.join(mi.tags if mi.tags else [])) self.tags.update_items_cache(self.db.all_tags()) self.comment.html = comments_to_html(mi.comments) if mi.comments else '' self.series.show_initial_value(mi.series if mi.series else '') if mi.series_index is not None: try: self.series_index.setValue(mi.series_index) except: self.series_index.setValue(1.0) cover = self.db.cover(self.book_id, index_is_id=True) if cover: pm = QPixmap() pm.loadFromData(cover) if not pm.isNull(): self.cover.setPixmap(pm) self.cover_data = cover self.set_cover_tooltip(pm) else: self.cover.setPixmap(QPixmap(I('default_cover.png'))) self.cover.setToolTip(_('This book has no cover')) for x in ('author', 'series', 'publisher'): x = getattr(self, x) x.lineEdit().deselect()
def render_html(mi, css, vertical, widget, all_fields=False): # {{{ table = render_data( mi, all_fields=all_fields, use_roman_numbers=config['use_roman_numerals_for_series_number']) def color_to_string(col): ans = '#000000' if col.isValid(): col = col.toRgb() if col.isValid(): ans = unicode(col.name()) return ans fi = QFontInfo(QApplication.font(widget)) f = fi.pixelSize() + 1 + int(tweaks['change_book_details_font_size_by']) fam = unicode(fi.family()).strip().replace('"', '') if not fam: fam = 'sans-serif' c = color_to_string(QApplication.palette().color(QPalette.Normal, QPalette.WindowText)) templ = u'''\ <html> <head> <style type="text/css"> body, td { background-color: transparent; font-size: %dpx; font-family: "%s",sans-serif; color: %s } </style> <style type="text/css"> %s </style> </head> <body> %%s </body> <html> ''' % (f, fam, c, css) fm = getattr(mi, 'field_metadata', field_metadata) fl = dict(get_field_list(fm)) show_comments = (all_fields or fl.get('comments', True)) comments = u'' if mi.comments and show_comments: comments = comments_to_html(force_unicode(mi.comments)) right_pane = u'<div id="comments" class="comments">%s</div>' % comments if vertical: ans = templ % (table + right_pane) else: ans = templ % ( u'<table><tr><td valign="top" ' 'style="padding-right:2em; width:40%%">%s</td><td valign="top">%s</td></tr></table>' % (table, right_pane)) return ans
def add_field(field, db, book_id, ans, field_metadata): datatype = field_metadata.get('datatype') if datatype is not None: val = db._field_for(field, book_id) if val is not None and val not in empty_val: if datatype == 'datetime': val = encode_datetime(val) if val is None: return elif datatype == 'comments' or field == 'comments': ctype = field_metadata.get('display', {}).get('interpret_as', 'html') if ctype == 'markdown': val = markdown(val) elif ctype not in passthrough_comment_types: val = comments_to_html(val) elif datatype == 'composite' and field_metadata['display'].get('contains_html'): val = comments_to_html(val) ans[field] = val
def browse_render_details(self, id_): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args(mi, id_, add_category_links=True) args['formats'] = '' if fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'\ .format(fmt, fname, id_, fmt.upper(), self.opts.url_prefix) for fmt in fmts] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if m['is_custom'] and field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and \ m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((m['name'], r)) fields.sort(key=lambda x: sort_key(x[0])) fields = [u'<div class="field">{0}</div>'.format(f[1]) for f in fields] fields = u'<div class="fields">%s</div>'%('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [(u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments] comments = u'<div class="comments">%s</div>'%('\n\n'.join(comments)) return self.browse_details_template.format(id=id_, title=xml(mi.title, True), fields=fields, formats=args['formats'], comments=comments)
def render_html(mi, css, vertical, widget, all_fields=False): # {{{ table = render_data(mi, all_fields=all_fields, use_roman_numbers=config['use_roman_numerals_for_series_number']) def color_to_string(col): ans = '#000000' if col.isValid(): col = col.toRgb() if col.isValid(): ans = unicode(col.name()) return ans fi = QFontInfo(QApplication.font(widget)) f = fi.pixelSize() + 1 + int(tweaks['change_book_details_font_size_by']) fam = unicode(fi.family()).strip().replace('"', '') if not fam: fam = 'sans-serif' c = color_to_string(QApplication.palette().color(QPalette.Normal, QPalette.WindowText)) templ = u'''\ <html> <head> <style type="text/css"> body, td { background-color: transparent; font-size: %dpx; font-family: "%s",sans-serif; color: %s } </style> <style type="text/css"> %s </style> </head> <body> %%s </body> <html> '''%(f, fam, c, css) fm = getattr(mi, 'field_metadata', field_metadata) fl = dict(get_field_list(fm)) show_comments = (all_fields or fl.get('comments', True)) comments = u'' if mi.comments and show_comments: comments = comments_to_html(force_unicode(mi.comments)) right_pane = u'<div id="comments" class="comments">%s</div>'%comments if vertical: ans = templ%(table+right_pane) else: ans = templ%(u'<table><tr><td valign="top" ' 'style="padding-right:2em; width:40%%">%s</td><td valign="top">%s</td></tr></table>' % (table, right_pane)) return ans
def add_field(field, db, book_id, ans, field_metadata): datatype = field_metadata.get('datatype') if datatype is not None: val = db._field_for(field, book_id) if val is not None and val not in empty_val: if datatype == 'datetime': val = encode_datetime(val) if val is None: return elif datatype == 'comments' or field == 'comments': ctype = field_metadata.get('display', {}).get('interpret_as', 'html') if ctype == 'markdown': val = markdown(val) elif ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % prepare_string_for_xml(val) elif ctype == 'short-text': val = '<span">%s</span>' % prepare_string_for_xml(val) else: val = comments_to_html(val) elif datatype == 'composite' and field_metadata['display'].get('contains_html'): val = comments_to_html(val) ans[field] = val
def __init__(self, parent, text): QDialog.__init__(self, parent) Ui_CommentsDialog.__init__(self) self.setupUi(self) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) if text is not None: self.textbox.html = comments_to_html(text) # self.textbox.setTabChangesFocus(True) self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel'))
def __init__(self, parent, text): QDialog.__init__(self, parent) Ui_CommentsDialog.__init__(self) self.setupUi(self) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags() & (~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) if text is not None: self.textbox.html = comments_to_html(text) # self.textbox.setTabChangesFocus(True) self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel'))
def __init__(self, parent, text, column_name=None): QDialog.__init__(self, parent) Ui_CommentsDialog.__init__(self) self.setupUi(self) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint)) self.setWindowIcon(icon) self.textbox.html = comments_to_html(text) if text else '' self.textbox.wyswyg_dirtied() # self.textbox.setTabChangesFocus(True) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('O&K')) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel')) if column_name: self.setWindowTitle(_('Edit "{0}"').format(column_name)) geom = gprefs.get('comments_dialog_geom', None) if geom is not None: QApplication.instance().safe_restore_geometry(self, geom)
def __init__(self, parent, text, column_name=None): QDialog.__init__(self, parent) Ui_CommentsDialog.__init__(self) self.setupUi(self) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) self.textbox.html = comments_to_html(text) if text else '' self.textbox.wyswyg_dirtied() # self.textbox.setTabChangesFocus(True) self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) if column_name: self.setWindowTitle(_('Edit "{0}"').format(column_name)) geom = gprefs.get('comments_dialog_geom', None) if geom is not None: self.restoreGeometry(geom)
def show_details(self, index): f = rating_font() book = self.model().data(index, Qt.UserRole) parts = ["<center>", "<h2>%s</h2>" % book.title, "<div><i>%s</i></div>" % authors_to_string(book.authors)] if not book.is_null("series"): series = book.format_field("series") if series[1]: parts.append("<div>%s: %s</div>" % series) if not book.is_null("rating"): style = "style='font-family:\"%s\"'" % f parts.append("<div %s>%s</div>" % (style, "\u2605" * int(book.rating))) parts.append("</center>") if book.identifiers: urls = urls_from_identifiers(book.identifiers) ids = ['<a href="%s">%s</a>' % (url, name) for name, ign, ign, url in urls] if ids: parts.append("<div><b>%s:</b> %s</div><br>" % (_("See at"), ", ".join(ids))) if book.tags: parts.append("<div>%s</div><div>\u00a0</div>" % ", ".join(book.tags)) if book.comments: parts.append(comments_to_html(book.comments)) self.show_details_signal.emit("".join(parts))
def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix): FM = db.FIELD_MAP title = item[FM['title']] if not title: title = _('Unknown') authors = item[FM['authors']] if not authors: authors = _('Unknown') authors = ' & '.join([i.replace('|', ',') for i in authors.split(',')]) extra = [] rating = item[FM['rating']] if rating > 0: rating = u''.join(repeat(u'\u2605', int(rating/2.))) extra.append(_('RATING: %s<br />')%rating) tags = item[FM['tags']] if tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(tags, ',', ignore_max=True, no_tag_count=True))) series = item[FM['series']] if series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')%\ dict(series=xml(series), sidx=fmt_sidx(float(item[FM['series_index']])))) for key in CKEYS: mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) 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, 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' or (CFM[key]['datatype'] == 'composite' and CFM[key]['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(unicode(val)))) comments = item[FM['comments']] if comments: comments = comments_to_html(comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) idm = 'calibre' if version == 0 else 'uuid' id_ = 'urn:%s:%s'%(idm, item[FM['uuid']]) ans = E.entry(TITLE(title), E.author(E.name(authors)), ID(id_), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) formats = item[FM['formats']] if formats: for fmt in formats.split(','): fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] href = prefix + '/get/%s/%s'%(fmt, item[FM['id']]) if mt: link = E.link(type=mt, href=href) if version > 0: link.set('rel', "http://opds-spec.org/acquisition") ans.append(link) ans.append(E.link(type='image/jpeg', href=prefix+'/get/cover/%s'%item[FM['id']], rel="x-stanza-cover-image" if version == 0 else "http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=prefix+'/get/thumb/%s'%item[FM['id']], rel="x-stanza-cover-image-thumbnail" if version == 0 else "http://opds-spec.org/thumbnail")) return ans
def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix): FM = db.FIELD_MAP title = item[FM["title"]] if not title: title = _("Unknown") authors = item[FM["authors"]] if not authors: authors = _("Unknown") authors = " & ".join([i.replace("|", ",") for i in authors.split(",")]) extra = [] rating = item[FM["rating"]] if rating > 0: rating = u"".join(repeat(u"\u2605", int(rating / 2.0))) extra.append(_("RATING: %s<br />") % rating) tags = item[FM["tags"]] if tags: extra.append(_("TAGS: %s<br />") % xml(format_tag_string(tags, ",", ignore_max=True, no_tag_count=True))) series = item[FM["series"]] if series: extra.append( _("SERIES: %(series)s [%(sidx)s]<br />") % dict(series=xml(series), sidx=fmt_sidx(float(item[FM["series_index"]]))) ) for key in CKEYS: mi = db.get_metadata(item[CFM["id"]["rec_index"]], index_is_id=True) name, val = mi.format_field(key) 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, 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" or ( CFM[key]["datatype"] == "composite" and CFM[key]["display"].get("contains_html", False) ): extra.append("%s: %s<br />" % (xml(name), comments_to_html(unicode(val)))) else: extra.append("%s: %s<br />" % (xml(name), xml(unicode(val)))) comments = item[FM["comments"]] if comments: comments = comments_to_html(comments) extra.append(comments) if extra: extra = html_to_lxml("\n".join(extra)) idm = "calibre" if version == 0 else "uuid" id_ = "urn:%s:%s" % (idm, item[FM["uuid"]]) ans = E.entry(TITLE(title), E.author(E.name(authors)), ID(id_), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type="xhtml")) formats = item[FM["formats"]] if formats: for fmt in formats.split(","): fmt = fmt.lower() mt = guess_type("a." + fmt)[0] href = prefix + "/get/%s/%s" % (fmt, item[FM["id"]]) if mt: link = E.link(type=mt, href=href) if version > 0: link.set("rel", "http://opds-spec.org/acquisition") ans.append(link) ans.append( E.link( type="image/jpeg", href=prefix + "/get/cover/%s" % item[FM["id"]], rel="x-stanza-cover-image" if version == 0 else "http://opds-spec.org/cover", ) ) ans.append( E.link( type="image/jpeg", href=prefix + "/get/thumb/%s" % item[FM["id"]], rel="x-stanza-cover-image-thumbnail" if version == 0 else "http://opds-spec.org/thumbnail", ) ) return 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 += ':' 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 browse_booklist_page(self, ids=None, sort=None): if sort == "null": sort = None if ids is None: ids = json.dumps("[]") try: ids = json.loads(ids) except: raise cherrypy.HTTPError(404, "invalid ids") summs = [] for id_ in ids: try: id_ = int(id_) mi = self.db.get_metadata(id_, index_is_id=True) except: continue args, fmt, fmts, fname = self.browse_get_book_args(mi, id_) args["other_formats"] = "" args["fmt"] = fmt if fmts and fmt: other_fmts = [x for x in fmts if x.lower() != fmt.lower()] if other_fmts: ofmts = [ u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'.format( f, fname, id_, f.upper(), self.opts.url_prefix ) for f in other_fmts ] ofmts = ", ".join(ofmts) args["other_formats"] = u"<strong>%s: </strong>" % _("Other formats") + ofmts args["details_href"] = self.opts.url_prefix + "/browse/details/" + str(id_) if fmt: href = self.opts.url_prefix + "/get/%s/%s_%d.%s" % (fmt, fname, id_, fmt) rt = xml(_("Read %(title)s in the %(fmt)s format") % {"title": args["title"], "fmt": fmt.upper()}, True) args["get_button"] = '<a href="%s" class="read" title="%s">%s</a>' % ( xml(href, True), rt, xml(_("Get")), ) args["get_url"] = xml(href, True) else: args["get_button"] = "" args["get_url"] = "javascript:alert('%s')" % xml(_("This book has no available formats to view"), True) args["comments"] = comments_to_html(mi.comments) args["stars"] = "" if mi.rating: args["stars"] = render_rating(mi.rating / 2.0, self.opts.url_prefix, prefix=_("Rating"))[0] if args["tags"]: args["tags"] = u"<strong>%s: </strong>" % xml(_("Tags")) + args["tags"] if args["series"]: args["series"] = args["series"] args["details"] = xml(_("Details"), True) args["details_tt"] = xml(_("Show book details"), True) args["permalink"] = xml(_("Permalink"), True) args["permalink_tt"] = xml(_("A permanent link to this book"), True) summs.append(self.browse_summary_template.format(**args)) raw = json.dumps("\n".join(summs), ensure_ascii=True) return raw
def render_jacket(mi, output_profile, alt_title=_('Unknown'), alt_tags=[], alt_comments='', alt_publisher=('')): css = P('jacket/stylesheet.css', data=True).decode('utf-8') try: title_str = mi.title if mi.title else alt_title except: title_str = _('Unknown') title = '<span class="title">%s</span>' % (escape(title_str)) series = escape(mi.series if mi.series else '') if mi.series and mi.series_index is not None: series += escape(' [%s]'%mi.format_series_index()) if not mi.series: series = '' try: publisher = mi.publisher if mi.publisher else alt_publisher except: publisher = '' try: if is_date_undefined(mi.pubdate): pubdate = '' else: pubdate = strftime(u'%Y', mi.pubdate.timetuple()) except: pubdate = '' rating = get_rating(mi.rating, output_profile.ratings_char, output_profile.empty_ratings_char) tags = mi.tags if mi.tags else alt_tags if tags: tags = output_profile.tags_to_string(tags) else: tags = '' comments = mi.comments if mi.comments else alt_comments comments = comments.strip() orig_comments = comments if comments: comments = comments_to_html(comments) try: author = mi.format_authors() except: author = '' def generate_html(comments): args = dict(xmlns=XHTML_NS, title_str=title_str, css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=_('Series'), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='' ) for key in mi.custom_field_keys(): try: display_name, val = mi.format_field_extended(key)[:2] key = key.replace('#', '_') args[key] = escape(val) args[key+'_label'] = escape(display_name) except: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" %s: %s" % ('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') formatter = SafeFormatter() generated_html = formatter.format(P('jacket/template.xhtml', data=True).decode('utf-8'), **args) # Post-process the generated html to strip out empty header items soup = BeautifulSoup(generated_html) if not series: series_tag = soup.find(attrs={'class':'cbj_series'}) if series_tag is not None: series_tag.extract() if not rating: rating_tag = soup.find(attrs={'class':'cbj_rating'}) if rating_tag is not None: rating_tag.extract() if not tags: tags_tag = soup.find(attrs={'class':'cbj_tags'}) if tags_tag is not None: tags_tag.extract() if not pubdate: pubdate_tag = soup.find(attrs={'class':'cbj_pubdata'}) if pubdate_tag is not None: pubdate_tag.extract() if output_profile.short_name != 'kindle': hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'}) if hr_tag is not None: hr_tag.extract() return strip_encoding_declarations( soup.renderContents('utf-8').decode('utf-8')) from calibre.ebooks.oeb.base import RECOVER_PARSER try: root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER) except: try: root = etree.fromstring(generate_html(escape(orig_comments)), parser=RECOVER_PARSER) except: root = etree.fromstring(generate_html(''), parser=RECOVER_PARSER) return root
def browse_booklist_page(self, ids=None, sort=None): if sort == 'null': sort = None if ids is None: ids = json.dumps('[]') try: ids = json.loads(ids) except: raise cherrypy.HTTPError(404, 'invalid ids') summs = [] for id_ in ids: try: id_ = int(id_) mi = self.db.get_metadata(id_, index_is_id=True) except: continue args, fmt, fmts, fname = self.browse_get_book_args(mi, id_) args['other_formats'] = '' args['fmt'] = fmt if fmts and fmt: other_fmts = [x for x in fmts if x.lower() != fmt.lower()] if other_fmts: ofmts = [ u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>' .format(f, fname, id_, f.upper(), self.opts.url_prefix) for f in other_fmts ] ofmts = ', '.join(ofmts) args['other_formats'] = u'<strong>%s: </strong>' % \ _('Other formats') + ofmts args[ 'details_href'] = self.opts.url_prefix + '/browse/details/' + str( id_) if fmt: href = self.opts.url_prefix + '/get/%s/%s_%d.%s' % (fmt, fname, id_, fmt) rt = xml( _('Read %(title)s in the %(fmt)s format') % { 'title': args['title'], 'fmt': fmt.upper() }, True) args['get_button'] = \ '<a href="%s" class="read" title="%s">%s</a>' % \ (xml(href, True), rt, xml(_('Get'))) args['get_url'] = xml(href, True) else: args['get_button'] = '' args['get_url'] = 'javascript:alert(\'%s\')' % xml( _('This book has no available formats to view'), True) args['comments'] = comments_to_html(mi.comments) args['stars'] = '' if mi.rating: args['stars'] = render_rating(mi.rating / 2.0, self.opts.url_prefix, prefix=_('Rating'))[0] if args['tags']: args['tags'] = u'<strong>%s: </strong>'%xml(_('Tags')) + \ args['tags'] if args['series']: args['series'] = args['series'] args['details'] = xml(_('Details'), True) args['details_tt'] = xml(_('Show book details'), True) args['permalink'] = xml(_('Permalink'), True) args['permalink_tt'] = xml(_('A permanent link to this book'), True) summs.append(self.browse_summary_template.format(**args)) raw = json.dumps('\n'.join(summs), ensure_ascii=True) return raw
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(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
def render_jacket(mi, output_profile, alt_title=_('Unknown'), alt_tags=[], alt_comments='', alt_publisher='', rescale_fonts=False, alt_authors=None): css = P('jacket/stylesheet.css', data=True).decode('utf-8') template = P('jacket/template.xhtml', data=True).decode('utf-8') template = re.sub(r'<!--.*?-->', '', template, flags=re.DOTALL) css = re.sub(r'/\*.*?\*/', '', css, flags=re.DOTALL) try: title_str = alt_title if mi.is_null('title') else mi.title except: title_str = _('Unknown') title_str = escape(title_str) title = '<span class="title">%s</span>' % title_str series = Series(mi.series, mi.series_index) try: publisher = mi.publisher if not mi.is_null( 'publisher') else alt_publisher except: publisher = '' publisher = escape(publisher) try: if is_date_undefined(mi.pubdate): pubdate = '' else: dt = as_local_time(mi.pubdate) pubdate = strftime('%Y', dt.timetuple()) except: pubdate = '' rating = get_rating(mi.rating, output_profile.ratings_char, output_profile.empty_ratings_char) tags = Tags((mi.tags if mi.tags else alt_tags), output_profile) comments = mi.comments if mi.comments else alt_comments comments = comments.strip() if comments: comments = comments_to_html(comments) orig = mi.authors if mi.is_null('authors'): mi.authors = list(alt_authors or (_('Unknown'), )) try: author = mi.format_authors() except: author = '' mi.authors = orig author = escape(author) has_data = {} def generate_html(comments): display = Attributes() args = dict( xmlns=XHTML_NS, title_str=title_str, identifiers=Identifiers(mi.identifiers), css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=ngettext('Series', 'Series', 1), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='', display=display, searchable_tags=' '.join( escape(t) + 'ttt' for t in tags.tags_list), ) for key in mi.custom_field_keys(): m = mi.get_user_metadata(key, False) or {} try: display_name, val = mi.format_field_extended(key)[:2] dkey = key.replace('#', '_') dt = m.get('datatype') if dt == 'series': args[dkey] = Series(mi.get(key), mi.get(key + '_index')) elif dt == 'rating': args[dkey] = rating_to_stars( mi.get(key), m.get('display', {}).get('allow_half_stars', False)) elif dt == 'comments': val = val or '' ctype = m.get('display', {}).get('interpret_as') or 'html' if ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % escape( val) elif ctype == 'short-text': val = '<span>%s</span>' % escape(val) elif ctype == 'markdown': val = markdown(val) else: val = comments_to_html(val) args[dkey] = val else: args[dkey] = escape(val) args[dkey + '_label'] = escape(display_name) setattr(display, dkey, 'none' if mi.is_null(key) else 'initial') except Exception: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" {}: {}".format('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') has_data['series'] = bool(series) has_data['tags'] = bool(tags) has_data['rating'] = bool(rating) has_data['pubdate'] = bool(pubdate) for k, v in has_data.items(): setattr(display, k, 'initial' if v else 'none') display.title = 'initial' if mi.identifiers: display.identifiers = 'initial' formatter = SafeFormatter() generated_html = formatter.format(template, **args) return strip_encoding_declarations(generated_html) from calibre.ebooks.oeb.polish.parsing import parse raw = generate_html(comments) root = parse(raw, line_numbers=False, force_html5_parse=True) if rescale_fonts: # We ensure that the conversion pipeline will set the font sizes for # text in the jacket to the same size as the font sizes for the rest of # the text in the book. That means that as long as the jacket uses # relative font sizes (em or %), the post conversion font size will be # the same as for text in the main book. So text with size x em will # be rescaled to the same value in both the jacket and the main content. # # We cannot use data-calibre-rescale 100 on the body tag as that will just # give the body tag a font size of 1em, which is useless. for body in root.xpath('//*[local-name()="body"]'): fw = body.makeelement(XHTML('div')) fw.set('data-calibre-rescale', '100') for child in body: fw.append(child) body.append(fw) postprocess_jacket(root, output_profile, has_data) from calibre.ebooks.oeb.polish.pretty import pretty_html_tree pretty_html_tree(None, root) return root
def render_jacket(mi, output_profile, alt_title=_('Unknown'), alt_tags=[], alt_comments='', alt_publisher=(''), rescale_fonts=False): css = P('jacket/stylesheet.css', data=True).decode('utf-8') template = P('jacket/template.xhtml', data=True).decode('utf-8') template = re.sub(r'<!--.*?-->', '', template, flags=re.DOTALL) css = re.sub(r'/\*.*?\*/', '', css, flags=re.DOTALL) try: title_str = mi.title if mi.title else alt_title except: title_str = _('Unknown') title_str = escape(title_str) title = '<span class="title">%s</span>' % title_str series = Series(mi.series, mi.series_index) try: publisher = mi.publisher if mi.publisher else alt_publisher except: publisher = '' publisher = escape(publisher) try: if is_date_undefined(mi.pubdate): pubdate = '' else: dt = as_local_time(mi.pubdate) pubdate = strftime(u'%Y', dt.timetuple()) except: pubdate = '' rating = get_rating(mi.rating, output_profile.ratings_char, output_profile.empty_ratings_char) tags = Tags((mi.tags if mi.tags else alt_tags), output_profile) comments = mi.comments if mi.comments else alt_comments comments = comments.strip() orig_comments = comments if comments: comments = comments_to_html(comments) try: author = mi.format_authors() except: author = '' author = escape(author) def generate_html(comments): args = dict( xmlns=XHTML_NS, title_str=title_str, css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=_('Series'), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='', searchable_tags=' '.join( escape(t) + 'ttt' for t in tags.tags_list), ) for key in mi.custom_field_keys(): m = mi.get_user_metadata(key, False) or {} try: display_name, val = mi.format_field_extended(key)[:2] dkey = key.replace('#', '_') dt = m.get('datatype') if dt == 'series': args[dkey] = Series(mi.get(key), mi.get(key + '_index')) elif dt == 'rating': args[dkey] = rating_to_stars( mi.get(key), m.get('display', {}).get('allow_half_stars', False)) else: args[dkey] = escape(val) args[dkey + '_label'] = escape(display_name) except Exception: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" %s: %s" % ('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') formatter = SafeFormatter() generated_html = formatter.format(template, **args) # Post-process the generated html to strip out empty header items soup = BeautifulSoup(generated_html) if not series: series_tag = soup.find(attrs={'class': 'cbj_series'}) if series_tag is not None: series_tag.extract() if not rating: rating_tag = soup.find(attrs={'class': 'cbj_rating'}) if rating_tag is not None: rating_tag.extract() if not tags: tags_tag = soup.find(attrs={'class': 'cbj_tags'}) if tags_tag is not None: tags_tag.extract() if not pubdate: pubdate_tag = soup.find(attrs={'class': 'cbj_pubdata'}) if pubdate_tag is not None: pubdate_tag.extract() if output_profile.short_name != 'kindle': hr_tag = soup.find('hr', attrs={'class': 'cbj_kindle_banner_hr'}) if hr_tag is not None: hr_tag.extract() return strip_encoding_declarations( soup.renderContents('utf-8').decode('utf-8')) from calibre.ebooks.oeb.base import RECOVER_PARSER try: root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER) except: try: root = etree.fromstring(generate_html(escape(orig_comments)), parser=RECOVER_PARSER) except: root = etree.fromstring(generate_html(''), parser=RECOVER_PARSER) if rescale_fonts: # We ensure that the conversion pipeline will set the font sizes for # text in the jacket to the same size as the font sizes for the rest of # the text in the book. That means that as long as the jacket uses # relative font sizes (em or %), the post conversion font size will be # the same as for text in the main book. So text with size x em will # be rescaled to the same value in both the jacket and the main content. # # We cannot use calibre_rescale_100 on the body tag as that will just # give the body tag a font size of 1em, which is useless. for body in root.xpath('//*[local-name()="body"]'): fw = body.makeelement(XHTML('div')) fw.set('class', 'calibre_rescale_100') for child in body: fw.append(child) body.append(fw) from calibre.ebooks.oeb.polish.pretty import pretty_html_tree pretty_html_tree(None, root) return root
def setter(self, val): if val is None: val = '' self._tb.html = comments_to_html(val)
def render_jacket(mi, output_profile, alt_title=_('Unknown'), alt_tags=[], alt_comments='', alt_publisher='', rescale_fonts=False, alt_authors=None): css = P('jacket/stylesheet.css', data=True).decode('utf-8') template = P('jacket/template.xhtml', data=True).decode('utf-8') template = re.sub(r'<!--.*?-->', '', template, flags=re.DOTALL) css = re.sub(r'/\*.*?\*/', '', css, flags=re.DOTALL) try: title_str = alt_title if mi.is_null('title') else mi.title except: title_str = _('Unknown') title_str = escape(title_str) title = '<span class="title">%s</span>' % title_str series = Series(mi.series, mi.series_index) try: publisher = mi.publisher if not mi.is_null('publisher') else alt_publisher except: publisher = '' publisher = escape(publisher) try: if is_date_undefined(mi.pubdate): pubdate = '' else: dt = as_local_time(mi.pubdate) pubdate = strftime(u'%Y', dt.timetuple()) except: pubdate = '' rating = get_rating(mi.rating, output_profile.ratings_char, output_profile.empty_ratings_char) tags = Tags((mi.tags if mi.tags else alt_tags), output_profile) comments = mi.comments if mi.comments else alt_comments comments = comments.strip() orig_comments = comments if comments: comments = comments_to_html(comments) orig = mi.authors if mi.is_null('authors'): mi.authors = list(alt_authors or (_('Unknown'),)) try: author = mi.format_authors() except: author = '' mi.authors = orig author = escape(author) has_data = {} def generate_html(comments): args = dict(xmlns=XHTML_NS, title_str=title_str, css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=_('Series'), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='', searchable_tags=' '.join(escape(t)+'ttt' for t in tags.tags_list), ) for key in mi.custom_field_keys(): m = mi.get_user_metadata(key, False) or {} try: display_name, val = mi.format_field_extended(key)[:2] dkey = key.replace('#', '_') dt = m.get('datatype') if dt == 'series': args[dkey] = Series(mi.get(key), mi.get(key + '_index')) elif dt == 'rating': args[dkey] = rating_to_stars(mi.get(key), m.get('display', {}).get('allow_half_stars', False)) elif dt == 'comments': val = val or '' display = m.get('display', {}) ctype = display.get('interpret_as') or 'html' if ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % escape(val) elif ctype == 'short-text': val = '<span>%s</span>' % escape(val) elif ctype == 'markdown': val = markdown(val) else: val = comments_to_html(val) args[dkey] = val else: args[dkey] = escape(val) args[dkey+'_label'] = escape(display_name) except Exception: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" %s: %s" % ('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') formatter = SafeFormatter() generated_html = formatter.format(template, **args) has_data['series'] = bool(series) has_data['tags'] = bool(tags) has_data['rating'] = bool(rating) has_data['pubdate'] = bool(pubdate) return strip_encoding_declarations(generated_html) from calibre.ebooks.oeb.base import RECOVER_PARSER try: root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER) except: try: root = etree.fromstring(generate_html(escape(orig_comments)), parser=RECOVER_PARSER) except: root = etree.fromstring(generate_html(''), parser=RECOVER_PARSER) if rescale_fonts: # We ensure that the conversion pipeline will set the font sizes for # text in the jacket to the same size as the font sizes for the rest of # the text in the book. That means that as long as the jacket uses # relative font sizes (em or %), the post conversion font size will be # the same as for text in the main book. So text with size x em will # be rescaled to the same value in both the jacket and the main content. # # We cannot use calibre_rescale_100 on the body tag as that will just # give the body tag a font size of 1em, which is useless. for body in root.xpath('//*[local-name()="body"]'): fw = body.makeelement(XHTML('div')) fw.set('class', 'calibre_rescale_100') for child in body: fw.append(child) body.append(fw) postprocess_jacket(root, output_profile, has_data) from calibre.ebooks.oeb.polish.pretty import pretty_html_tree pretty_html_tree(None, root) return root
def render_jacket(mi, output_profile, alt_title=_('Unknown'), alt_tags=[], alt_comments='', alt_publisher=(''), rescale_fonts=False): css = P('jacket/stylesheet.css', data=True).decode('utf-8') template = P('jacket/template.xhtml', data=True).decode('utf-8') try: title_str = mi.title if mi.title else alt_title except: title_str = _('Unknown') title = '<span class="title">%s</span>' % (escape(title_str)) series = Series(mi.series, mi.series_index) try: publisher = mi.publisher if mi.publisher else alt_publisher except: publisher = '' try: if is_date_undefined(mi.pubdate): pubdate = '' else: pubdate = strftime(u'%Y', mi.pubdate.timetuple()) except: pubdate = '' rating = get_rating(mi.rating, output_profile.ratings_char, output_profile.empty_ratings_char) tags = Tags((mi.tags if mi.tags else alt_tags), output_profile) comments = mi.comments if mi.comments else alt_comments comments = comments.strip() orig_comments = comments if comments: comments = comments_to_html(comments) try: author = mi.format_authors() except: author = '' def generate_html(comments): args = dict(xmlns=XHTML_NS, title_str=title_str, css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=_('Series'), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='', searchable_tags=' '.join(escape(t)+'ttt' for t in tags.tags_list), ) for key in mi.custom_field_keys(): try: display_name, val = mi.format_field_extended(key)[:2] key = key.replace('#', '_') args[key] = escape(val) args[key+'_label'] = escape(display_name) except: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" %s: %s" % ('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') formatter = SafeFormatter() generated_html = formatter.format(template, **args) # Post-process the generated html to strip out empty header items soup = BeautifulSoup(generated_html) if not series: series_tag = soup.find(attrs={'class':'cbj_series'}) if series_tag is not None: series_tag.extract() if not rating: rating_tag = soup.find(attrs={'class':'cbj_rating'}) if rating_tag is not None: rating_tag.extract() if not tags: tags_tag = soup.find(attrs={'class':'cbj_tags'}) if tags_tag is not None: tags_tag.extract() if not pubdate: pubdate_tag = soup.find(attrs={'class':'cbj_pubdata'}) if pubdate_tag is not None: pubdate_tag.extract() if output_profile.short_name != 'kindle': hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'}) if hr_tag is not None: hr_tag.extract() return strip_encoding_declarations( soup.renderContents('utf-8').decode('utf-8')) from calibre.ebooks.oeb.base import RECOVER_PARSER try: root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER) except: try: root = etree.fromstring(generate_html(escape(orig_comments)), parser=RECOVER_PARSER) except: root = etree.fromstring(generate_html(''), parser=RECOVER_PARSER) if rescale_fonts: # We ensure that the conversion pipeline will set the font sizes for # text in the jacket to the same size as the font sizes for the rest of # the text in the book. That means that as long as the jacket uses # relative font sizes (em or %), the post conversion font size will be # the same as for text in the main book. So text with size x em will # be rescaled to the same value in both the jacket and the main content. # # We cannot use calibre_rescale_100 on the body tag as that will just # give the body tag a font size of 1em, which is useless. for body in root.xpath('//*[local-name()="body"]'): fw = body.makeelement(XHTML('div')) fw.set('class', 'calibre_rescale_100') for child in body: fw.append(child) body.append(fw) from calibre.ebooks.oeb.polish.pretty import pretty_html_tree pretty_html_tree(None, root) return root
def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix): FM = db.FIELD_MAP title = item[FM['title']] if not title: title = _('Unknown') authors = item[FM['authors']] if not authors: authors = _('Unknown') authors = ' & '.join([i.replace('|', ',') for i in authors.split(',')]) extra = [] rating = item[FM['rating']] if rating > 0: rating = u''.join(repeat(u'\u2605', int(rating / 2.))) extra.append(_('RATING: %s<br />') % rating) tags = item[FM['tags']] if tags: extra.append( _('TAGS: %s<br />') % xml( format_tag_string( tags, ',', ignore_max=True, no_tag_count=True))) series = item[FM['series']] if series: extra.append( _('SERIES: %(series)s [%(sidx)s]<br />') % dict(series=xml(series), sidx=fmt_sidx(float(item[FM['series_index']])))) for key in CKEYS: mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) 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, 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' or (CFM[key]['datatype'] == 'composite' and CFM[key]['display'].get( 'contains_html', False)): extra.append('%s: %s<br />' % (xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />' % (xml(name), xml(unicode(val)))) comments = item[FM['comments']] if comments: comments = comments_to_html(comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) idm = 'calibre' if version == 0 else 'uuid' id_ = 'urn:%s:%s' % (idm, item[FM['uuid']]) ans = E.entry(TITLE(title), E.author(E.name(authors)), ID(id_), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) formats = item[FM['formats']] if formats: for fmt in formats.split(','): fmt = fmt.lower() mt = guess_type('a.' + fmt)[0] href = prefix + '/get/%s/%s' % (fmt, item[FM['id']]) if mt: link = E.link(type=mt, href=href) if version > 0: link.set('rel', "http://opds-spec.org/acquisition") ans.append(link) ans.append( E.link(type='image/jpeg', href=prefix + '/get/cover/%s' % item[FM['id']], rel="x-stanza-cover-image" if version == 0 else "http://opds-spec.org/cover")) ans.append( E.link(type='image/jpeg', href=prefix + '/get/thumb/%s' % item[FM['id']], rel="x-stanza-cover-image-thumbnail" if version == 0 else "http://opds-spec.org/thumbnail")) return ans
def generate_html(comments): args = dict(xmlns=XHTML_NS, title_str=title_str, css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=_('Series'), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='', searchable_tags=' '.join(escape(t)+'ttt' for t in tags.tags_list), ) for key in mi.custom_field_keys(): m = mi.get_user_metadata(key, False) or {} try: display_name, val = mi.format_field_extended(key)[:2] dkey = key.replace('#', '_') dt = m.get('datatype') if dt == 'series': args[dkey] = Series(mi.get(key), mi.get(key + '_index')) elif dt == 'rating': args[dkey] = rating_to_stars(mi.get(key), m.get('display', {}).get('allow_half_stars', False)) elif dt == 'comments': val = val or '' display = m.get('display', {}) ctype = display.get('interpret_as') or 'html' if ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % escape(val) elif ctype == 'short-text': val = '<span>%s</span>' % escape(val) elif ctype == 'markdown': val = markdown(val) else: val = comments_to_html(val) args[dkey] = val else: args[dkey] = escape(val) args[dkey+'_label'] = escape(display_name) except Exception: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" %s: %s" % ('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') formatter = SafeFormatter() generated_html = formatter.format(template, **args) # Post-process the generated html to strip out empty header items soup = BeautifulSoup(generated_html) if not series: series_tag = soup.find(attrs={'class':'cbj_series'}) if series_tag is not None: series_tag.extract() if not rating: rating_tag = soup.find(attrs={'class':'cbj_rating'}) if rating_tag is not None: rating_tag.extract() if not tags: tags_tag = soup.find(attrs={'class':'cbj_tags'}) if tags_tag is not None: tags_tag.extract() if not pubdate: pubdate_tag = soup.find(attrs={'class':'cbj_pubdata'}) if pubdate_tag is not None: pubdate_tag.extract() if output_profile.short_name != 'kindle': hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'}) if hr_tag is not None: hr_tag.extract() return strip_encoding_declarations( soup.renderContents('utf-8').decode('utf-8'))
def browse_render_details(self, id_, add_random_button=False, add_title=False): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args( mi, id_, add_category_links=True) args['fmt'] = fmt if fmt: args['get_url'] = xml( self.opts.url_prefix + '/get/%s/%s_%d.%s' % (fmt, fname, id_, fmt), True) else: args['get_url'] = 'javascript:alert(\'%s\')' % xml( _('This book has no available formats to view'), True) args['formats'] = '' if fmts: ofmts = [ u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'. format(xfmt, fname, id_, xfmt.upper(), self.opts.url_prefix) for xfmt in fmts ] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if self.db.field_metadata.is_ignorable_field(field) and \ field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if field == 'identifiers': urls = urls_from_identifiers(mi.get(field, {})) links = [ u'<a class="details_category_link" target="_new" 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: fields.append( (field, m['name'], u'<strong>%s: </strong>%s' % (_('Ids'), links))) continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((field, m['name'], r)) def fsort(x): num = {'authors': 0, 'series': 1, 'tags': 2}.get(x[0], 100) return (num, sort_key(x[-1])) fields.sort(key=fsort) if add_title: fields.insert(0, ('title', 'Title', u'<strong>%s: </strong>%s' % (xml(_('Title')), xml(mi.title)))) fields = [ u'<div class="field">{0}</div>'.format(f[-1]) for f in fields ] fields = u'<div class="fields">%s</div>' % ('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [ (u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments ] comments = u'<div class="comments">%s</div>' % ( '\n\n'.join(comments)) random = '' if add_random_button: href = '%s/browse/random?v=%s' % (self.opts.url_prefix, time.time()) random = '<a href="%s" id="random_button" title="%s">%s</a>' % ( xml(href, True), xml(_('Choose another random book'), True), xml(_('Another random book'))) return self.browse_details_template.format(id=id_, title=xml( mi.title, True), fields=fields, get_url=args['get_url'], fmt=args['fmt'], formats=args['formats'], comments=comments, random=random)
def render_jacket(mi, output_profile, alt_title=_('Unknown'), alt_tags=[], alt_comments='', alt_publisher=('')): css = P('jacket/stylesheet.css', data=True).decode('utf-8') try: title_str = mi.title if mi.title else alt_title except: title_str = _('Unknown') title = '<span class="title">%s</span>' % (escape(title_str)) series = escape(mi.series if mi.series else '') if mi.series and mi.series_index is not None: series += escape(' [%s]' % mi.format_series_index()) if not mi.series: series = '' try: publisher = mi.publisher if mi.publisher else alt_publisher except: publisher = '' try: if is_date_undefined(mi.pubdate): pubdate = '' else: pubdate = strftime(u'%Y', mi.pubdate.timetuple()) except: pubdate = '' rating = get_rating(mi.rating, output_profile.ratings_char, output_profile.empty_ratings_char) tags = mi.tags if mi.tags else alt_tags if tags: tags = output_profile.tags_to_string(tags) else: tags = '' comments = mi.comments if mi.comments else alt_comments comments = comments.strip() orig_comments = comments if comments: comments = comments_to_html(comments) try: author = mi.format_authors() except: author = '' def generate_html(comments): args = dict(xmlns=XHTML_NS, title_str=title_str, css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=_('Series'), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='') for key in mi.custom_field_keys(): try: display_name, val = mi.format_field_extended(key)[:2] key = key.replace('#', '_') args[key] = escape(val) args[key + '_label'] = escape(display_name) except: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" %s: %s" % ('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') generated_html = P('jacket/template.xhtml', data=True).decode('utf-8').format(**args) # Post-process the generated html to strip out empty header items soup = BeautifulSoup(generated_html) if not series: series_tag = soup.find(attrs={'class': 'cbj_series'}) if series_tag is not None: series_tag.extract() if not rating: rating_tag = soup.find(attrs={'class': 'cbj_rating'}) if rating_tag is not None: rating_tag.extract() if not tags: tags_tag = soup.find(attrs={'class': 'cbj_tags'}) if tags_tag is not None: tags_tag.extract() if not pubdate: pubdate_tag = soup.find(attrs={'class': 'cbj_pubdata'}) if pubdate_tag is not None: pubdate_tag.extract() if output_profile.short_name != 'kindle': hr_tag = soup.find('hr', attrs={'class': 'cbj_kindle_banner_hr'}) if hr_tag is not None: hr_tag.extract() return strip_encoding_declarations( soup.renderContents('utf-8').decode('utf-8')) from calibre.ebooks.oeb.base import RECOVER_PARSER try: root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER) except: try: root = etree.fromstring(generate_html(escape(orig_comments)), parser=RECOVER_PARSER) except: root = etree.fromstring(generate_html(''), parser=RECOVER_PARSER) return root
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 browse_render_details(self, id_, add_random_button=False): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args(mi, id_, add_category_links=True) args['fmt'] = fmt if fmt: args['get_url'] = xml(self.opts.url_prefix + '/get/%s/%s_%d.%s'%( fmt, fname, id_, fmt), True) else: args['get_url'] = '' args['formats'] = '' if fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>' .format(xfmt, fname, id_, xfmt.upper(), self.opts.url_prefix) for xfmt in fmts] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if self.db.field_metadata.is_ignorable_field(field) and \ field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if field == 'identifiers': urls = urls_from_identifiers(mi.get(field, {})) links = [u'<a class="details_category_link" target="_new" 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: fields.append((m['name'], u'<strong>%s: </strong>%s'%( _('Ids'), links))) continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((m['name'], r)) fields.sort(key=lambda x: sort_key(x[0])) fields = [u'<div class="field">{0}</div>'.format(f[1]) for f in fields] fields = u'<div class="fields">%s</div>'%('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [(u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments] comments = u'<div class="comments">%s</div>'%('\n\n'.join(comments)) random = '' if add_random_button: href = '%s/browse/random?v=%s'%( self.opts.url_prefix, time.time()) random = '<a href="%s" id="random_button" title="%s">%s</a>' % ( xml(href, True), xml(_('Choose another random book'), True), xml(_('Another random book'))) return self.browse_details_template.format( id=id_, title=xml(mi.title, True), fields=fields, get_url=args['get_url'], fmt=args['fmt'], formats=args['formats'], comments=comments, random=random)
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 ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = u''.join(repeat(u'\u2605', int(mi.rating / 2.))) extra.append(_('RATING: %s<br />') % rating) if mi.tags: extra.append( _('TAGS: %s<br />') % xml(format_tag_string(mi.tags, None, no_tag_count=True))) if mi.series: extra.append( _('SERIES: %(series)s [%(sidx)s]<br />') % dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in field_metadata.ignorable_field_keys(): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append( '%s: %s<br />' % (xml(name), xml( format_tag_string( val, fm['is_multiple']['ui_to_list'], no_tag_count=True, joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get( 'contains_html', False)): extra.append('%s: %s<br />' % (xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />' % (xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.' + fmt)[0] if mt: ans.append( E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition")) ans.append( E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append( E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) return ans
def browse_render_details(self, id_, add_random_button=False, add_title=False): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _("This book has been deleted") else: args, fmt, fmts, fname = self.browse_get_book_args(mi, id_, add_category_links=True) args["fmt"] = fmt if fmt: args["get_url"] = xml(self.opts.url_prefix + "/get/%s/%s_%d.%s" % (fmt, fname, id_, fmt), True) else: args["get_url"] = "javascript:alert('%s')" % xml(_("This book has no available formats to view"), True) args["formats"] = "" if fmts: ofmts = [ u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'.format( xfmt, fname, id_, xfmt.upper(), self.opts.url_prefix ) for xfmt in fmts ] ofmts = ", ".join(ofmts) args["formats"] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + list( mi.get_all_user_metadata(False).items() ): if self.db.field_metadata.is_ignorable_field(field) and field not in displayed_custom_fields: continue if ( m["datatype"] == "comments" or field == "comments" or (m["datatype"] == "composite" and m["display"].get("contains_html", False)) ): val = mi.get(field, "") if val and val.strip(): comments.append((m["name"], comments_to_html(val))) continue if field in ("title", "formats") or not args.get(field, False) or not m["name"]: continue if field == "identifiers": urls = urls_from_identifiers(mi.get(field, {})) links = [ u'<a class="details_category_link" target="_new" 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: fields.append((field, m["name"], u"<strong>%s: </strong>%s" % (_("Ids"), links))) continue if m["datatype"] == "rating": r = ( u"<strong>%s: </strong>" % xml(m["name"]) + render_rating(mi.get(field) / 2.0, self.opts.url_prefix, prefix=m["name"])[0] ) else: r = u"<strong>%s: </strong>" % xml(m["name"]) + args[field] fields.append((field, m["name"], r)) def fsort(x): num = {"authors": 0, "series": 1, "tags": 2}.get(x[0], 100) return (num, sort_key(x[-1])) fields.sort(key=fsort) if add_title: fields.insert(0, ("title", "Title", u"<strong>%s: </strong>%s" % (xml(_("Title")), xml(mi.title)))) fields = [u'<div class="field">{0}</div>'.format(f[-1]) for f in fields] fields = u'<div class="fields">%s</div>' % ("\n\n".join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [ (u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments ] comments = u'<div class="comments">%s</div>' % ("\n\n".join(comments)) random = "" if add_random_button: href = "%s/browse/random?v=%s" % (self.opts.url_prefix, time.time()) random = '<a href="%s" id="random_button" title="%s">%s</a>' % ( xml(href, True), xml(_("Choose another random book"), True), xml(_("Another random book")), ) return self.browse_details_template.format( id=id_, title=xml(mi.title, True), fields=fields, get_url=args["get_url"], fmt=args["fmt"], formats=args["formats"], comments=comments, random=random, )
def generate_html(comments): args = dict(xmlns=XHTML_NS, title_str=title_str, css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=_('Series'), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='', searchable_tags=' '.join(escape(t)+'ttt' for t in tags.tags_list), ) for key in mi.custom_field_keys(): m = mi.get_user_metadata(key, False) or {} try: display_name, val = mi.format_field_extended(key)[:2] dkey = key.replace('#', '_') dt = m.get('datatype') if dt == 'series': args[dkey] = Series(mi.get(key), mi.get(key + '_index')) elif dt == 'rating': args[dkey] = rating_to_stars(mi.get(key), m.get('display', {}).get('allow_half_stars', False)) elif dt == 'comments': val = val or '' display = m.get('display', {}) ctype = display.get('interpret_as') or 'html' if ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % escape(val) elif ctype == 'short-text': val = '<span>%s</span>' % escape(val) elif ctype == 'markdown': val = markdown(val) else: val = comments_to_html(val) args[dkey] = val else: args[dkey] = escape(val) args[dkey+'_label'] = escape(display_name) except Exception: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" %s: %s" % ('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') formatter = SafeFormatter() generated_html = formatter.format(template, **args) has_data['series'] = bool(series) has_data['tags'] = bool(tags) has_data['rating'] = bool(rating) has_data['pubdate'] = bool(pubdate) return strip_encoding_declarations(generated_html)
def browse_booklist_page(self, ids=None, sort=None): if sort == 'null': sort = None if ids is None: ids = json.dumps('[]') try: ids = json.loads(ids) except: raise cherrypy.HTTPError(404, 'invalid ids') summs = [] for id_ in ids: try: id_ = int(id_) mi = self.db.get_metadata(id_, index_is_id=True) except: continue args, fmt, fmts, fname = self.browse_get_book_args(mi, id_) args['other_formats'] = '' if fmts and fmt: other_fmts = [x for x in fmts if x.lower() != fmt.lower()] if other_fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'\ .format(f, fname, id_, f.upper(), self.opts.url_prefix) for f in other_fmts] ofmts = ', '.join(ofmts) args['other_formats'] = u'<strong>%s: </strong>' % \ _('Other formats') + ofmts args['details_href'] = self.opts.url_prefix + '/browse/details/'+str(id_) if fmt: href = self.opts.url_prefix + '/get/%s/%s_%d.%s'%( fmt, fname, id_, fmt) rt = xml(_('Read %(title)s in the %(fmt)s format')% \ {'title':args['title'], 'fmt':fmt.upper()}, True) args['get_button'] = \ '<a href="%s" class="read" title="%s">%s</a>' % \ (xml(href, True), rt, xml(_('Get'))) else: args['get_button'] = '' args['comments'] = comments_to_html(mi.comments) args['stars'] = '' if mi.rating: args['stars'] = render_rating(mi.rating/2.0, self.opts.url_prefix, prefix=_('Rating'))[0] if args['tags']: args['tags'] = u'<strong>%s: </strong>'%xml(_('Tags')) + \ args['tags'] if args['series']: args['series'] = args['series'] args['details'] = xml(_('Details'), True) args['details_tt'] = xml(_('Show book details'), True) args['permalink'] = xml(_('Permalink'), True) args['permalink_tt'] = xml(_('A permanent link to this book'), True) summs.append(self.browse_summary_template.format(**args)) raw = json.dumps('\n'.join(summs), ensure_ascii=False) return raw
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 browse_render_details(self, id_): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args( mi, id_, add_category_links=True) args['formats'] = '' if fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'\ .format(fmt, fname, id_, fmt.upper(), self.opts.url_prefix) for fmt in fmts] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if m['is_custom'] and field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and \ m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((m['name'], r)) fields.sort(key=lambda x: sort_key(x[0])) fields = [ u'<div class="field">{0}</div>'.format(f[1]) for f in fields ] fields = u'<div class="fields">%s</div>' % ('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [ (u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments ] comments = u'<div class="comments">%s</div>' % ( '\n\n'.join(comments)) return self.browse_details_template.format(id=id_, title=xml( mi.title, True), fields=fields, formats=args['formats'], comments=comments)
def generate_html(comments): display = Attributes() args = dict( xmlns=XHTML_NS, title_str=title_str, identifiers=Identifiers(mi.identifiers), css=css, title=title, author=author, publisher=publisher, pubdate_label=_('Published'), pubdate=pubdate, series_label=ngettext('Series', 'Series', 1), series=series, rating_label=_('Rating'), rating=rating, tags_label=_('Tags'), tags=tags, comments=comments, footer='', display=display, searchable_tags=' '.join( escape(t) + 'ttt' for t in tags.tags_list), ) for key in mi.custom_field_keys(): m = mi.get_user_metadata(key, False) or {} try: display_name, val = mi.format_field_extended(key)[:2] dkey = key.replace('#', '_') dt = m.get('datatype') if dt == 'series': args[dkey] = Series(mi.get(key), mi.get(key + '_index')) elif dt == 'rating': args[dkey] = rating_to_stars( mi.get(key), m.get('display', {}).get('allow_half_stars', False)) elif dt == 'comments': val = val or '' ctype = m.get('display', {}).get('interpret_as') or 'html' if ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % escape( val) elif ctype == 'short-text': val = '<span>%s</span>' % escape(val) elif ctype == 'markdown': val = markdown(val) else: val = comments_to_html(val) args[dkey] = val else: args[dkey] = escape(val) args[dkey + '_label'] = escape(display_name) setattr(display, dkey, 'none' if mi.is_null(key) else 'initial') except Exception: # if the val (custom column contents) is None, don't add to args pass if False: print("Custom column values available in jacket template:") for key in args.keys(): if key.startswith('_') and not key.endswith('_label'): print(" {}: {}".format('#' + key[1:], args[key])) # Used in the comment describing use of custom columns in templates # Don't change this unless you also change it in template.xhtml args['_genre_label'] = args.get('_genre_label', '{_genre_label}') args['_genre'] = args.get('_genre', '{_genre}') has_data['series'] = bool(series) has_data['tags'] = bool(tags) has_data['rating'] = bool(rating) has_data['pubdate'] = bool(pubdate) for k, v in has_data.items(): setattr(display, k, 'initial' if v else 'none') display.title = 'initial' if mi.identifiers: display.identifiers = 'initial' formatter = SafeFormatter() generated_html = formatter.format(template, **args) return strip_encoding_declarations(generated_html)
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