Fix clickable parent Part navigation in Calibre

This commit is contained in:
Kai Tan
2026-10-08 08:09:50 +08:00
parent a158b4e26c
commit 498b5d09ce
5 changed files with 86 additions and 2 deletions
+49
View File
@@ -67,6 +67,10 @@ def test_headings_nav_ncx_contents_share_translation_and_keep_links(tmp_path,mod
assert '第一章:根源' in link.get_text()
assert link.get_text().count('根源')==1
assert link.find_parent('ol').find_parent('li') is not None
if relative == 'nav.xhtml':
parent_link = b.find('a', href='Text/part.xhtml#part')
assert parent_link.parent.name == 'li'
assert parent_link.parent.find('ol', recursive=False) is not None
a=etree.fromstring(before.read(prefix+'toc.ncx'));b=etree.fromstring(after.read(prefix+'toc.ncx'))
assert a.xpath('//*[local-name()="content"]/@src')==b.xpath('//*[local-name()="content"]/@src')
assert a.xpath('//*[local-name()="navPoint"]/@id')==b.xpath('//*[local-name()="navPoint"]/@id')
@@ -179,3 +183,48 @@ def test_ncx_declared_utf16_is_parsed_without_utf8_assumptions(tmp_path):
z.writestr(info,data)
prepare_navigation(converted,structure,entries)
assert any(r['source_label']=='Chapter 1: Roots' for r in structure.navigation_titles)
def test_navigation_generated_wrappers_are_repaired_but_publisher_styles_remain():
from src.common.html import parse_html, translation_targets
soup=parse_html('<nav epub:type="toc"><ol><li><span data-translation-unit="true" id="tr-old"><a href="part.xhtml"><em>Part 1</em></a></span><ol><li><a href="ch.xhtml">Chapter 1</a></li></ol></li></ol></nav>')
translation_targets(soup)
link=soup.find('a',href='part.xhtml')
assert link.parent.name=='li'
assert link.em.get_text()=='Part 1'
assert link.parent.find('ol',recursive=False)
translation_targets(soup)
assert link.parent.name=='li'
def test_prepare_navigation_upgrades_cached_wrapped_part_link(tmp_path):
source,work,structure,entries=fixture(tmp_path)
nav=next(r for r in structure.resources.values() if r.href=='nav.xhtml')
soup=BeautifulSoup(nav.content,'html.parser')
link=soup.find('a',href='Text/part.xhtml#part')
wrapper=soup.new_tag('span',attrs={'data-translation-unit':'true','id':'tr-old'})
link.wrap(wrapper);nav.content=str(soup)
prepare_navigation(source,structure,entries)
assert BeautifulSoup(nav.content,'html.parser').find('a',href='Text/part.xhtml#part').parent.name=='li'
@pytest.mark.parametrize('mode',['target_only','bilingual'])
def test_calibre_resolves_parent_part_destination(tmp_path,mode):
import json
import shutil
import subprocess
calibre=shutil.which('calibre-debug')
if not calibre: pytest.skip('Calibre integration check requires calibre-debug')
source,work,structure,entries=fixture(tmp_path)
asyncio.run(BackfillEngine().backfill(structure,entries,mode))
output=tmp_path/'reader.epub';BilingualBuilder(work,source).build(structure,output)
script=('import json,sys; from calibre.ebooks.oeb.polish.container import get_container; '
'from calibre.ebooks.oeb.polish.toc import get_toc; '
'print(json.dumps(get_toc(get_container(sys.argv[-1])).as_dict))')
result=subprocess.run([calibre,'-c',script,'--',str(output)],capture_output=True,text=True,check=True,timeout=30)
toc=json.loads(result.stdout)
part=next(n for n in toc['children'] if n['children'])
assert part['dest'].endswith('Text/part.xhtml')
assert part['frag']=='part'
assert part['dest_exists'] is True
assert part['children'][0]['dest_exists'] is True