Fix nested chapter anchors and Chinese ordinal navigation
This commit is contained in:
@@ -98,6 +98,12 @@ def _headings(soup, target, label):
|
||||
anchor = soup.find(id=fragment) if fragment else None
|
||||
if fragment and anchor is None:
|
||||
return [], 'missing anchor'
|
||||
# Publisher TOCs often target an <a>/<span> inside a heading. The
|
||||
# enclosing heading owns the text; subsequent prose is not the target.
|
||||
if anchor and anchor.name not in HEADINGS:
|
||||
enclosing = anchor.find_parent(HEADINGS)
|
||||
if enclosing is not None:
|
||||
anchor = enclosing
|
||||
identity = title_identity(label)
|
||||
# Only inspect the opening leaf blocks or blocks at the explicit anchor.
|
||||
# An exact match across adjacent blocks also recognizes Calibre-converted
|
||||
@@ -108,7 +114,7 @@ def _headings(soup, target, label):
|
||||
nearby = [anchor] + anchor.find_all_next(HEADINGS + ['p','div'], limit=16)
|
||||
block_ids = {id(t) for t in blocks}
|
||||
blocks = [t for t in nearby if id(t) in block_ids]
|
||||
blocks = [t for t in blocks if plain(t.get_text())][:12]
|
||||
blocks = [t for t in blocks if any(c.isalnum() for c in plain(t.get_text()))][:12]
|
||||
matches = []
|
||||
for start in range(min(len(blocks), 6)):
|
||||
for end in range(start+1, min(start+4, len(blocks))+1):
|
||||
@@ -220,7 +226,10 @@ def normalize_heading_html(record, html):
|
||||
prefix=ordinal(record)
|
||||
if not prefix: return html
|
||||
soup=parse_html('<div>'+html+'</div>');root=soup.body.div
|
||||
nodes=[n for n in root.find_all(string=True) if plain(str(n))]
|
||||
hidden = [n for n in root.find_all(string=True) if plain(str(n)) and
|
||||
n.find_parent(class_=re.compile(r'^(?:sr-only|visually-hidden|screen-reader-only)$'))]
|
||||
hidden_ids = {id(n) for n in hidden}
|
||||
nodes=[n for n in root.find_all(string=True) if plain(str(n)) and id(n) not in hidden_ids]
|
||||
if not nodes: return html
|
||||
node=nodes[0];text=plain(str(node));compact=re.sub(r'\s+', '', text).strip('::')
|
||||
# Some publishers put the digit in its own styled span: 第<span>1</span>部.
|
||||
@@ -228,11 +237,18 @@ def normalize_heading_html(record, html):
|
||||
and re.fullmatch(r'[章部篇][::]?',plain(str(nodes[2])))):
|
||||
nodes[1].replace_with(chinese_number(record['number']))
|
||||
nodes[2].replace_with('章' if record['role']=='chapter' else '部')
|
||||
elif re.fullmatch(r'(?:\d+|[IVXLCDM]+|第[零一二三四五六七八九十百千万\d]+(?:章|部分?|篇))',compact,re.I):
|
||||
elif re.fullmatch(r'(?:\d+|[IVXLCDM]+|[零一二三四五六七八九十百千万]+(?:章|部分?|篇)?|第[零一二三四五六七八九十百千万\d]+(?:章|部分?|篇))',compact,re.I):
|
||||
node.replace_with(prefix)
|
||||
if len(nodes)>1 and plain(str(nodes[1])) in {':',':'}: nodes[1].replace_with('')
|
||||
elif len(nodes)>1 and plain(str(nodes[1])) in {'章','部','部分','篇'}: nodes[1].replace_with('')
|
||||
elif re.match(r'^第[零一二三四五六七八九十百千万\d]+[章部篇]',compact):
|
||||
node.replace_with(re.sub(r'^第[零一二三四五六七八九十百千万\d]+(?:章|部分?|篇)',prefix,compact))
|
||||
visible_now = [n for n in root.find_all(string=True) if plain(str(n)) and id(n) not in hidden_ids]
|
||||
if visible_now and plain(str(visible_now[0])).startswith(prefix):
|
||||
# The visible ordinal now includes "chapter/part"; the hidden prefix
|
||||
# would otherwise announce it a second time to assistive readers.
|
||||
for text_node in hidden:
|
||||
text_node.parent['aria-hidden'] = 'true'
|
||||
return root.decode_contents()
|
||||
|
||||
|
||||
@@ -242,7 +258,13 @@ def navigation_labels(structure, entries):
|
||||
units=[by_id.get(key) for key in record.get('entry_ids',[record['entry_id']])]
|
||||
if any(not e or not e.translated_html or e.validation_errors for e in units): continue
|
||||
units[0].translated_html=normalize_heading_html(record,units[0].translated_html)
|
||||
label=plain(' '.join(parse_html(e.translated_html).get_text(' ',strip=True) for e in units))
|
||||
visible = []
|
||||
for entry in units:
|
||||
soup = parse_html(entry.translated_html)
|
||||
for hidden in soup.select('[aria-hidden="true"], .sr-only, .visually-hidden, .screen-reader-only'):
|
||||
hidden.decompose()
|
||||
visible.append(soup.get_text(' ', strip=True))
|
||||
label=plain(' '.join(visible))
|
||||
prefix=ordinal(record)
|
||||
# Standardize punctuation only for the navigation label; the rich
|
||||
# heading can keep its number and subtitle on separate lines.
|
||||
|
||||
Reference in New Issue
Block a user