I tested my EPUB-to-Markdown converter on a public-domain novel and the output read like a shuffled deck: the prologue landed near the end, chapter 10 sat between chapters 1 and 2, and two appendices showed up before the epilogue.
The bug was embarrassingly simple. An EPUB is a ZIP archive full of XHTML files, so my first implementation just iterated over the archive entries in sorted order and converted each one. But filename order has nothing to do with reading order.
The real order is defined in the package document (content.opf). It contains a <manifest> listing every file and a <spine> with an ordered list of <itemref> elements. That spine is the book. Some publishers use clean names like chapter01.xhtml, but plenty use opaque names — a11.xhtml, p-0003.xhtml — where nobody can guess the sequence from the filename. And lexical sorting puts chapter10 before chapter2 every single time.
The fix:
- Read
META-INF/container.xml, which points to the actual.opffile (it is not always named content.opf) - Parse the manifest to build an id → href map
- Walk the
<spine>itemrefs in declaration order and convert each referenced document
A second surprise while I was in there: EPUB content files are strict XML, not forgiving HTML. A stray or unclosed tag kills a strict parser, and a broken chapter mid-spine should not abort the whole book. My converter now skips a bad chapter and keeps going instead of dying on page 40 of 300.
I've since run it through a dozen EPUBs, including one where the spine deliberately omits the cover page, and chapter order has held. The scrambled-novel file is now in my test suite under a name I won't repeat here.
I ended up packaging it as https://x402.freeq.one/tools/epub_to_markdown.html — but the lesson generalizes: with container formats, always find the manifest that defines order. Sorting by filename is just a coincidence you happened to trust.
Top comments (0)