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 {}
|
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), )
|
offsets = (step.get('temporal_offset', 0), tuple(reversed(step.get('spatial_offset', (0, 0)))), step.get('text_offset', 0), )
|
||||||
return (step_nums, offsets)
|
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
|
import unittest
|
||||||
from polyglot.builtins import map
|
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):
|
class Tests(unittest.TestCase):
|
||||||
@ -96,6 +96,40 @@ class Tests(unittest.TestCase):
|
|||||||
]:
|
]:
|
||||||
self.assertEqual(p.parse_path(raw), (path, leftover))
|
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():
|
def find_tests():
|
||||||
return unittest.TestLoader().loadTestsFromTestCase(Tests)
|
return unittest.TestLoader().loadTestsFromTestCase(Tests)
|
||||||
|
@ -1404,6 +1404,24 @@ class Boss(QObject):
|
|||||||
if name is not None and getattr(ed, 'syntax', None) == 'html':
|
if name is not None and getattr(ed, 'syntax', None) == 'html':
|
||||||
self.gui.preview.sync_to_editor(name, ed.current_tag())
|
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):
|
def goto_style_declaration(self, data):
|
||||||
name = data['name']
|
name = data['name']
|
||||||
editor = self.edit_file(name, syntax=data['syntax'])
|
editor = self.edit_file(name, syntax=data['syntax'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user