Edit book: Add a convenience function to jump to the line corresponding to a cfi

To use,

boss.show_partial_cfi_in_editor(filename, cfi)
This commit is contained in:
Kovid Goyal 2019-01-14 18:34:13 +05:30
parent 57438b58ef
commit 0870125019
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 90 additions and 1 deletions

View File

@ -204,3 +204,40 @@ def cfi_sort_key(cfi, only_path=True):
step = steps[-1] if steps else {}
offsets = (step.get('temporal_offset', 0), tuple(reversed(step.get('spatial_offset', (0, 0)))), step.get('text_offset', 0), )
return (step_nums, offsets)
def decode_cfi(root, cfi):
from lxml.etree import XPathEvalError
p = parser()
try:
pcfi = p.parse_path(cfi)[0]
except Exception:
import traceback
traceback.print_exc()
return
if not pcfi:
import sys
print ('Failed to parse CFI: %r' % pcfi, file=sys.stderr)
return
steps = get_steps(pcfi)
ans = root
for step in steps:
num = step.get('num', 0)
node_id = step.get('id')
try:
match = ans.xpath('descendant::*[@id="%s"]' % node_id)
except XPathEvalError:
match = ()
if match:
ans = match[0]
continue
index = 0
for child in ans.iterchildren('*'):
index |= 1 # increment index by 1 if it is even
index += 1
if index == num:
ans = child
break
else:
return
return ans

View File

@ -9,7 +9,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import unittest
from polyglot.builtins import map
from calibre.ebooks.epub.cfi.parse import parser, cfi_sort_key
from calibre.ebooks.epub.cfi.parse import parser, cfi_sort_key, decode_cfi
class Tests(unittest.TestCase):
@ -96,6 +96,40 @@ class Tests(unittest.TestCase):
]:
self.assertEqual(p.parse_path(raw), (path, leftover))
def test_cfi_decode(self):
from calibre.ebooks.oeb.polish.parsing import parse
root = parse('''
<html>
<head></head>
<body id="body01">
<p></p>
<p></p>
<p></p>
<p></p>
<p id="para05">xxx<em>yyy</em>0123456789</p>
<p></p>
<p></p>
<img id="svgimg" src="foo.svg" alt=""/>
<p></p>
<p><span>hello</span><span>goodbye</span>text here<em>adieu</em>text there</p>
</body>
</html>
''', line_numbers=True, linenumber_attribute='data-lnum')
body = root[-1]
def test(cfi, expected):
self.assertIs(decode_cfi(root, cfi), expected)
for cfi in '/4 /4[body01] /900[body01] /2[body01]'.split():
test(cfi, body)
for i in range(len(body)):
test('/4/{}'.format((i + 1)*2), body[i])
p = body[4]
test('/4/999[para05]', p)
test('/4/999[para05]/2', p[0])
def find_tests():
return unittest.TestLoader().loadTestsFromTestCase(Tests)

View File

@ -1404,6 +1404,24 @@ class Boss(QObject):
if name is not None and getattr(ed, 'syntax', None) == 'html':
self.gui.preview.sync_to_editor(name, ed.current_tag())
def show_partial_cfi_in_editor(self, name, cfi):
editor = self.edit_file(name, 'html')
if not editor or not editor.has_line_numbers:
return False
from calibre.ebooks.oeb.polish.parsing import parse
from calibre.ebooks.epub.cfi.parse import decode_cfi
root = parse(
editor.get_raw_data(), decoder=lambda x: x.decode('utf-8'),
line_numbers=True, linenumber_attribute='data-lnum')
node = decode_cfi(root, cfi)
if node is not None:
lnum = node.get('data-lnum')
if lnum:
lnum = int(lnum)
editor.current_line = lnum
return True
return False
def goto_style_declaration(self, data):
name = data['name']
editor = self.edit_file(name, syntax=data['syntax'])