EPUB Conversion Pitfall: Reading Order Lives in the .opf Spine, Not the Filenames

EPUB chapter order is defined by the spine in content.opf, not by ZIP filenames — a classic converter pitfall.
A developer converting EPUBs to Markdown discovered scrambled chapter output caused by sorting files alphabetically by filename instead of following the reading order defined in the EPUB's `content.opf` package document. The correct approach reads `META-INF/container.xml` to locate the .opf file, parses the manifest for an id→href map, then processes chapters in spine order. EPUB content is also strict XML, not forgiving HTML, so broken chapters should be skipped rather than aborting the entire conversion. The broader lesson: for any container format, always look to the metadata for authoritative ordering — never trust filename alphabetization.
Converting an EPUB ebook to Markdown sounds straightforward — unzip, iterate, convert each file. But when one developer ran their homemade converter on a public-domain novel, the output looked like a shuffled deck of cards: the prologue ended up near the back, Chapter 10 was wedged between Chapters 1 and 2, and two appendices appeared before the epilogue.
This seemingly bizarre disorder is actually a classic misconception about container formats.
The Root Cause: Filename Order ≠ Reading Order
EPUB is essentially a ZIP archive stuffed with XHTML files. The most intuitive implementation is to sort the archive entries and convert them one by one. The problem is — the alphabetical order of filenames has nothing to do with the book's reading order.
The developer openly admitted the bug was "embarrassingly simple." Some publishers do use clean, sequential names like chapter01.xhtml, but more often you'll encounter opaque names like a11.xhtml or p-0003.xhtml that give no hint of their order. And even with clean naming, alphabetical sorting will always place chapter10 before chapter2 — a classic trap anyone who's done batch file processing has fallen into.

Where the Real Order Is Defined: The Package Document
The true reading order of an EPUB is defined in its package document, content.opf. This file contains two critical sections:
<manifest>: lists every file in the book;<spine>: an ordered set of<itemref>elements, arranged in reading order.
The developer's summary cuts right to the point: "That spine is the book itself."
In other words, the manifest tells you "what files are in the book," while the spine tells you "in what order to read them." Relying solely on filenames inside the ZIP is like guessing page numbers from the cover's thickness — completely unreliable.
OPF (Open Packaging Format) is a core component of the EPUB specification — essentially an XML file, typically named content.opf though not required to be. It serves as the "table of contents index" for the entire ebook, divided into several main sections: <metadata> stores descriptive information like title, author, and publication date; <manifest> lists all resources in the book (chapters, images, stylesheets, fonts) as key-value pairs; and <spine> is an ordered list that specifies reading sequence by referencing ids from the manifest. EPUB 2 and EPUB 3 differ slightly in detail — EPUB 3 additionally introduces a Navigation Document (nav.xhtml) as a human-readable table of contents, but the spine remains the authoritative source for programmatic parsing of reading order. Understanding this two-layer "manifest + skeleton" design is a prerequisite for correctly handling any OPF-based ebook format, including the EPUB 3 variant used by Apple Books.
The Correct Parsing Flow
The developer's fix is a clean three-step process with broad practical value:
- Start with
META-INF/container.xml. This file points to the actual.opffile — note that it is not always calledcontent.opf, and hardcoding that filename is another hidden trap. - Parse the manifest to build an id → href lookup table.
- Iterate through the
<itemref>elements in<spine>in declaration order, converting each referenced document in sequence.
The core principle here is to always trust the format's own manifest rather than whatever order the filesystem happens to provide.
A Second Surprise: Strict XML vs. Forgiving HTML
While fixing the bug, the developer stumbled into another pitfall: EPUB content files are strict XML, not forgiving HTML.
A stray entity or an unclosed tag is enough to make a strict XML parser crash outright. Worse, if a failed chapter parse aborts the entire conversion, a 300-page book might die at page 40.
The developer's approach is more pragmatic: skip broken chapters and keep going, rather than letting the entire conversion fail. This is a more robust engineering tradeoff when dealing with real-world data — completeness matters, but a single point of failure shouldn't take down the entire output.
After these fixes, the developer validated against over a dozen EPUBs, including one where the spine deliberately omitted the cover page. Chapter order was correct in every case. The test file that originally scrambled the novel now lives in their test suite.
EPUB content files follow the XHTML standard, not plain HTML, which means they inherit XML's strictness: all tags must be properly closed, attribute values must be quoted, and special characters must be escaped via entity references or CDATA sections. Named HTML entities like are undefined in standard XML — you either need to declare a DTD or use the numeric entity   instead. Many mainstream browsers and readers ship with HTML5-style lenient parsers (like Blink's HTML parser) that silently fix these errors, which actually masks the fact that the source files are non-compliant. When developers call strict XML parsing libraries directly — like Python's xml.etree.ElementTree or Java's SAX parser — these hidden defects surface as parse exceptions. When working with real-world EPUBs, choosing a fault-tolerant parsing library is the more robust strategy, such as Python's lxml with the recover=True option, or BeautifulSoup with the lxml-xml parser.
The Transferable Lesson
The developer eventually packaged this tool as a web-based utility (epub_to_markdown), but emphasizes that the methodology itself is what's worth remembering:
When dealing with a container format, always find the manifest that defines the order. Sorting by filename is just a coincidence you happened to trust.
This lesson extends well beyond EPUB. Whether it's ZIP-packaged document formats, multi-file datasets, or any directory structure that "looks ordered," the authoritative definition of sequence is usually written in the metadata — not in the alphabet of filenames. Mistaking an ordering coincidence for a rule is the breeding ground for many subtle bugs.
Related articles

Will GitHub Die? Open Source Maintainer Burnout and the Collaboration Crisis in the AI Era
Lex Fridman podcast discussion on open source maintainer burnout and GitHub's predicament: massive pull requests overwhelm core maintainers, and AI worsens code bloat. Using math journal peer review and Poincaré conjecture verification as analogies, the article analyzes why verification in knowledge production must be deliberately slow, and how open source communities can avoid the failure mode of maintainers exiting.

Stop Building Cookie-Cutter AI Websites: 3 Tools That Transform Your Coding Agent
Three tools that boost AI coding agents: FFmpeg skill for local video editing in Claude Code and Cursor, Hallmark's 50 design themes to fix cookie-cutter AI sites, and Jailbreaks' 25+ model prompts.

How America's Gerontocracy Squeezes the Younger Generation
Americans over 55 hold nearly three-quarters of national wealth, alongside an 80-year-old president and elderly congressional leaders. The Atlantic's Idrees Kahloon explains how gerontocracy drives up housing costs, squeezes opportunities for the young, and explores zoning reform and child poverty reduction as practical fixes.