EPUB 변환기가 소설을 알파벳순으로 정렬했을 때: 읽기 순서는 파일명이 아닌 .opf spine에 있다
요약
EPUB 파일을 마크다운 등으로 변환할 때, 단순히 파일명을 기준으로 순서대로 처리하면 내용의 실제 읽기 순서를 놓치게 됩니다. EPUB의 실제 구조는 `content.opf` 문서 내의 `<spine>` 태그에 정의된 순서(itemref)를 따라야 합니다.
핵심 포인트
- EPUB의 실제 순서는 파일명 대신 `.opf`의 `<spine>`에 있다.
- 변환 시에는 `META-INF/container.xml`을 읽어 구조를 파악해야 한다.
- XML 기반 콘텐츠는 엄격한 파싱이 필요하며, 오류가 있어도 전체 처리가 중단되지 않도록 구현하는 것이 중요하다.
저는 공공 영역의 소설에 EPUB-to-Markdown 변환기를 테스트했는데, 결과물이 섞인 카드 덱처럼 나왔습니다. 프롤로그는 끝 근처에 배치되었고, 챕터 10은 챕터 1과 2 사이에 위치했으며, 두 개의 부록이 에필로그보다 먼저 나타났습니다.
버그는 너무나도 간단했습니다. EPUB은 XHTML 파일들로 가득 찬 ZIP 아카이브이기 때문에, 저의 첫 번째 구현은 단순히 아카이브 항목들을 정렬된 순서로 반복하며 각각을 변환하는 방식이었습니다. 하지만 파일명 순서는 읽기 순서와 아무런 관련이 없습니다.
실제 순서는 패키지 문서(content.opf)에 정의되어 있습니다. 이 문서는 모든 파일을 나열한 <manifest>와 순서가 지정된 <itemref> 요소 목록을 가진 <spine>을 포함합니다. 그 spine이야말로 책의 실제 구조입니다. 일부 출판사들은 chapter01.xhtml과 같은 깔끔한 이름을 사용하지만, 많은 경우엔 누가 파일명만 보고도 순서를 추측할 수 없는 불투명한 이름들—예: a11.xhtml, p-0003.xhtml—을 사용합니다. 게다가 사전식 정렬(lexical sorting)은 항상 chapter10을 chapter2보다 먼저 배치합니다.
수정 방법:
- 실제
.opf파일이 위치한META-INF/container.xml을 읽습니다 (항상 content.opf라는 이름인 것은 아닙니다). - manifest를 파싱하여 id → href 맵을 구축합니다.
<spine>의 itemrefs를 선언된 순서대로 탐색하며 참조되는 각 문서를 변환합니다.
제가 그 과정에서 발견한 두 번째 놀라운 점은, EPUB 콘텐츠 파일이 관대한 HTML이 아니라 엄격한 XML이라는 것입니다. 흩어진 나 닫히지 않은 태그 하나만 있어도 엄격한 파서(strict parser)는 작동을 멈추고, spine 중간에 깨진 챕터가 있다고 해서 책 전체를 중단시킬 필요는 없습니다. 이제 제 변환기는 문제가 있는 챕터를 건너뛰고 계속 진행하며, 300페이지 중 40페이지에서 죽지 않습니다.
저는 이후 수십 개의 EPUB을 테스트했으며, 심지어 spine이 의도적으로 표지 페이지를 누락한 경우에도 챕터 순서가 유지되는 것을 확인했습니다. 이 엉망진창인 소설 파일은 제가 다시 언급하지 않을 이름으로 제 테스트 스위트에 저장되어 있습니다.
결국 저는 이것을 https://x402.freeq.one/tools/epub_to_markdown.html으로 패키징하게 되었는데, 이 교훈은 일반화됩니다: 컨테이너 형식(container formats)을 사용할 때는 항상 순서를 정의하는 매니페스트(manifest)를 찾으세요. 파일명으로 정렬하는 것은 단지 우연히 신뢰했던 것일 뿐입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기