From 72a7de61468804482eefd781353441ec4a64066a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=9C=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 15 Jul 2026 18:14:08 +0700 Subject: [PATCH] Include parent sections in chapter filenames Preserve outline ancestry when generating chapter slugs so split files retain their section context. Co-authored-by: Cursor --- extract.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/extract.py b/extract.py index e76137f..14c3192 100644 --- a/extract.py +++ b/extract.py @@ -192,10 +192,17 @@ def render_page_png( 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] = [] seen_pages: set[int] = set() + parents: list[str] = [] 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(): continue 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: continue seen_pages.add(page) - sections.append({"title": title.strip(), "start_page": page}) + sections.append( + {"title": title, "parents": ancestors, "start_page": page} + ) if sections: return sections # Keep PDFs without bookmarks usable. This fallback only recognizes # numbered headings and is deliberately limited to one boundary per page. + heading_path: list[str] = [] for page_index, page in enumerate(doc, start=1): text = clean_page_text(page.get_text()) if is_toc_page(text): @@ -220,14 +230,23 @@ def toc_sections(doc: fitz.Document, level: int) -> list[dict]: if not match: continue 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 title = title.strip() if not title or not title[0].isalpha() or TOC_LINE.search(title): continue if page_index not in seen_pages: 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 return sections @@ -322,7 +341,7 @@ def convert( { "num": f"{index:02d}", "title": entry["title"], - "slug": f"{index:02d}-{slugify(entry['title'])}", + "slug": f"{index:02d}-{slugify(' - '.join((*entry['parents'], entry['title'])))}", "pages": pages, } )