feat: Verzeichnisse unterhalb von --chapter-level als eigene Datei
Überschriften, die genau einem Verzeichnisnamen entsprechen (Quellen, Literatur, Glossar, Register, Index …), beginnen auch unterhalb von --chapter-level eine eigene Datei mit passendem Typ. Die nächste Überschrift gleicher oder höherer Ebene beendet den Abschnitt; innerhalb eines Kapitels, das selbst ein Verzeichnis ist, wird nicht weiter geteilt. Im Roman-Beispiel wird 'Quellen' aus dem Anhang so zum Literaturverzeichnis; die anderen vier Beispielbücher bleiben unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7835cde1ca
commit
2125555e2a
4 changed files with 77 additions and 5 deletions
|
|
@ -215,6 +215,18 @@ Standardmäßig stehen die Fußnoten am Ende des jeweiligen Kapitels. Alternativ
|
|||
|
||||
---
|
||||
|
||||
### Verzeichnisse: Quellen, Literatur, Register, Glossar
|
||||
|
||||
Quellen- und Literaturverzeichnisse, Sach- und Personenregister, Glossare und Anhänge aus dem E-Book werden
|
||||
übernommen. Sie stehen jeweils in einer eigenen Datei und sind in der `README.md` unter „Besondere Abschnitte“
|
||||
verlinkt. Das gilt auch, wenn ein solches Verzeichnis im Buch nur ein Unterabschnitt ist, etwa „Quellen“
|
||||
im Anhang eines Romans. Neu erzeugt wird nichts: Hat ein Buch kein Register, entsteht auch keins.
|
||||
|
||||
- Register: Buchstabengruppen werden zu Überschriften, Untereinträge sind eingerückt, Verweise auf
|
||||
Textstellen bleiben anklickbare Links.
|
||||
- Literatur: Autoren, Titel, DOI, ISBN und Internetadressen bleiben vollständig erhalten; Adressen
|
||||
bleiben Links.
|
||||
|
||||
## 7. Weiterverarbeiten
|
||||
|
||||
### In Obsidian lesen
|
||||
|
|
|
|||
|
|
@ -167,7 +167,13 @@ output/mein-buch/
|
|||
- erst als letzte Notlösung der Dateiname
|
||||
5. Ein HTML-Inhaltsverzeichnis wird übersprungen, wenn NAV/NCX vorhanden ist (keine doppelte Ausgabe).
|
||||
Links darauf zeigen auf `README.md`.
|
||||
6. Überschriftenebenen werden je Datei verschoben: Der Kapiteltitel ist `#`, alle weiteren beginnen bei `##`.
|
||||
6. Verzeichnisse erhalten immer eine eigene Datei, auch unterhalb von `--chapter-level`, z. B. ein
|
||||
`## Quellen` im Anhang. Das gilt nur für Überschriften, die genau einem Verzeichnisnamen entsprechen:
|
||||
Literatur(verzeichnis), Quellen(verzeichnis), Bibliografie, References, Glossar, Stichwortverzeichnis,
|
||||
Sach-/Personen-/Namensregister, Register, Index. Die nächste Überschrift gleicher oder höherer Ebene
|
||||
beendet den Abschnitt. Innerhalb eines Kapitels, das selbst ein Verzeichnis ist, wird nicht weiter
|
||||
geteilt.
|
||||
7. Überschriftenebenen werden je Datei verschoben: Der Kapiteltitel ist `#`, alle weiteren beginnen bei `##`.
|
||||
|
||||
Verlagsspezifische Heuristiken: Layout-Tabellen mit Klassen wie `Head1`/`Title_document` werden zu
|
||||
Überschriften. `<pre>` mit `<br/>`-Zeilen und `div.*programlisting* > span.pre` werden zu Codeblöcken.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,14 @@ KIND_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
|||
("prologue", re.compile(r"^(prolog|prologue)", re.I)),
|
||||
]
|
||||
TITLE_CLASS = re.compile(r"^(?!sub).*(heading|title)$")
|
||||
# Headings below --chapter-level that still get their own file (exact names only, no prefixes)
|
||||
SPECIAL_HEADING = re.compile(
|
||||
r"^(literatur(verzeichnis|hinweise|angaben)?|weiterführende literatur|quellen(verzeichnis|angaben|nachweise?)?|"
|
||||
r"bibliogra(fie|phie)|references|bibliography|glossar|glossary|stichwortverzeichnis|sachregister|"
|
||||
r"sachverzeichnis|register|index|personenregister|namensregister|personenverzeichnis|namensverzeichnis)$",
|
||||
re.I,
|
||||
)
|
||||
SPECIAL_KINDS = {"bibliography", "glossary", "index", "index-persons"}
|
||||
NUMBER_PREFIX = re.compile(r"^\s*(?:(?:kapitel|chapter)\s+)?(?:\d+(?:\.\d+)*|[IVXLC]+)\.?\s+", re.I)
|
||||
|
||||
|
||||
|
|
@ -90,6 +98,8 @@ class ChapterBuilder:
|
|||
book_title: str = "", authors: list[str] | None = None) -> None:
|
||||
self.level = chapter_level
|
||||
self.diag = diag
|
||||
self._special_level = 0 # level of a split-out special heading whose section is still open
|
||||
self._in_special = False # current chapter itself is a bibliography/index/glossary
|
||||
self.book_title = book_title
|
||||
self.author_names = _surnames(authors or [])
|
||||
self.nav_titles: dict[str, str] = {}
|
||||
|
|
@ -103,19 +113,37 @@ class ChapterBuilder:
|
|||
|
||||
# ------------------------------------------------------------------ split detection
|
||||
|
||||
def _split_kind(self, el: Any) -> Marker | None:
|
||||
def _split_kind(self, el: Any, update: bool = True) -> Marker | None:
|
||||
"""Chapter start at ``el``? ``update=False`` only looks ahead without changing the section state."""
|
||||
lvl = heading_level(el)
|
||||
if lvl and lvl <= self.level and has_content(el):
|
||||
if lvl and has_content(el):
|
||||
special = _special_heading(el)
|
||||
if lvl <= self.level:
|
||||
if update:
|
||||
self._special_level = 0
|
||||
self._in_special = classify_title(_heading_text(el)) in SPECIAL_KINDS
|
||||
return Marker(el, "heading", lvl)
|
||||
if self._special_level and lvl <= self._special_level:
|
||||
# a heading of the same or higher level ends the split-out special section
|
||||
if update:
|
||||
self._special_level = lvl if special else 0
|
||||
return Marker(el, "heading", lvl)
|
||||
if special and not self._in_special:
|
||||
if update:
|
||||
self._special_level = lvl
|
||||
return Marker(el, "heading", lvl)
|
||||
if local(el.tag) in CONTAINERS | {"body"}:
|
||||
types = semantic_types(el)
|
||||
if types and local(el.tag) != "body":
|
||||
if update:
|
||||
self._special_level = 0
|
||||
self._in_special = bool(SPECIAL_KINDS & set(types))
|
||||
return Marker(el, "semantic", 0, types)
|
||||
return None
|
||||
|
||||
def _contains_split(self, el: Any) -> bool:
|
||||
for e in iter_elements(el):
|
||||
if e is not el and self._split_kind(e):
|
||||
if e is not el and self._split_kind(e, update=False):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -279,6 +307,11 @@ class _TypeCarrier:
|
|||
types: list[str]
|
||||
|
||||
|
||||
def _special_heading(el: Any) -> bool:
|
||||
title = NUMBER_PREFIX.sub("", norm_ws(_heading_text(el))).strip(" :.")
|
||||
return bool(SPECIAL_HEADING.match(title))
|
||||
|
||||
|
||||
def leading_heading_level(item: Any) -> int:
|
||||
"""Heading level of ``item`` or of the first meaningful descendant inside wrapper containers."""
|
||||
node = item
|
||||
|
|
|
|||
|
|
@ -164,3 +164,24 @@ def test_adjacent_field_spans_get_a_separator(convert):
|
|||
assert "J. Liesen, V. Mehrmann Lineare Algebra Springer Studium" in text
|
||||
assert "Das Wetter war schlecht." in text
|
||||
assert "Hauptbahnhof und x<sub>i</sub>." in text
|
||||
|
||||
|
||||
def test_special_sections_below_chapter_level_get_own_files(convert):
|
||||
body = ("<h1>Anhang</h1><p>Vorbemerkung.</p>"
|
||||
"<h2><em>Quellen</em></h2><ul><li>Archiv A</li><li>Buch B, ISBN 978-0-201-89683-1</li></ul>"
|
||||
"<h2>Danksagung</h2><p>Danke allen.</p>"
|
||||
"<h2>Literatur und Film</h2><p>Ein normaler Abschnitt.</p>")
|
||||
run = convert(Book(docs=[Doc("a.xhtml", body)]))
|
||||
assert [p.name for p in run.chapters()] == ["001-anhang.md", "002-quellen.md", "003-danksagung.md"]
|
||||
assert "Literatur und Film" in run.chapter("003") # not a special heading: stays in the chapter
|
||||
assert "Archiv A" in run.chapter("002") and "Danke" not in run.chapter("002")
|
||||
assert frontmatter(run.chapter("002"))["epub_type"] == ["bibliography"]
|
||||
readme = (run.out / "README.md").read_text(encoding="utf-8")
|
||||
assert "Literatur / Quellen: [Quellen](chapters/002-quellen.md)" in readme
|
||||
|
||||
|
||||
def test_special_chapter_is_not_split_further(convert):
|
||||
body = ("<h1>Literatur</h1><h2>Quellen</h2><p>Q1</p><h2>Sekundärliteratur</h2><p>S1</p>"
|
||||
"<h1>Register</h1><h2>A</h2><p>Apfel</p><h2>Index</h2><p>x</p>")
|
||||
run = convert(Book(docs=[Doc("a.xhtml", body)]))
|
||||
assert [p.name for p in run.chapters()] == ["001-literatur.md", "002-register.md"]
|
||||
|
|
|
|||
Loading…
Reference in a new issue