diff --git a/src/calibre/ebooks/metadata/opf3.py b/src/calibre/ebooks/metadata/opf3.py index e399540c9d..1b6570a8d7 100644 --- a/src/calibre/ebooks/metadata/opf3.py +++ b/src/calibre/ebooks/metadata/opf3.py @@ -11,8 +11,8 @@ from lxml import etree from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata.book.base import Metadata -from calibre.ebooks.metadata.utils import parse_opf -from calibre.ebooks.oeb.base import OPF2_NSMAP, OPF +from calibre.ebooks.metadata.utils import parse_opf, pretty_print_opf +from calibre.ebooks.oeb.base import OPF2_NSMAP, OPF, DC # Utils {{{ # http://www.idpf.org/epub/vocab/package/pfx/ @@ -119,6 +119,32 @@ def read_identifiers(root, prefixes, refines): if scheme and val: ans[scheme].append(val) return ans + +def set_identifiers(root, prefixes, refines, new_identifiers, force_identifiers=False): + uid = root.get('unique-identifier') + package_identifier = None + for ident in XPath('./opf:metadata/dc:identifier')(root): + if uid is not None and uid == ident.get('id'): + package_identifier = ident + continue + val = (ident.text or '').strip() + if not val: + ident.getparent().remove(ident) + continue + scheme, val = parse_identifier(ident, val, refines) + if not scheme or not val or force_identifiers or scheme in new_identifiers: + ident.getparent().remove(ident) + continue + metadata = XPath('./opf:metadata')(root)[0] + for scheme, val in new_identifiers.iteritems(): + ident = metadata.makeelement(DC('identifier')) + ident.text = '%s:%s' % (scheme, val) + if package_identifier is None: + metadata.append(ident) + else: + p = package_identifier.getparent() + p.insert(p.index(package_identifier), ident) + # }}} def read_metadata(root): @@ -139,6 +165,18 @@ def get_metadata(stream): root = parse_opf(stream) return read_metadata(root) +def apply_metadata(root, mi, cover_prefix='', cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False): + prefixes, refines = read_prefixes(root), read_refines(root) + set_identifiers(root, prefixes, refines, mi.identifiers, force_identifiers=force_identifiers) + pretty_print_opf(root) + +def set_metadata(stream, mi, cover_prefix='', cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False): + root = parse_opf(stream) + return apply_metadata( + root, mi, cover_prefix=cover_prefix, cover_data=cover_data, + apply_null=apply_null, update_timestamp=update_timestamp, + force_identifiers=force_identifiers) + if __name__ == '__main__': import sys print(get_metadata(open(sys.argv[-1], 'rb'))) diff --git a/src/calibre/ebooks/metadata/opf3_test.py b/src/calibre/ebooks/metadata/opf3_test.py index 3173bf7e5b..e2a4be01ff 100644 --- a/src/calibre/ebooks/metadata/opf3_test.py +++ b/src/calibre/ebooks/metadata/opf3_test.py @@ -10,10 +10,12 @@ import unittest from lxml import etree from calibre.ebooks.metadata.opf3 import ( - parse_prefixes, reserved_prefixes, expand_prefix, read_identifiers, read_metadata + parse_prefixes, reserved_prefixes, expand_prefix, read_identifiers, + read_metadata, set_identifiers, XPath ) TEMPLATE = '''{metadata}''' # noqa +default_refines = defaultdict(list) class TestOPF3(unittest.TestCase): @@ -33,8 +35,11 @@ class TestOPF3(unittest.TestCase): self.ae(expand_prefix(raw, reserved_prefixes), expanded) def test_identifiers(self): - def idt(val, scheme=None): - return '{val}'.format(scheme=('opf:scheme="%s"'%scheme if scheme else ''), val=val) + def idt(val, scheme=None, iid=''): + return '{val}'.format(scheme=('opf:scheme="%s"'%scheme if scheme else ''), val=val, id=iid) + def ri(root): + return dict(read_identifiers(root, reserved_prefixes, default_refines)) + for m, result in ( (idt('abc', 'ISBN'), {}), (idt('isbn:9780230739581'), {'isbn':['9780230739581']}), @@ -45,11 +50,19 @@ class TestOPF3(unittest.TestCase): (idt('url:http://x'), {'url':['http://x']}), (idt('a:1')+idt('a:2'), {'a':['1', '2']}), ): - self.ae(result, dict(read_identifiers(self.get_opf(m), reserved_prefixes, defaultdict(list)))) + self.ae(result, ri(self.get_opf(m))) mi = read_metadata(self.get_opf( metadata=idt('a:1')+idt('a:2')+idt('calibre:x')+idt('uuid:y'))) self.ae(mi.application_id, 'x') + root = self.get_opf(metadata=idt('i:1', iid='uid') + idt('r:1') + idt('o:1')) + set_identifiers(root, reserved_prefixes, default_refines, {'i':'2', 'o':'2'}) + self.ae({'i':['2', '1'], 'r':['1'], 'o':['2']}, ri(root)) + self.ae(1, len(XPath('//dc:identifier[@id="uid"]')(root))) + root = self.get_opf(metadata=idt('i:1', iid='uid') + idt('r:1') + idt('o:1')) + set_identifiers(root, reserved_prefixes, default_refines, {'i':'2', 'o':'2'}, force_identifiers=True) + self.ae({'i':['2', '1'], 'o':['2']}, ri(root)) + class TestRunner(unittest.main): def createTests(self):