292 lines
17 KiB
Markdown
292 lines
17 KiB
Markdown
|
|
# epub2md
|
|||
|
|
|
|||
|
|
Kommandozeilenwerkzeug, das **DRM-freie EPUB-Dateien** (EPUB 2 und EPUB 3) lokal und offline in eine
|
|||
|
|
strukturierte Sammlung **kapitelweiser Markdown-Dateien** umwandelt – mit extrahierten Medien,
|
|||
|
|
YAML-Metadaten, Inhaltsverzeichnis, maschinenlesbarem Manifest und einem Konvertierungsbericht.
|
|||
|
|
|
|||
|
|
Gedacht für die Weiterverarbeitung mit Markdown-Renderern, Pandoc, Obsidian/Zettelkasten, Git,
|
|||
|
|
RAG-/Embedding-Pipelines, Volltextsuche und lokale LLMs.
|
|||
|
|
|
|||
|
|
## Grenzen
|
|||
|
|
|
|||
|
|
- **Kein DRM:** Verschlüsselte EPUBs (`META-INF/encryption.xml` mit verschlüsselten Inhalten) werden mit
|
|||
|
|
Exit-Code 2 abgelehnt. Es wird kein Kopierschutz umgangen. Reine Font-Obfuscation ist erlaubt.
|
|||
|
|
- **Keine OCR:** Gescannte Bücher (Seiten als Bilder) liefern nur Bildreferenzen, keinen Text.
|
|||
|
|
- **Formeln als Bild/SVG** werden nie in TeX „geraten“. Sie bleiben als Bild/SVG erhalten und werden im
|
|||
|
|
Bericht gemeldet. TeX entsteht nur aus echtem MathML, TeX-Annotationen oder TeX im `alt`-Text.
|
|||
|
|
- **Layout-Heuristiken** (z. B. Überschriften, die als Tabellen gesetzt sind) sind konservativ und
|
|||
|
|
erfassen nicht jedes Verlagslayout.
|
|||
|
|
|
|||
|
|
## Installation (Ubuntu 24.04)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo apt install python3 python3-venv
|
|||
|
|
python3 -m venv .venv
|
|||
|
|
source .venv/bin/activate
|
|||
|
|
pip install -r requirements.txt # Laufzeit: nur lxml
|
|||
|
|
pip install -r requirements-dev.txt # Tests: pytest, PyYAML
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Laufzeitabhängigkeit ist ausschließlich **lxml** (sicheres XML/HTML-Parsing). Alles andere ist
|
|||
|
|
Standardbibliothek. Pandoc, Calibre, Java oder Node.js werden **nicht** benötigt. Pandoc ist nur für
|
|||
|
|
die optionale Weiterverarbeitung der Ausgabe nützlich.
|
|||
|
|
|
|||
|
|
## Aufruf
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python3 epub2md.py INPUT.epub OUTPUT_VERZEICHNIS [Optionen]
|
|||
|
|
# alternativ
|
|||
|
|
python3 -m epub2md INPUT.epub OUTPUT_VERZEICHNIS [Optionen]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Beispiele:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Programmierbuch mit Standardoptionen
|
|||
|
|
python3 epub2md.py \
|
|||
|
|
Beispiele/programmierbuch.epub \
|
|||
|
|
output/programmierbuch \
|
|||
|
|
--format gfm \
|
|||
|
|
--math auto \
|
|||
|
|
--footnotes end-of-chapter \
|
|||
|
|
--extract-media \
|
|||
|
|
--frontmatter \
|
|||
|
|
--toc \
|
|||
|
|
--verbose
|
|||
|
|
|
|||
|
|
# Roman mit Teilen (h1) und Kapiteln (h2): ein File pro Kapitel
|
|||
|
|
python3 epub2md.py roman.epub output/roman --chapter-level 2
|
|||
|
|
|
|||
|
|
# Nur analysieren, nichts schreiben
|
|||
|
|
python3 epub2md.py buch.epub output/buch --dry-run
|
|||
|
|
|
|||
|
|
# Erneut konvertieren und frühere Ausgabe ersetzen
|
|||
|
|
python3 epub2md.py buch.epub output/buch --force --report berichte/buch.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Optionen
|
|||
|
|
|
|||
|
|
| Option | Standard | Wirkung |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| `--format {gfm,commonmark}` | `gfm` | `gfm`: Pipe-Tabellen, `~~durchgestrichen~~`, `[^fn]`-Fußnoten. `commonmark`: Tabellen als HTML, `<del>`, Fußnoten als Anker-Links |
|
|||
|
|
| `--extract-media` / `--no-extract-media` | an | Medien nach `media/` extrahieren. Ohne Extraktion stehen Bildreferenzen als Text `[Bild: alt – Pfad]` da (keine kaputten Links) |
|
|||
|
|
| `--copy-original` / `--no-copy-original` | aus | Original-EPUB nach `source/` kopieren |
|
|||
|
|
| `--chapter-level N` | `1` | Überschriftenebene (1–6), ab der eine neue Kapiteldatei beginnt |
|
|||
|
|
| `--toc` / `--no-toc` | an | `README.md` mit Inhaltsverzeichnis, Statistik und Reproduktionsbefehl |
|
|||
|
|
| `--frontmatter` / `--no-frontmatter` | an | YAML-Frontmatter in jeder Kapiteldatei |
|
|||
|
|
| `--footnotes {keep-links,end-of-chapter,end-of-book,inline}` | `end-of-chapter` | siehe [Fußnoten](#fußnoten-modi) |
|
|||
|
|
| `--math {auto,tex,mathml,image}` | `auto` | siehe [Mathematik](#math-modi) |
|
|||
|
|
| `--images {relative,flat,original-path}` | `relative` | `relative`: `media/images/0001-name.ext`; `flat`: `media/name.ext`; `original-path`: `media/<Pfad im EPUB>` |
|
|||
|
|
| `--slug-language {de,en,none}` | `de` | Dateinamen: `de` ä→ae, ß→ss; `en` ä→a, ß→ss; `none` Unicode bleibt |
|
|||
|
|
| `--keep-html` | aus | Unbekannte/nicht sicher konvertierbare Elemente als bereinigtes HTML erhalten (sonst: Text übernommen + Eintrag in `diagnostics/unsupported-elements.md`) |
|
|||
|
|
| `--strict` | aus | Abbruch bei kritischen Fehlern (fehlende/unlesbare Spine-Dokumente, unsichere ZIP-Pfade, ungültige Spine-Verweise) |
|
|||
|
|
| `--force` | aus | Bestehendes Ausgabeverzeichnis ersetzen – siehe [Sicherheit](#sicherheit-und-fehlerbehandlung) |
|
|||
|
|
| `--verbose`, `-v` | aus | Verarbeitungsschritte und Warnungen auf stderr |
|
|||
|
|
| `--dry-run` | aus | EPUB analysieren, geplante Kapiteldateien anzeigen, **nichts** schreiben (auch kein `--report`) |
|
|||
|
|
| `--report DATEI.json` | – | Bericht zusätzlich an diesen Ort schreiben. `OUTPUT/conversion-report.json` entsteht immer |
|
|||
|
|
| `--version` | – | Programmversion |
|
|||
|
|
|
|||
|
|
### Rückgabecodes
|
|||
|
|
|
|||
|
|
| Code | Bedeutung |
|
|||
|
|
| --- | --- |
|
|||
|
|
| 0 | Erfolgreich, ggf. mit nicht-kritischen Warnungen |
|
|||
|
|
| 1 | Ungültige Parameter, Eingabedatei fehlt/nicht lesbar, Ausgabeverzeichnis belegt |
|
|||
|
|
| 2 | ZIP-/Container-Fehler, DRM, unsichere ZIP-Pfade (mit `--strict`) |
|
|||
|
|
| 3 | OPF-/Spine-/XHTML-Fehler (z. B. leere Spine; mit `--strict` auch einzelne defekte Dokumente) |
|
|||
|
|
| 4 | Unerwarteter interner Fehler (Traceback mit `--verbose`) |
|
|||
|
|
|
|||
|
|
## Ausgabestruktur
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
output/mein-buch/
|
|||
|
|
├── README.md # Titel, Metadaten, Inhaltsverzeichnis, Statistik, Reproduktion
|
|||
|
|
├── metadata.yaml # Buchmetadaten (nur vorhandene Felder, nichts erfunden)
|
|||
|
|
├── manifest.json # Kapitel mit Anker-Listen, Wortzahl, Code/Bild/Formel-Zählern – für RAG
|
|||
|
|
├── conversion-report.json # vollständiger Bericht (Optionen, SHA-256, Warnungen, Formeln, Links …)
|
|||
|
|
├── source/mein-buch.epub # nur mit --copy-original
|
|||
|
|
├── media/
|
|||
|
|
│ ├── images/0001-cover.jpg # stabile, kollisionsfreie Namen; jede Quelle genau einmal
|
|||
|
|
│ ├── audio/ video/ other/ # Audio, Video, Schriften, CSS …
|
|||
|
|
├── chapters/
|
|||
|
|
│ ├── 001-cover.md
|
|||
|
|
│ ├── 002-vorwort.md
|
|||
|
|
│ ├── 003-1-einleitung.md
|
|||
|
|
│ ├── …
|
|||
|
|
│ └── 999-anmerkungen.md # nur bei --footnotes end-of-book
|
|||
|
|
└── diagnostics/
|
|||
|
|
├── warnings.md # Warnungen/Hinweise gruppiert, für Menschen
|
|||
|
|
└── unsupported-elements.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- Dateinamen: fortlaufend dreistellig nummeriert (`001-…`) plus Slug aus dem Kapiteltitel. Gleiche Titel
|
|||
|
|
bekommen eindeutige Namen, weil die Nummer immer Teil des Namens ist.
|
|||
|
|
- Kapitel-Frontmatter (YAML, `---`-Begrenzer): `book_title`, `book_author`, `language`,
|
|||
|
|
`chapter_number`, `chapter_title`, `source_epub`, `source_href` (+ `source_hrefs`, wenn ein Kapitel aus
|
|||
|
|
mehreren XHTML-Dateien besteht), `epub_type`, `conversion_status` (`converted` oder
|
|||
|
|
`converted-with-warnings`).
|
|||
|
|
- Spezialabschnitte (Glossar, Literatur, Sach-/Personenregister, Anhang, Endnoten) erhalten eigene
|
|||
|
|
Dateien und werden in `README.md` unter „Besondere Abschnitte“ verlinkt.
|
|||
|
|
- Nicht-lineare Spine-Dokumente (`linear="no"`, z. B. Bild-Zoomseiten) bleiben in Spine-Reihenfolge
|
|||
|
|
erhalten und sind im Inhaltsverzeichnis als *Zusatzseite* markiert.
|
|||
|
|
|
|||
|
|
## Wie Kapitel gebildet werden
|
|||
|
|
|
|||
|
|
1. Die **OPF-Spine** bestimmt die Lesereihenfolge (nicht Dateinamen, nicht das Inhaltsverzeichnis).
|
|||
|
|
2. Überschriften `h1`…`hN` (N = `--chapter-level`) und semantische Abschnitte (`epub:type`/`role`:
|
|||
|
|
chapter, part, appendix, glossary, bibliography, index, preface, …) beginnen neue Kapiteldateien,
|
|||
|
|
auch mitten in einer XHTML-Datei.
|
|||
|
|
3. Eine XHTML-Datei, die nur mit einer tieferen Überschrift beginnt (z. B. eine Datei pro Abschnitt
|
|||
|
|
`1.2`), wird an das laufende Kapitel angehängt.
|
|||
|
|
4. Dateien ohne Überschrift (Cover, Titelei, Widmung) werden eigene Kapitel. Ihr Titel kommt aus
|
|||
|
|
NAV/NCX, `<title>`, `epub:type` oder dem Dateinamen.
|
|||
|
|
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 `##`.
|
|||
|
|
|
|||
|
|
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.
|
|||
|
|
Literaturlisten mit eigenen Labels (`[BryL06]`) werden nicht doppelt nummeriert.
|
|||
|
|
|
|||
|
|
**Tipp:** Hat ein Buch Teile als `h1` und Kapitel als `h2` (typisch bei Romanen), erzeugt
|
|||
|
|
`--chapter-level 2` eine Datei pro Kapitel. Mit dem Standard `1` entsteht eine Datei pro Teil.
|
|||
|
|
|
|||
|
|
## Math-Modi
|
|||
|
|
|
|||
|
|
| Modus | MathML | TeX in `alt`/Annotation | SVG-/Bildformel ohne TeX |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| `auto` | konservativ → TeX `$…$` / `$$…$$`; bei Unklarheit MathML behalten | TeX `$…$` / `$$…$$` | Bild + Warnung |
|
|||
|
|
| `tex` | wie auto, aber Begrenzer `\(…\)` / `\[…\]` (nie `$`) | TeX `\(…\)` / `\[…\]` | Bild + Warnung |
|
|||
|
|
| `mathml` | MathML als eingebettetes XML (Namespace normalisiert) | Bild (TeX bleibt im Alt-Text) | Bild |
|
|||
|
|
| `image` | `altimg`-Bild falls vorhanden, sonst MathML + Warnung | Bild | Bild |
|
|||
|
|
|
|||
|
|
- Die MathML→TeX-Umsetzung unterstützt `mi, mn, mo, mtext, mspace, mrow, mstyle, mfrac, msqrt, mroot,
|
|||
|
|
msup, msub, msubsup, mover, munder, munderover, mfenced, mtable/mtr/mtd, semantics, maction`.
|
|||
|
|
Bei allem anderen (z. B. `menclose`, `mmultiscripts`, `mlabeledtr`) bleibt das MathML erhalten. Lieber
|
|||
|
|
MathML als falsches TeX.
|
|||
|
|
- TeX-Annotationen (`<annotation encoding="application/x-tex">`) haben Vorrang.
|
|||
|
|
- Im `alt`-Text doppelt escapte Zeichen (`&amp;` → `&`) werden repariert.
|
|||
|
|
- Gleichungsnummern wie `(3.7)` bleiben als Text sichtbar, Gleichungsanker als `<a id>` erhalten.
|
|||
|
|
- Formelbilder, die durch TeX ersetzt wurden, werden nicht extrahiert. Die Anzahl steht im Bericht
|
|||
|
|
(`formula_images_replaced_by_tex`).
|
|||
|
|
- **Qualität hängt vom EPUB ab:** Echtes MathML/TeX liefert semantische Formeln. Reine SVG-/Bitmap-Formeln
|
|||
|
|
bleiben Bilder.
|
|||
|
|
|
|||
|
|
## Fußnoten-Modi
|
|||
|
|
|
|||
|
|
Erkannt werden `epub:type="noteref|footnote|endnote|rearnote"`, `role="doc-noteref|doc-footnote|doc-endnote"`,
|
|||
|
|
Klassen wie `footnote`/`endnote` und Links auf solche Ziele. Rücklinks innerhalb der Note werden entfernt.
|
|||
|
|
Die Note erscheint nicht zusätzlich im Fließtext.
|
|||
|
|
|
|||
|
|
| Modus | Ergebnis |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `end-of-chapter` | `Text[^fn-001]` und `[^fn-001]: …` am Kapitelende. Mehrere Absätze, Code, Formeln und Links bleiben erhalten. Mehrfachverweise nutzen dieselbe Definition |
|
|||
|
|
| `end-of-book` | `chapters/999-anmerkungen.md` mit Ankern. Verweise `<sup>[1](999-anmerkungen.md#fn-001)</sup>`, Rücklinks `↩` zu jeder Verweisstelle |
|
|||
|
|
| `inline` | `Text (Anmerkung: …)`. Noten mit Blockinhalt (Code, Listen, Tabellen) fallen auf `end-of-chapter` zurück (Hinweis im Bericht) |
|
|||
|
|
| `keep-links` | Originalstruktur: Note bleibt an ihrer Stelle, Verweise sind normale Links auf Anker |
|
|||
|
|
|
|||
|
|
Mit `--format commonmark` werden Fußnoten als Anker-Links mit Abschnitt „Anmerkungen“ ausgegeben,
|
|||
|
|
weil CommonMark keine `[^…]`-Syntax kennt.
|
|||
|
|
|
|||
|
|
## Qualitätshinweise
|
|||
|
|
|
|||
|
|
- **Formelbasierte EPUBs:** Am besten sind EPUBs mit MathML oder TeX im `alt`-Text (z. B. Springer).
|
|||
|
|
- **SVG-Formeln** bleiben SVG (nie gerastert) und werden im Bericht als Formel markiert.
|
|||
|
|
- **Layouttabellen / komplexe Tabellen** (`colspan`, `rowspan`, Blockinhalte in Zellen, verschachtelte
|
|||
|
|
Tabellen) bleiben als bereinigtes HTML im Markdown erhalten, mit Warnung. Einfache Tabellen werden
|
|||
|
|
GFM-Pipe-Tabellen. Einzellige Layouttabellen werden als normaler Text ausgegeben.
|
|||
|
|
- **Syntax-Highlighting:** Farb-Spans, `<strong>`/`<em>` in Listings und Zeilennummern werden entfernt.
|
|||
|
|
Einrückung, Tabs, `<`, `>`, `&`, `$`, `|`, Backslashes und Backticks bleiben exakt erhalten. Die Sprache
|
|||
|
|
kommt aus Klassen/Attributen (`language-python`, `data-lang`, …) oder starken Inhaltssignalen
|
|||
|
|
(`#include`, `def …:`, Shebang, …). Ist sie unsicher, bleibt das Label leer.
|
|||
|
|
- **Gescannte Texte:** nur Bilder, kein Text (keine OCR).
|
|||
|
|
- **DRM:** wird erkannt und abgelehnt.
|
|||
|
|
- **Register:** Buchstabengruppen werden zu `##`-Überschriften, Untereinträge erhalten `– `. Interne
|
|||
|
|
Registerlinks werden auf Kapiteldatei + Anker umgeschrieben. Seitenzahlen bleiben als Text sichtbar.
|
|||
|
|
|
|||
|
|
## Weiterverarbeitung mit Pandoc
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pandoc output/programmierbuch/chapters/*.md \
|
|||
|
|
--metadata-file=output/programmierbuch/metadata.yaml \
|
|||
|
|
-o programmierbuch.html
|
|||
|
|
|
|||
|
|
pandoc output/mathematikbuch/chapters/*.md \
|
|||
|
|
--metadata-file=output/mathematikbuch/metadata.yaml \
|
|||
|
|
--pdf-engine=xelatex \
|
|||
|
|
-o mathematikbuch.pdf
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- Für HTML mit Formeln `--mathml`, `--mathjax` oder `--katex` verwenden. Pandocs Standard-HTML kann
|
|||
|
|
Matrizen nicht in Unicode umsetzen, meldet „Could not convert TeX math“ und belässt das TeX.
|
|||
|
|
- Ausgaben mit `--math tex` (`\(…\)`) brauchen `-f markdown+tex_math_single_backslash`.
|
|||
|
|
- Das YAML-Frontmatter jeder Kapiteldatei wird von Pandoc als Metadaten gelesen. Für ein Gesamtdokument
|
|||
|
|
ggf. `--no-frontmatter` verwenden.
|
|||
|
|
|
|||
|
|
## Bericht und Diagnose
|
|||
|
|
|
|||
|
|
`conversion-report.json` enthält u. a. Programmversion, Zeitstempel, Eingabedatei mit SHA-256, EPUB-Version,
|
|||
|
|
Optionen, Metadaten, Kapitel mit Statistik, alle Medien, Codeblöcke (Sprache + Erkennungsart),
|
|||
|
|
Formeln (Herkunft + Darstellung), Fußnoten (Modus, erkannt/referenziert/ausgegeben), Spezialabschnitte,
|
|||
|
|
Warnungen, nicht-fatale Fehler, nicht aufgelöste interne Links, nicht unterstützte Elemente,
|
|||
|
|
übersprungene Dokumente und das Ergebnis eines **Linkchecks über die erzeugte Ausgabe**. Dabei wird
|
|||
|
|
jeder relative Link und jedes Bild auf Zieldatei und Anker geprüft.
|
|||
|
|
|
|||
|
|
`status` ist `success`, `success-with-warnings` oder `success-with-errors`. Bei Warnungen wird nie
|
|||
|
|
Verlustfreiheit behauptet (`lossless: false`).
|
|||
|
|
|
|||
|
|
## Sicherheit und Fehlerbehandlung
|
|||
|
|
|
|||
|
|
- Das EPUB wird **nie** pauschal entpackt. Einträge werden einzeln gelesen. Absolute Pfade und `..`
|
|||
|
|
werden ignoriert und gemeldet (mit `--strict`: Abbruch). Alle Ausgabedateien werden vor dem Schreiben
|
|||
|
|
gegen das Zielverzeichnis geprüft.
|
|||
|
|
- Schutz gegen ZIP-Bomben: Größenlimit je Eintrag und Prüfung des Kompressionsverhältnisses.
|
|||
|
|
- XML wird mit lxml **ohne DTD-Laden, ohne Entity-Auflösung und ohne Netzwerk** geparst (kein XXE).
|
|||
|
|
HTML-Entities (` ` …) werden vorab in Zeichenreferenzen umgesetzt.
|
|||
|
|
- Ungültiges UTF-8 wird mit Ersatzzeichen gelesen und als Warnung gemeldet.
|
|||
|
|
- Keine Netzwerkzugriffe, keine Shell-Aufrufe, keine Telemetrie.
|
|||
|
|
- Die Ausgabe entsteht in einem temporären Verzeichnis neben dem Ziel und wird erst am Ende an ihren Platz
|
|||
|
|
verschoben. Bei Fehlern wird das temporäre Verzeichnis gelöscht, und es bleibt keine halbe Ausgabe zurück.
|
|||
|
|
- **Bestehendes Ausgabeverzeichnis:** Ist es nicht leer, bricht das Programm ab (Exit 1). `--force` ersetzt
|
|||
|
|
es nur, wenn es eine frühere epub2md-Ausgabe ist (`conversion-report.json`/`manifest.json` vorhanden),
|
|||
|
|
nicht das Arbeits- oder Home-Verzeichnis ist und die Eingabedatei nicht darin liegt.
|
|||
|
|
|
|||
|
|
## Architektur
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
epub2md.py Einstiegspunkt
|
|||
|
|
epub2md/
|
|||
|
|
├── cli.py argparse, Exit-Codes
|
|||
|
|
├── pipeline.py Ablaufsteuerung, Schreiben (atomar), Bericht/Manifest
|
|||
|
|
├── epub_reader.py ZIP-Sicherheit, DRM-Check, container.xml, OPF, NAV, NCX, Guide
|
|||
|
|
├── normalize.py Laden der Spine-Dokumente, Layout-Heuristiken, Seitenumbrüche, HTML-TOC
|
|||
|
|
├── footnotes.py Erkennung/Auslagerung von Fuß- und Endnoten
|
|||
|
|
├── chapters.py Kapitelbildung (Spine + Überschriften + epub:type), Klassifikation
|
|||
|
|
├── references.py Ziel-IDs → Kapiteldatei + Anker, Link-Auflösung
|
|||
|
|
├── html_converter.py rekursiver XHTML→Markdown-Renderer (Blöcke, Inline, Tabellen, Listen, Bilder)
|
|||
|
|
├── codeblocks.py Codeerkennung, exakte Textextraktion, Spracherkennung, Fences
|
|||
|
|
├── mathml.py konservative MathML→TeX-Konvertierung, TeX-Erkennung
|
|||
|
|
├── media.py Medienregistry mit stabilen Namen
|
|||
|
|
├── linkcheck.py Validierung aller relativen Links/Anker der Ausgabe
|
|||
|
|
├── writer.py README, metadata.yaml, Frontmatter, Diagnose-Markdown
|
|||
|
|
├── diagnostics.py zentrale Sammlung von Warnungen/Fehlern
|
|||
|
|
├── models.py Dataclasses (Kapitel, Medien, Noten, Metadaten …)
|
|||
|
|
└── utils.py Slugs, YAML-Emitter, Pfadhelfer
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Statt BeautifulSoup wird direkt lxml verwendet: Es ist für die XHTML-Bäume ausreichend, schneller und
|
|||
|
|
ermöglicht die sichere Parser-Konfiguration (XXE). Die Konvertierung ist deterministisch. Gleiche
|
|||
|
|
Eingabe und Optionen ergeben identische Kapiteldateien.
|
|||
|
|
|
|||
|
|
## Tests
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python3 -m pytest -q
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Die Tests erzeugen synthetische Minimal-EPUBs (EPUB 2/3, NAV/NCX, Code, Tabellen, MathML, SVG,
|
|||
|
|
Fußnoten, Glossar/Literatur/Register, unsichere ZIP-Pfade, DRM, XXE, defekte Container, Kodierung) und
|
|||
|
|
prüfen die tatsächliche CLI-Ausgabe. Liegen EPUBs unter `Beispiele/`, werden sie zusätzlich auf
|
|||
|
|
Struktur-Invarianten geprüft: keine kaputten Links, balancierte Code-Fences, gültiges YAML, alle Medien
|
|||
|
|
vorhanden. Dabei werden keine Buchtexte verglichen. `Beispiele/` ist per `.gitignore` vom Repository
|
|||
|
|
ausgeschlossen.
|