mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement merging of annotations in rapydscript
This commit is contained in:
parent
5075fc2d36
commit
631a907773
93
src/pyj/read_book/annotations.pyj
Normal file
93
src/pyj/read_book/annotations.pyj
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
from __python__ import bound_methods, hash_literals
|
||||||
|
|
||||||
|
from read_book.cfi import create_cfi_cmp, cfi_sort_key
|
||||||
|
|
||||||
|
|
||||||
|
no_cfi = '/99999999'
|
||||||
|
|
||||||
|
|
||||||
|
def bookmark_get_cfi(b):
|
||||||
|
if b.pos_type is 'epubcfi':
|
||||||
|
return b.pos[8:-1]
|
||||||
|
|
||||||
|
|
||||||
|
def highlight_get_cfi(hl):
|
||||||
|
cfi = hl.start_cfi
|
||||||
|
if cfi:
|
||||||
|
return cfi[8:-1]
|
||||||
|
|
||||||
|
|
||||||
|
def sort_annot_list(annots, get_cfi_func):
|
||||||
|
key_map = {no_cfi: cfi_sort_key(no_cfi)}
|
||||||
|
for annot in annots:
|
||||||
|
cfi = get_cfi_func(annot)
|
||||||
|
if cfi and not key_map[cfi]:
|
||||||
|
key_map[cfi] = cfi_sort_key(cfi)
|
||||||
|
cfi_cmp = create_cfi_cmp(key_map)
|
||||||
|
annots.sort(def (a, b):
|
||||||
|
acfi = get_cfi_func(a) or no_cfi
|
||||||
|
bcfi = get_cfi_func(b) or no_cfi
|
||||||
|
return cfi_cmp(acfi, bcfi)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def annots_descending_cmp(a, b):
|
||||||
|
return -1 if a.timestamp > b.timestamp else (1 if a.timestamp < b.timestamp else 0)
|
||||||
|
|
||||||
|
|
||||||
|
def merge_annots_with_identical_field(annots_a, annots_b, field, cfi_getter_func):
|
||||||
|
title_groups = {}
|
||||||
|
changed = False
|
||||||
|
all_annots = annots_a.concat(annots_b)
|
||||||
|
for a in all_annots:
|
||||||
|
q = title_groups[a[field]]
|
||||||
|
if not q:
|
||||||
|
q = title_groups[a[field]] = v'[]'
|
||||||
|
q.push(a)
|
||||||
|
for tg in Object.values(title_groups):
|
||||||
|
tg.sort(annots_descending_cmp)
|
||||||
|
seen = {}
|
||||||
|
ans = v'[]'
|
||||||
|
for a in all_annots:
|
||||||
|
title = a[field]
|
||||||
|
if not seen[title]:
|
||||||
|
seen[title] = True
|
||||||
|
candidates = title_groups[title]
|
||||||
|
if not changed and candidates.length > 1 and candidates[0].timestamp is not candidates[1].timestamp:
|
||||||
|
changed = True
|
||||||
|
ans.push(title_groups[title][0])
|
||||||
|
if ans.length is not annots_a.length or ans.length is not annots_b.length:
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
sort_annot_list(ans, cfi_getter_func)
|
||||||
|
return changed, ans
|
||||||
|
|
||||||
|
|
||||||
|
field_map = {'bookmark': 'title', 'highlight': 'uuid'}
|
||||||
|
getter_map = {'bookmark': bookmark_get_cfi, 'highlight': highlight_get_cfi}
|
||||||
|
|
||||||
|
|
||||||
|
def merge_annot_lists(a, b, field):
|
||||||
|
key_field = field_map[field]
|
||||||
|
return merge_annots_with_identical_field(a, b, key_field, getter_map[field])
|
||||||
|
|
||||||
|
|
||||||
|
def merge_annotation_maps(a, b):
|
||||||
|
updated = False
|
||||||
|
ans = {}
|
||||||
|
for field in field_map:
|
||||||
|
a_items = a[field] or v'[]'
|
||||||
|
b_items = b[field] or v'[]'
|
||||||
|
if not a_items.length:
|
||||||
|
ans[field] = b_items
|
||||||
|
continue
|
||||||
|
if not b_items.length:
|
||||||
|
ans[field] = a_items
|
||||||
|
continue
|
||||||
|
changed, ans[field] = merge_annot_lists(a_items, b_items, field)
|
||||||
|
if changed:
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
return updated, ans
|
@ -8,6 +8,7 @@ import initialize # noqa: unused-import
|
|||||||
import test_date # noqa: unused-import
|
import test_date # noqa: unused-import
|
||||||
import test_utils # noqa: unused-import
|
import test_utils # noqa: unused-import
|
||||||
import read_book.test_cfi # noqa: unused-import
|
import read_book.test_cfi # noqa: unused-import
|
||||||
|
import test_annotations # noqa: unused-import
|
||||||
|
|
||||||
from testing import registered_tests, reset_dom
|
from testing import registered_tests, reset_dom
|
||||||
|
|
||||||
|
35
src/pyj/test_annotations.pyj
Normal file
35
src/pyj/test_annotations.pyj
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
from __python__ import bound_methods, hash_literals
|
||||||
|
|
||||||
|
from read_book.annotations import merge_annot_lists
|
||||||
|
from testing import assert_equal, test, assert_true
|
||||||
|
|
||||||
|
|
||||||
|
def bm(title, bmid, year=20, first_cfi_number=1):
|
||||||
|
return {
|
||||||
|
'title': title, 'id': bmid, 'timestamp': str.format('20{}-06-29T03:21:48.895323+00:00', year),
|
||||||
|
'pos_type': 'epubcfi', 'pos': str.format('epubcfi(/{}/4/8)', first_cfi_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def hl(uuid, hlid, year=20, first_cfi_number=1):
|
||||||
|
return {
|
||||||
|
'uuid': uuid, 'id': hlid, 'timestamp': str.format('20{}-06-29T03:21:48.895323+00:00', year),
|
||||||
|
'start_cfi': str.format('epubcfi(/{}/4/8)', first_cfi_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def merging_annotations():
|
||||||
|
for atype in 'bookmark highlight'.split(' '):
|
||||||
|
f = bm if atype == 'bookmark' else hl
|
||||||
|
a = [f('one', 1, 20, 2), f('two', 2, 20, 4), f('a', 3, 20, 16),]
|
||||||
|
b = [f('one', 10, 30, 2), f('two', 20, 10, 4), f('b', 30, 20, 8),]
|
||||||
|
changed, c = merge_annot_lists(a, b, atype)
|
||||||
|
assert_true(changed)
|
||||||
|
|
||||||
|
def get_id(x):
|
||||||
|
return x.id
|
||||||
|
|
||||||
|
assert_equal(list(map(get_id, c)), [10, 2, 30, 3])
|
Loading…
x
Reference in New Issue
Block a user