diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index 8894362766..1ad12a915a 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -277,20 +277,9 @@ def update_metadata(opf, mi, apply_null=False, update_timestamp=False, force_ide if mi.languages: mi.languages = normalize_languages(list(opf.raw_languages) or [], mi.languages) - opf.smart_update(mi) + opf.smart_update(mi, apply_null=apply_null) if getattr(mi, 'uuid', None): opf.application_id = mi.uuid - if apply_null: - if not getattr(mi, 'series', None): - opf.series = None - if not getattr(mi, 'tags', []): - opf.tags = [] - if not getattr(mi, 'isbn', None): - opf.isbn = None - if not getattr(mi, 'comments', None): - opf.comments = None - if not getattr(mi, 'publisher', None): - opf.publisher = None if apply_null or force_identifiers: opf.set_identifiers(mi.get_identifiers()) else: diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 7b71e03cc1..10ac69ac73 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -1249,19 +1249,46 @@ class OPF(object): # {{{ raw = '\n'%encoding.upper()+raw return raw - def smart_update(self, mi, replace_metadata=False): + def smart_update(self, mi, replace_metadata=False, apply_null=False): for attr in ('title', 'authors', 'author_sort', 'title_sort', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'category', 'comments', 'book_producer', 'pubdate', 'user_categories', 'author_link_map'): val = getattr(mi, attr, None) - if val is not None and val != [] and val != (None, None): + is_null = val is None or val in ((), [], (None, None), {}) + if is_null: + if apply_null and attr in {'series', 'tags', 'isbn', 'comments', 'publisher'}: + setattr(self, attr, ([] if attr == 'tags' else None)) + else: setattr(self, attr, val) langs = getattr(mi, 'languages', []) if langs and langs != ['und']: self.languages = langs temp = self.to_book_metadata() temp.smart_update(mi, replace_metadata=replace_metadata) + if not replace_metadata and callable(getattr(temp, 'custom_field_keys', None)): + # We have to replace non-null fields regardless of the value of + # replace_metadata to match the behavior of the builtin fields + # above. + for x in temp.custom_field_keys(): + meta = temp.get_user_metadata(x, make_copy=True) + if meta is None: + continue + if meta['datatype'] == 'text' and meta['is_multiple']: + val = mi.get(x, []) + if val or apply_null: + temp.set(x, val) + elif meta['datatype'] in {'int', 'float', 'bool'}: + missing = object() + val = mi.get(x, missing) + if val is missing: + if apply_null: + temp.set(x, None) + elif apply_null or val is not None: + temp.set(x, val) + elif apply_null and mi.is_null(x) and not temp.is_null(x): + temp.set(x, None) + self._user_metadata_ = temp.get_all_user_metadata(True) # }}}