1#!/usr/local/bin/python3.8
2# vim:fileencoding=utf-8
3
4
5__license__ = 'GPL v3'
6__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
7
8import unittest, numbers
9
10from calibre.ebooks.epub.cfi.parse import parser, cfi_sort_key, decode_cfi
11from polyglot.builtins import iteritems
12
13
14class Tests(unittest.TestCase):
15
16    def test_sorting(self):
17        null_offsets = (0, (0, 0), 0)
18        for path, key in [
19                ('/1/2/3', ((1, 2, 3), null_offsets)),
20                ('/1[id]:34[yyyy]', ((1,), (0, (0, 0), 34))),
21                ('/1@1:2', ((1,), (0, (2, 1), 0))),
22                ('/1~1.2', ((1,), (1.2, (0, 0), 0))),
23        ]:
24            self.assertEqual(cfi_sort_key(path), key)
25
26    def test_parsing(self):
27        p = parser()
28
29        def step(x):
30            if isinstance(x, numbers.Integral):
31                return {'num': x}
32            return {'num':x[0], 'id':x[1]}
33
34        def s(*args):
35            return {'steps':list(map(step, args))}
36
37        def r(*args):
38            idx = args.index('!')
39            ans = s(*args[:idx])
40            ans['redirect'] = s(*args[idx+1:])
41            return ans
42
43        def o(*args):
44            ans = s(1)
45            step = ans['steps'][-1]
46            typ, val = args[:2]
47            step[{'@':'spatial_offset', '~':'temporal_offset', ':':'text_offset'}[typ]] = val
48            if len(args) == 4:
49                typ, val = args[2:]
50                step[{'@':'spatial_offset', '~':'temporal_offset'}[typ]] = val
51            return ans
52
53        def a(before=None, after=None, **params):
54            ans = o(':', 3)
55            step = ans['steps'][-1]
56            ta = {}
57            if before is not None:
58                ta['before'] = before
59            if after is not None:
60                ta['after'] = after
61            if params:
62                ta['params'] = {str(k):(v,) if isinstance(v, str) else v for k, v in iteritems(params)}
63            if ta:
64                step['text_assertion'] = ta
65            return ans
66
67        for raw, path, leftover in [
68            # Test parsing of steps
69            ('/2', s(2), ''),
70            ('/2/3/4', s(2, 3, 4), ''),
71            ('/1/2[some^,^^id]/3', s(1, (2, 'some,^id'), 3), ''),
72            ('/1/2!/3/4', r(1, 2, '!', 3, 4), ''),
73            ('/1/2[id]!/3/4', r(1, (2, 'id'), '!', 3, 4), ''),
74            ('/1!/2[id]/3/4', r(1, '!', (2, 'id'), 3, 4), ''),
75
76            # Test parsing of offsets
77            ('/1~0', o('~', 0), ''),
78            ('/1~7', o('~', 7), ''),
79            ('/1~43.1', o('~', 43.1), ''),
80            ('/1~0.01', o('~', 0.01), ''),
81            ('/1~1.301', o('~', 1.301), ''),
82            ('/1@23:34.1', o('@', (23, 34.1)), ''),
83            ('/1~3@3.1:2.3', o('~', 3.0, '@', (3.1, 2.3)), ''),
84            ('/1:0', o(':', 0), ''),
85            ('/1:3', o(':', 3), ''),
86
87            # Test parsing of text assertions
88            ('/1:3[aa^,b]', a('aa,b'), ''),
89            ('/1:3[aa-b]', a('aa-b'), ''),
90            ('/1:3[aa^-b]', a('aa-b'), ''),
91            ('/1:3[aa-^--b]', a('aa---b'), ''),
92            ('/1:3[aa^,b,c1]', a('aa,b', 'c1'), ''),
93            ('/1:3[,aa^,b]', a(after='aa,b'), ''),
94            ('/1:3[;s=a]', a(s='a'), ''),
95            ('/1:3[a;s=a]', a('a', s='a'), ''),
96            ('/1:3[a;s=a^,b,c^;d;x=y]', a('a', s=('a,b', 'c;d'), x='y'), ''),
97
98        ]:
99            self.assertEqual(p.parse_path(raw), (path, leftover))
100
101    def test_cfi_decode(self):
102        from calibre.ebooks.oeb.polish.parsing import parse
103        root = parse('''
104<html>
105<head></head>
106<body id="body01">
107        <p>…</p>
108        <p>…</p>
109        <p>…</p>
110        <p>…</p>
111        <p id="para05">xxx<em>yyy</em>0123456789</p>
112        <p>…</p>
113        <p>…</p>
114        <img id="svgimg" src="foo.svg" alt="…"/>
115        <p>…</p>
116        <p><span>hello</span><span>goodbye</span>text here<em>adieu</em>text there</p>
117    </body>
118</html>
119''', line_numbers=True, linenumber_attribute='data-lnum')
120        body = root[-1]
121
122        def test(cfi, expected):
123            self.assertIs(decode_cfi(root, cfi), expected)
124
125        for cfi in '/4 /4[body01] /900[body01] /2[body01]'.split():
126            test(cfi, body)
127
128        for i in range(len(body)):
129            test('/4/{}'.format((i + 1)*2), body[i])
130
131        p = body[4]
132        test('/4/999[para05]', p)
133        test('/4/999[para05]/2', p[0])
134
135
136def find_tests():
137    return unittest.TestLoader().loadTestsFromTestCase(Tests)
138
139
140if __name__ == '__main__':
141    unittest.TextTestRunner(verbosity=2).run(find_tests())
142