Improve EPUB restoration, book terminology and Calibre QA workflow

This commit is contained in:
Kai Tan
2026-10-08 07:25:27 +08:00
parent 40491e6386
commit caf9d8f4bb
35 changed files with 1439 additions and 959 deletions
+49
View File
@@ -0,0 +1,49 @@
"""One HTML5 repair policy shared by extraction and backfilling."""
import re
from bs4 import BeautifulSoup, NavigableString, Tag
BLOCK_TAGS = {'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'div',
'section', 'article', 'blockquote', 'table', 'tr', 'td', 'th',
'ul', 'ol', 'dl', 'dt', 'dd', 'figure', 'figcaption', 'pre'}
TARGET_TAGS = {'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'th',
'dt', 'dd', 'figcaption', 'blockquote', 'div', 'section', 'article'}
SKIP_TAGS = {'script', 'style', 'pre', 'code', 'math', 'svg', 'textarea'}
def parse_html(content):
return BeautifulSoup(re.sub(r'^\s*<\?xml[^>]*\?>', '', content), 'html5lib')
def translation_targets(soup):
"""Choose disjoint leaf blocks; wrap mixed direct text without renaming containers.
A li containing p must never be extracted twice. Text before a nested list or
inside an otherwise bare body still needs its own unit.
"""
for container in list(soup.find_all(list(TARGET_TAGS | {'body'}))):
if container.find_parent(list(SKIP_TAGS)):
continue
has_blocks = container.find(list(BLOCK_TAGS)) is not None
if container.name != 'body' and not has_blocks:
continue
run = []
def flush():
if run and any(isinstance(x, NavigableString) and x.strip() or
isinstance(x, Tag) and x.get_text(strip=True) for x in run):
wrapper = soup.new_tag('span')
wrapper['data-translation-unit'] = 'true'
run[0].insert_before(wrapper)
for node in run:
wrapper.append(node.extract())
run.clear()
for child in list(container.children):
if isinstance(child, Tag) and (child.name in BLOCK_TAGS | SKIP_TAGS or child.get('data-translation-unit') == 'true' or child.find(list(BLOCK_TAGS))):
flush()
else:
run.append(child)
flush()
return [tag for tag in soup.find_all(True)
if (tag.get('data-translation-unit') == 'true' or
tag.name in TARGET_TAGS and not tag.find(list(BLOCK_TAGS)))
and not tag.find_parent(list(SKIP_TAGS))
and not tag.find_parent(attrs={'data-translation-unit': 'true'})]