Include parent sections in chapter filenames
Preserve outline ancestry when generating chapter slugs so split files retain their section context. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+24
-5
@@ -192,10 +192,17 @@ def render_page_png(
|
|||||||
|
|
||||||
|
|
||||||
def toc_sections(doc: fitz.Document, level: int) -> list[dict]:
|
def toc_sections(doc: fitz.Document, level: int) -> list[dict]:
|
||||||
"""Return unique page boundaries from the PDF outline at one level."""
|
"""Return unique page boundaries and ancestor titles from the PDF outline."""
|
||||||
sections: list[dict] = []
|
sections: list[dict] = []
|
||||||
seen_pages: set[int] = set()
|
seen_pages: set[int] = set()
|
||||||
|
parents: list[str] = []
|
||||||
for toc_level, title, page in doc.get_toc():
|
for toc_level, title, page in doc.get_toc():
|
||||||
|
title = title.strip()
|
||||||
|
if not title:
|
||||||
|
continue
|
||||||
|
parents = parents[: toc_level - 1]
|
||||||
|
ancestors = parents.copy()
|
||||||
|
parents.append(title)
|
||||||
if toc_level != level or not title.strip():
|
if toc_level != level or not title.strip():
|
||||||
continue
|
continue
|
||||||
page = max(1, min(page, doc.page_count))
|
page = max(1, min(page, doc.page_count))
|
||||||
@@ -205,12 +212,15 @@ def toc_sections(doc: fitz.Document, level: int) -> list[dict]:
|
|||||||
if page in seen_pages:
|
if page in seen_pages:
|
||||||
continue
|
continue
|
||||||
seen_pages.add(page)
|
seen_pages.add(page)
|
||||||
sections.append({"title": title.strip(), "start_page": page})
|
sections.append(
|
||||||
|
{"title": title, "parents": ancestors, "start_page": page}
|
||||||
|
)
|
||||||
if sections:
|
if sections:
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
# Keep PDFs without bookmarks usable. This fallback only recognizes
|
# Keep PDFs without bookmarks usable. This fallback only recognizes
|
||||||
# numbered headings and is deliberately limited to one boundary per page.
|
# numbered headings and is deliberately limited to one boundary per page.
|
||||||
|
heading_path: list[str] = []
|
||||||
for page_index, page in enumerate(doc, start=1):
|
for page_index, page in enumerate(doc, start=1):
|
||||||
text = clean_page_text(page.get_text())
|
text = clean_page_text(page.get_text())
|
||||||
if is_toc_page(text):
|
if is_toc_page(text):
|
||||||
@@ -220,14 +230,23 @@ def toc_sections(doc: fitz.Document, level: int) -> list[dict]:
|
|||||||
if not match:
|
if not match:
|
||||||
continue
|
continue
|
||||||
number, title = match.groups()
|
number, title = match.groups()
|
||||||
if number.count(".") + 1 != level:
|
heading_level = number.count(".") + 1
|
||||||
|
heading_path = heading_path[: heading_level - 1]
|
||||||
|
heading_path.append(title.strip())
|
||||||
|
if heading_level != level:
|
||||||
continue
|
continue
|
||||||
title = title.strip()
|
title = title.strip()
|
||||||
if not title or not title[0].isalpha() or TOC_LINE.search(title):
|
if not title or not title[0].isalpha() or TOC_LINE.search(title):
|
||||||
continue
|
continue
|
||||||
if page_index not in seen_pages:
|
if page_index not in seen_pages:
|
||||||
seen_pages.add(page_index)
|
seen_pages.add(page_index)
|
||||||
sections.append({"title": title, "start_page": page_index})
|
sections.append(
|
||||||
|
{
|
||||||
|
"title": title,
|
||||||
|
"parents": heading_path[:-1],
|
||||||
|
"start_page": page_index,
|
||||||
|
}
|
||||||
|
)
|
||||||
break
|
break
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
@@ -322,7 +341,7 @@ def convert(
|
|||||||
{
|
{
|
||||||
"num": f"{index:02d}",
|
"num": f"{index:02d}",
|
||||||
"title": entry["title"],
|
"title": entry["title"],
|
||||||
"slug": f"{index:02d}-{slugify(entry['title'])}",
|
"slug": f"{index:02d}-{slugify(' - '.join((*entry['parents'], entry['title'])))}",
|
||||||
"pages": pages,
|
"pages": pages,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user