class NonLinearItems(BaseError): level = WARN has_multiple_locations = True HELP = xml( _('There are items marked as non-linear in the <spine>.' ' These will be displayed in random order by different ebook readers.' ' Some will ignore the non-linear attribute, some will display' ' them at the end or the beginning of the book and some will' ' fail to display them at all. Instead of using non-linear items' ' simply place the items in the order you want them to be displayed.' )) INDIVIDUAL_FIX = _('Mark all non-linear items as linear') def __init__(self, name, locs): BaseError.__init__(self, _('Non-linear items in the spine'), name) self.all_locations = [(name, x, None) for x in locs] def __call__(self, container): [ elem.attrib.pop('linear') for elem in container.opf_xpath('//opf:spine/opf:itemref[@linear]') ] container.dirty(container.opf_name) return True
def __init__(self, name, section_name): BaseError.__init__( self, _('The <%s> section is missing from the OPF') % section_name, name) self.HELP = xml( _('The <%s> section is required in the OPF file. You have to create one.' ) % section_name)
class NoUID(BaseError): HELP = xml( _('The OPF must have a unique identifier, i.e. a <dc:identifier> element whose id is referenced' ' by the <package> element')) INDIVIDUAL_FIX = _('Auto-generate a unique identifier') def __init__(self, name): BaseError.__init__(self, _('The OPF has no unique identifier'), name) def __call__(self, container): import uuid opf = container.opf uid = str(uuid.uuid4()) opf.set('unique-identifier', uid) m = container.opf_xpath('/opf:package/opf:metadata') if not m: m = [ container.opf.makeelement(OPF('metadata'), nsmap={'dc': DC11_NS}) ] container.insert_into_xml(container.opf, m[0], 0) m = m[0] dc = m.makeelement(DC('identifier'), id=uid, nsmap={'opf': OPF2_NS}) dc.set(OPF('scheme'), 'uuid') dc.text = uid container.insert_into_xml(m, dc) container.dirty(container.opf_name) return True
def __init__(self, name, idref, lnum): BaseError.__init__(self, _('idref="%s" points to unknown id') % idref, name, lnum) self.HELP = xml( _('The idref="%s" points to an id that does not exist in the OPF') % idref)
def __init__(self, name, lnum, cover): BaseError.__init__( self, _('The meta cover tag points to an non-existent item'), name, lnum) self.HELP = xml( _('The meta cover tag points to an item with id="%s" which does not exist in the manifest' ) % cover)
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 = 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 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
class EmptyIdentifier(BaseError): HELP = xml(_('The <dc:identifier> element must not be empty.')) INDIVIDUAL_FIX = _('Remove empty identifiers') def __init__(self, name, lnum): BaseError.__init__(self, _('Empty identifier element'), name, lnum) def __call__(self, container): for dcid in container.opf_xpath( '/opf:package/opf:metadata/dc:identifier'): if not dcid.text or not dcid.text.strip(): container.remove_from_xml(dcid) container.dirty(container.opf_name) return True
class MultipleCovers(BaseError): has_multiple_locations = True HELP = xml(_( 'There is more than one <meta name="cover"> tag defined. There should be only one.')) INDIVIDUAL_FIX = _('Remove all but the first meta cover tag') def __init__(self, name, locs): BaseError.__init__(self, _('There is more than one cover defined'), name) self.all_locations = [(name, lnum, None) for lnum in sorted(locs)] def __call__(self, container): items = [e for e in container.opf_xpath('/opf:package/opf:metadata/opf:meta[@name="cover"]')] [container.remove_from_xml(e) for e in items[1:]] container.dirty(self.name) return True
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 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 check_opf(container): errors = [] if container.opf.tag != OPF('package'): err = BaseError(_('The OPF does not have the correct root element'), container.opf_name) err.HELP = xml(_( 'The opf must have the root element <package> in namespace {0}, like this: <package xmlns="{0}">')).format(OPF2_NS) errors.append(err) for tag in ('metadata', 'manifest', 'spine'): if not container.opf_xpath('/opf:package/opf:' + tag): errors.append(MissingSection(container.opf_name, tag)) all_ids = set(container.opf_xpath('//*/@id')) for elem in container.opf_xpath('//*[@idref]'): if elem.get('idref') not in all_ids: errors.append(IncorrectIdref(container.opf_name, elem.get('idref'), elem.sourceline)) nl_items = [elem.sourceline for elem in container.opf_xpath('//opf:spine/opf:itemref[@linear="no"]')] if nl_items: errors.append(NonLinearItems(container.opf_name, nl_items)) seen, dups = {}, {} for item in container.opf_xpath('/opf:package/opf:manifest/opf:item[@href]'): href = item.get('href') if not container.exists(container.href_to_name(href, container.opf_name)): errors.append(MissingHref(container.opf_name, href, item.sourceline)) if href in seen: if href not in dups: dups[href] = [seen[href]] dups[href].append(item.sourceline) else: seen[href] = item.sourceline errors.extend(DuplicateHref(container.opf_name, eid, locs) for eid, locs in dups.iteritems()) seen, dups = {}, {} for item in container.opf_xpath('/opf:package/opf:spine/opf:itemref[@idref]'): ref = item.get('idref') if ref in seen: if ref not in dups: dups[ref] = [seen[ref]] dups[ref].append(item.sourceline) else: seen[ref] = item.sourceline errors.extend(DuplicateHref(container.opf_name, eid, locs, for_spine=True) for eid, locs in dups.iteritems()) spine = container.opf_xpath('/opf:package/opf:spine[@toc]') if spine: spine = spine[0] mitems = [x for x in container.opf_xpath('/opf:package/opf:manifest/opf:item[@id]') if x.get('id') == spine.get('toc')] if mitems: mitem = mitems[0] if mitem.get('media-type', '') != guess_type('a.ncx'): errors.append(IncorrectToc(container.opf_name, mitem.sourceline, bad_mimetype=mitem.get('media-type'))) else: errors.append(IncorrectToc(container.opf_name, spine.sourceline, bad_idref=spine.get('toc'))) covers = container.opf_xpath('/opf:package/opf:metadata/opf:meta[@name="cover"]') if len(covers) > 0: if len(covers) > 1: errors.append(MultipleCovers(container.opf_name, [c.sourceline for c in covers])) manifest_ids = set(container.opf_xpath('/opf:package/opf:manifest/opf:item/@id')) for cover in covers: if cover.get('content', None) not in manifest_ids: errors.append(IncorrectCover(container.opf_name, cover.sourceline, cover.get('content', ''))) raw = etree.tostring(cover) try: n, c = raw.index('name="'), raw.index('content="') except ValueError: n = c = -1 if n > -1 and c > -1 and n > c: errors.append(NookCover(container.opf_name, cover.sourceline)) uid = container.opf.get('unique-identifier', None) if uid is None or not container.opf_xpath('/opf:package/opf:metadata/dc:identifier[@id=%r]' % uid): errors.append(NoUID(container.opf_name)) return errors
def check_opf(container): errors = [] opf_version = container.opf_version_parsed if container.opf.tag != OPF('package'): err = BaseError(_('The OPF does not have the correct root element'), container.opf_name, container.opf.sourceline) err.HELP = xml(_( 'The opf must have the root element <package> in namespace {0}, like this: <package xmlns="{0}">')).format(OPF2_NS) errors.append(err) elif container.opf.get('version') is None and container.book_type == 'epub': err = BaseError(_('The OPF does not have a version'), container.opf_name, container.opf.sourceline) err.HELP = xml(_( 'The <package> tag in the OPF must have a version attribute. This is usually version="2.0" for EPUB2 and AZW3 and version="3.0" for EPUB3')) errors.append(err) for tag in ('metadata', 'manifest', 'spine'): if not container.opf_xpath('/opf:package/opf:' + tag): errors.append(MissingSection(container.opf_name, tag)) all_ids = set(container.opf_xpath('//*/@id')) if '' in all_ids: for empty_id_tag in container.opf_xpath('//*[@id=""]'): errors.append(EmptyID(container.opf_name, empty_id_tag.sourceline)) all_ids.discard('') for elem in container.opf_xpath('//*[@idref]'): if elem.get('idref') not in all_ids: errors.append(IncorrectIdref(container.opf_name, elem.get('idref'), elem.sourceline)) nl_items = [elem.sourceline for elem in container.opf_xpath('//opf:spine/opf:itemref[@linear="no"]')] if nl_items: errors.append(NonLinearItems(container.opf_name, nl_items)) seen, dups = {}, {} for item in container.opf_xpath('/opf:package/opf:manifest/opf:item'): href = item.get('href', None) if href is None: errors.append(NoHref(container.opf_name, item.get('id', None), item.sourceline)) else: hname = container.href_to_name(href, container.opf_name) if not hname or not container.exists(hname): errors.append(MissingHref(container.opf_name, href, item.sourceline)) if href in seen: if href not in dups: dups[href] = [seen[href]] dups[href].append(item.sourceline) else: seen[href] = item.sourceline errors.extend(DuplicateHref(container.opf_name, eid, locs) for eid, locs in iteritems(dups)) seen, dups = {}, {} for item in container.opf_xpath('/opf:package/opf:spine/opf:itemref[@idref]'): ref = item.get('idref') if ref in seen: if ref not in dups: dups[ref] = [seen[ref]] dups[ref].append(item.sourceline) else: seen[ref] = item.sourceline errors.extend(DuplicateHref(container.opf_name, eid, locs, for_spine=True) for eid, locs in iteritems(dups)) spine = container.opf_xpath('/opf:package/opf:spine[@toc]') if spine: spine = spine[0] mitems = [x for x in container.opf_xpath('/opf:package/opf:manifest/opf:item[@id]') if x.get('id') == spine.get('toc')] if mitems: mitem = mitems[0] if mitem.get('media-type', '') != guess_type('a.ncx'): errors.append(IncorrectToc(container.opf_name, mitem.sourceline, bad_mimetype=mitem.get('media-type'))) else: errors.append(IncorrectToc(container.opf_name, spine.sourceline, bad_idref=spine.get('toc'))) else: spine = container.opf_xpath('/opf:package/opf:spine') if spine: spine = spine[0] ncx = container.manifest_type_map.get(guess_type('a.ncx')) if ncx: ncx_name = ncx[0] rmap = {v:k for k, v in iteritems(container.manifest_id_map)} ncx_id = rmap.get(ncx_name) if ncx_id: errors.append(MissingNCXRef(container.opf_name, spine.sourceline, ncx_id)) if opf_version.major > 2: existing_nav = find_existing_nav_toc(container) if existing_nav is None: errors.append(MissingNav(container.opf_name, 0)) else: toc = parse_nav(container, existing_nav) if len(toc) == 0: errors.append(EmptyNav(existing_nav, 0)) covers = container.opf_xpath('/opf:package/opf:metadata/opf:meta[@name="cover"]') if len(covers) > 0: if len(covers) > 1: errors.append(MultipleCovers(container.opf_name, [c.sourceline for c in covers])) manifest_ids = set(container.opf_xpath('/opf:package/opf:manifest/opf:item/@id')) for cover in covers: if cover.get('content', None) not in manifest_ids: errors.append(IncorrectCover(container.opf_name, cover.sourceline, cover.get('content', ''))) raw = etree.tostring(cover) try: n, c = raw.index(b'name="'), raw.index(b'content="') except ValueError: n = c = -1 if n > -1 and c > -1 and n > c: errors.append(NookCover(container.opf_name, cover.sourceline)) uid = container.opf.get('unique-identifier', None) if uid is None or not container.opf_xpath('/opf:package/opf:metadata/dc:identifier[@id=%r]' % uid): errors.append(NoUID(container.opf_name)) for elem in container.opf_xpath('/opf:package/opf:metadata/dc:identifier'): if not elem.text or not elem.text.strip(): errors.append(EmptyIdentifier(container.opf_name, elem.sourceline)) for item, name, linear in container.spine_iter: mt = container.mime_map[name] if mt != XHTML_MIME: iid = item.get('idref', None) lnum = None if iid: mitem = container.opf_xpath('/opf:package/opf:manifest/opf:item[@id=%r]' % iid) if mitem: lnum = mitem[0].sourceline else: iid = None errors.append(BadSpineMime(name, iid, mt, lnum, container.opf_name)) return errors
def __init__(self, name, lnum): BaseError.__init__(self, _('Empty id attributes are invalid'), name, lnum) self.HELP = xml(_( 'Empty ID attributes are invalid in OPF files.'))
def sony_metadata(oeb): m = oeb.metadata title = short_title = str(m.title[0]) publisher = __appname__ + ' ' + __version__ try: pt = str(oeb.metadata.publication_type[0]) short_title = ':'.join(pt.split(':')[2:]) except: pass try: date = parse_date(str(m.date[0]), as_utc=False).strftime('%Y-%m-%d') except: date = strftime('%Y-%m-%d') try: language = str(m.language[0]).replace('_', '-') except: language = 'en' short_title = xml(short_title, True) metadata = SONY_METADATA.format(title=xml(title), short_title=short_title, publisher=xml(publisher), issue_date=xml(date), language=xml(language)) updated = strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) def cal_id(x): for k, v in x.attrib.items(): if k.endswith('scheme') and v == 'uuid': return True try: base_id = str(list(filter(cal_id, m.identifier))[0]) except: base_id = str(uuid4()) toc = oeb.toc if False and toc.depth() < 3: # Single section periodical # Disabled since I prefer the current behavior from calibre.ebooks.oeb.base import TOC section = TOC(klass='section', title=_('All articles'), href=oeb.spine[2].href) for x in toc: section.nodes.append(x) toc = TOC(klass='periodical', href=oeb.spine[2].href, title=str(oeb.metadata.title[0])) toc.nodes.append(section) entries = [] seen_titles = set() for i, section in enumerate(toc): if not section.href: continue secid = 'section%d'%i sectitle = section.title if not sectitle: sectitle = _('Unknown') d = 1 bsectitle = sectitle while sectitle in seen_titles: sectitle = bsectitle + ' ' + str(d) d += 1 seen_titles.add(sectitle) sectitle = xml(sectitle, True) secdesc = section.description if not secdesc: secdesc = '' secdesc = xml(secdesc) entries.append(SONY_ATOM_SECTION.format(title=sectitle, href=section.href, id=xml(base_id)+'/'+secid, short_title=short_title, desc=secdesc, updated=updated)) for j, article in enumerate(section): if not article.href: continue atitle = article.title btitle = atitle d = 1 while atitle in seen_titles: atitle = btitle + ' ' + str(d) d += 1 auth = article.author if article.author else '' desc = section.description if not desc: desc = '' aid = 'article%d'%j entries.append(SONY_ATOM_ENTRY.format( title=xml(atitle), author=xml(auth), updated=updated, desc=desc, short_title=short_title, section_title=sectitle, href=article.href, word_count=str(1), id=xml(base_id)+'/'+secid+'/'+aid )) atom = SONY_ATOM.format(short_title=short_title, entries='\n\n'.join(entries), updated=updated, id=xml(base_id)).encode('utf-8') return metadata, atom
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 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, name, idref, lnum): BaseError.__init__(self, _('idref="%s" points to unknown id') % idref, name, lnum) self.HELP = xml(_( 'The idref="%s" points to an id that does not exist in the OPF') % idref)
def check_opf(container): errors = [] opf_version = container.opf_version_parsed if container.opf.tag != OPF('package'): err = BaseError(_('The OPF does not have the correct root element'), container.opf_name, container.opf.sourceline) err.HELP = xml(_( 'The opf must have the root element <package> in namespace {0}, like this: <package xmlns="{0}">')).format(OPF2_NS) errors.append(err) elif container.opf.get('version') is None and container.book_type == 'epub': err = BaseError(_('The OPF does not have a version'), container.opf_name, container.opf.sourceline) err.HELP = xml(_( 'The <package> tag in the OPF must have a version attribute. This is usually version="2.0" for EPUB2 and AZW3 and version="3.0" for EPUB3')) errors.append(err) for tag in ('metadata', 'manifest', 'spine'): if not container.opf_xpath('/opf:package/opf:' + tag): errors.append(MissingSection(container.opf_name, tag)) all_ids = set(container.opf_xpath('//*/@id')) for elem in container.opf_xpath('//*[@idref]'): if elem.get('idref') not in all_ids: errors.append(IncorrectIdref(container.opf_name, elem.get('idref'), elem.sourceline)) nl_items = [elem.sourceline for elem in container.opf_xpath('//opf:spine/opf:itemref[@linear="no"]')] if nl_items: errors.append(NonLinearItems(container.opf_name, nl_items)) seen, dups = {}, {} for item in container.opf_xpath('/opf:package/opf:manifest/opf:item'): href = item.get('href', None) if href is None: errors.append(NoHref(container.opf_name, item.get('id', None), item.sourceline)) else: hname = container.href_to_name(href, container.opf_name) if not hname or not container.exists(hname): errors.append(MissingHref(container.opf_name, href, item.sourceline)) if href in seen: if href not in dups: dups[href] = [seen[href]] dups[href].append(item.sourceline) else: seen[href] = item.sourceline errors.extend(DuplicateHref(container.opf_name, eid, locs) for eid, locs in dups.iteritems()) seen, dups = {}, {} for item in container.opf_xpath('/opf:package/opf:spine/opf:itemref[@idref]'): ref = item.get('idref') if ref in seen: if ref not in dups: dups[ref] = [seen[ref]] dups[ref].append(item.sourceline) else: seen[ref] = item.sourceline errors.extend(DuplicateHref(container.opf_name, eid, locs, for_spine=True) for eid, locs in dups.iteritems()) spine = container.opf_xpath('/opf:package/opf:spine[@toc]') if spine: spine = spine[0] mitems = [x for x in container.opf_xpath('/opf:package/opf:manifest/opf:item[@id]') if x.get('id') == spine.get('toc')] if mitems: mitem = mitems[0] if mitem.get('media-type', '') != guess_type('a.ncx'): errors.append(IncorrectToc(container.opf_name, mitem.sourceline, bad_mimetype=mitem.get('media-type'))) else: errors.append(IncorrectToc(container.opf_name, spine.sourceline, bad_idref=spine.get('toc'))) else: spine = container.opf_xpath('/opf:package/opf:spine') if spine: spine = spine[0] ncx = container.manifest_type_map.get(guess_type('a.ncx')) if ncx: ncx_name = ncx[0] rmap = {v:k for k, v in container.manifest_id_map.iteritems()} ncx_id = rmap.get(ncx_name) if ncx_id: errors.append(MissingNCXRef(container.opf_name, spine.sourceline, ncx_id)) if opf_version.major > 2: if find_existing_nav_toc(container) is None: errors.append(MissingNav(container.opf_name, 0)) covers = container.opf_xpath('/opf:package/opf:metadata/opf:meta[@name="cover"]') if len(covers) > 0: if len(covers) > 1: errors.append(MultipleCovers(container.opf_name, [c.sourceline for c in covers])) manifest_ids = set(container.opf_xpath('/opf:package/opf:manifest/opf:item/@id')) for cover in covers: if cover.get('content', None) not in manifest_ids: errors.append(IncorrectCover(container.opf_name, cover.sourceline, cover.get('content', ''))) raw = etree.tostring(cover) try: n, c = raw.index('name="'), raw.index('content="') except ValueError: n = c = -1 if n > -1 and c > -1 and n > c: errors.append(NookCover(container.opf_name, cover.sourceline)) uid = container.opf.get('unique-identifier', None) if uid is None or not container.opf_xpath('/opf:package/opf:metadata/dc:identifier[@id=%r]' % uid): errors.append(NoUID(container.opf_name)) for item, name, linear in container.spine_iter: mt = container.mime_map[name] if mt != XHTML_MIME: iid = item.get('idref', None) lnum = None if iid: mitem = container.opf_xpath('/opf:package/opf:manifest/opf:item[@id=%r]' % iid) if mitem: lnum = mitem[0].sourceline else: iid = None errors.append(BadSpineMime(name, iid, mt, lnum, container.opf_name)) return errors
def __init__(self, name, section_name): BaseError.__init__(self, _('The <%s> section is missing from the OPF') % section_name, name) self.HELP = xml(_( 'The <%s> section is required in the OPF file. You have to create one.') % section_name)
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 check_opf(container): errors = [] if container.opf.tag != OPF('package'): err = BaseError(_('The OPF does not have the correct root element'), container.opf_name) err.HELP = xml( _('The opf must have the root element <package> in namespace {0}, like this: <package xmlns="{0}">' )).format(OPF2_NS) errors.append(err) for tag in ('metadata', 'manifest', 'spine'): if not container.opf_xpath('/opf:package/opf:' + tag): errors.append(MissingSection(container.opf_name, tag)) all_ids = set(container.opf_xpath('//*/@id')) for elem in container.opf_xpath('//*[@idref]'): if elem.get('idref') not in all_ids: errors.append( IncorrectIdref(container.opf_name, elem.get('idref'), elem.sourceline)) nl_items = [ elem.sourceline for elem in container.opf_xpath( '//opf:spine/opf:itemref[@linear="no"]') ] if nl_items: errors.append(NonLinearItems(container.opf_name, nl_items)) seen, dups = {}, {} for item in container.opf_xpath( '/opf:package/opf:manifest/opf:item[@href]'): href = item.get('href') if not container.exists( container.href_to_name(href, container.opf_name)): errors.append( MissingHref(container.opf_name, href, item.sourceline)) if href in seen: if href not in dups: dups[href] = [seen[href]] dups[href].append(item.sourceline) else: seen[href] = item.sourceline errors.extend( DuplicateHref(container.opf_name, eid, locs) for eid, locs in dups.iteritems()) seen, dups = {}, {} for item in container.opf_xpath( '/opf:package/opf:spine/opf:itemref[@idref]'): ref = item.get('idref') if ref in seen: if ref not in dups: dups[ref] = [seen[ref]] dups[ref].append(item.sourceline) else: seen[ref] = item.sourceline errors.extend( DuplicateHref(container.opf_name, eid, locs, for_spine=True) for eid, locs in dups.iteritems()) spine = container.opf_xpath('/opf:package/opf:spine[@toc]') if spine: spine = spine[0] mitems = [ x for x in container.opf_xpath( '/opf:package/opf:manifest/opf:item[@id]') if x.get('id') == spine.get('toc') ] if mitems: mitem = mitems[0] if mitem.get('media-type', '') != guess_type('a.ncx'): errors.append( IncorrectToc(container.opf_name, mitem.sourceline, bad_mimetype=mitem.get('media-type'))) else: errors.append( IncorrectToc(container.opf_name, spine.sourceline, bad_idref=spine.get('toc'))) covers = container.opf_xpath( '/opf:package/opf:metadata/opf:meta[@name="cover"]') if len(covers) > 0: if len(covers) > 1: errors.append( MultipleCovers(container.opf_name, [c.sourceline for c in covers])) manifest_ids = set( container.opf_xpath('/opf:package/opf:manifest/opf:item/@id')) for cover in covers: if cover.get('content', None) not in manifest_ids: errors.append( IncorrectCover(container.opf_name, cover.sourceline, cover.get('content', ''))) raw = etree.tostring(cover) try: n, c = raw.index('name="'), raw.index('content="') except ValueError: n = c = -1 if n > -1 and c > -1 and n > c: errors.append(NookCover(container.opf_name, cover.sourceline)) uid = container.opf.get('unique-identifier', None) if uid is None or not container.opf_xpath( '/opf:package/opf:metadata/dc:identifier[@id=%r]' % uid): errors.append(NoUID(container.opf_name)) for item, name, linear in container.spine_iter: mt = container.mime_map[name] if mt != XHTML_MIME: iid = item.get('idref', None) lnum = None if iid: mitem = container.opf_xpath( '/opf:package/opf:manifest/opf:item[@id=%r]' % iid) if mitem: lnum = mitem[0].sourceline else: iid = None errors.append(BadSpineMime(name, iid, mt, lnum, container.opf_name)) return errors
class EmptyIdentifier(BaseError): HELP = xml(_('The <dc:identifier> element must not be empty.')) def __init__(self, name, lnum): BaseError.__init__(self, _('Empty identifier element'), name, lnum)
def __init__(self, name, lnum, cover): BaseError.__init__(self, _('The meta cover tag points to an non-existent item'), name, lnum) self.HELP = xml(_( 'The meta cover tag points to an item with id="%s" which does not exist in the manifest') % cover)
def sony_metadata(oeb): m = oeb.metadata title = short_title = unicode(m.title[0]) publisher = __appname__ + ' ' + __version__ try: pt = unicode(oeb.metadata.publication_type[0]) short_title = u':'.join(pt.split(':')[2:]) except: pass try: date = parse_date(unicode(m.date[0]), as_utc=False).strftime('%Y-%m-%d') except: date = strftime('%Y-%m-%d') try: language = unicode(m.language[0]).replace('_', '-') except: language = 'en' short_title = xml(short_title, True) metadata = SONY_METADATA.format(title=xml(title), short_title=short_title, publisher=xml(publisher), issue_date=xml(date), language=xml(language)) updated = strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) def cal_id(x): for k, v in x.attrib.items(): if k.endswith('scheme') and v == 'uuid': return True try: base_id = unicode(list(filter(cal_id, m.identifier))[0]) except: base_id = str(uuid4()) toc = oeb.toc if False and toc.depth() < 3: # Single section periodical # Disabled since I prefer the current behavior from calibre.ebooks.oeb.base import TOC section = TOC(klass='section', title=_('All articles'), href=oeb.spine[2].href) for x in toc: section.nodes.append(x) toc = TOC(klass='periodical', href=oeb.spine[2].href, title=unicode(oeb.metadata.title[0])) toc.nodes.append(section) entries = [] seen_titles = set([]) for i, section in enumerate(toc): if not section.href: continue secid = 'section%d'%i sectitle = section.title if not sectitle: sectitle = _('Unknown') d = 1 bsectitle = sectitle while sectitle in seen_titles: sectitle = bsectitle + ' ' + str(d) d += 1 seen_titles.add(sectitle) sectitle = xml(sectitle, True) secdesc = section.description if not secdesc: secdesc = '' secdesc = xml(secdesc) entries.append(SONY_ATOM_SECTION.format(title=sectitle, href=section.href, id=xml(base_id)+'/'+secid, short_title=short_title, desc=secdesc, updated=updated)) for j, article in enumerate(section): if not article.href: continue atitle = article.title btitle = atitle d = 1 while atitle in seen_titles: atitle = btitle + ' ' + str(d) d += 1 auth = article.author if article.author else '' desc = section.description if not desc: desc = '' aid = 'article%d'%j entries.append(SONY_ATOM_ENTRY.format( title=xml(atitle), author=xml(auth), updated=updated, desc=desc, short_title=short_title, section_title=sectitle, href=article.href, word_count=str(1), id=xml(base_id)+'/'+secid+'/'+aid )) atom = SONY_ATOM.format(short_title=short_title, entries='\n\n'.join(entries), updated=updated, id=xml(base_id)).encode('utf-8') return metadata, atom