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:
Сергей Маринкевич
2026-07-15 18:14:08 +07:00
parent 7aff901568
commit 72a7de6146
+24 -5
View File
@@ -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,
} }
) )