mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
57438b58ef
commit
0870125019
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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'])
|
||||
|
Loading…
x
Reference in New Issue
Block a user