I shipped the EPUB-to-Markdown converter assuming each .xhtml file in the archive was a chapter. Feed it a book, get back Markdown with # Chapter 1, # Chapter 2, in file order. Worked on every test ebook I threw at it — until a reader sent me a technical manual where the Markdown came out as: introduction, appendix, glossary, then the actual chapters in reverse.
The bug was in how I ordered output. EPUB stores reading order in content.opf as a spine — an ordered list of itemrefs pointing at manifest IDs. The manifest maps those IDs to actual file paths, which have no relationship to reading order. Files are often named chapter01.xhtml, chapter02.xhtml… but the spine is free to reference them in any sequence, and real publishers do: front matter first, back matter last, sometimes with a prologue file named part9.xhtml that belongs at position 3.
My fix was a two-pass read: parse the manifest into an ID→path map, parse the spine into an ordered ID list, then emit chapters in spine order by resolving each ID through the manifest. TOC (toc.ncx / nav.xhtml) is only for navigation labels — I still take chapter titles from the document <h1>/<title>, because the TOC and the actual heading text drift apart in real books, and agents quoting chapter titles need the truth, not the index.
The lesson: container formats lie about structure. A .epub is a zip of lies held together by XML indirection. If you're building anything that consumes ebooks — RAG ingestion, summarizers, agent reading lists — read the spine, not the filesystem. I packaged the fixed converter as https://x402.freeq.one/tools/epub_to_markdown.html
Top comments (0)