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):