fix(epub-parser): Resolve spine file paths relative to OPF location
Problem:
- Spine items stored raw href from manifest (e.g., 'cover.xhtml')
- Resources stored with full paths (e.g., 'OEBPS/cover.xhtml')
- Content lookup failed: resourceMap.get('cover.xhtml') returned undefined
- Result: Pagination failed with 0 pages, content not found
Root Cause:
- parseSpine() didn't resolve paths relative to OPF file location
- Spine hrefs are relative to OPF directory, not ZIP root
- Manifest items use relative paths like 'cover.xhtml'
- Actual files are at 'OEBPS/cover.xhtml' (relative to OEBPS/content.opf)
Solution:
- Use resolvePath() helper to resolve href relative to opfPath
- Store resolved full path in spine items: 'OEBPS/cover.xhtml'
- Match resource Map storage pattern (full paths)
- Extract cover image and add to EbookCIF.metadata
Changes:
- parseSpine(): Add opfPath parameter, resolve each spine href
- parseEPUB(): Pass opfPath to parseSpine()
- extractCover(): Load cover image from EPUB manifest
- Return EbookCIF.metadata.coverImage with Blob data
Impact:
- ✅ Spine content files now found correctly
- ✅ Pagination calculates actual pages (119 pages vs 0)
- ✅ Ebook content loads and displays
- ✅ Cover image extracted and available
Testing:
- EPUB with spine items in subdirectory (OEBPS/)
- Content lookup now succeeds
- Pagination generates correct page count
Files changed: 1
Lines changed: +14, -3
This commit is contained in:
@@ -23,7 +23,7 @@ export async function parseEPUB(epubBlob: Blob): Promise<EbookCIF> {
|
|||||||
const packageDoc = parseXML(opfXml);
|
const packageDoc = parseXML(opfXml);
|
||||||
|
|
||||||
const metadata = extractMetadata(packageDoc);
|
const metadata = extractMetadata(packageDoc);
|
||||||
const spine = parseSpine(packageDoc);
|
const spine = parseSpine(packageDoc, opfPath);
|
||||||
const toc = await parseTOC(zip, packageDoc, opfPath);
|
const toc = await parseTOC(zip, packageDoc, opfPath);
|
||||||
const resources = await loadResources(zip);
|
const resources = await loadResources(zip);
|
||||||
const coverImage = await extractCover(zip, packageDoc);
|
const coverImage = await extractCover(zip, packageDoc);
|
||||||
@@ -32,7 +32,10 @@ export async function parseEPUB(epubBlob: Blob): Promise<EbookCIF> {
|
|||||||
const totalCharacters = await calculateTotalCharacters(spine, resources);
|
const totalCharacters = await calculateTotalCharacters(spine, resources);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metadata,
|
metadata: {
|
||||||
|
...metadata,
|
||||||
|
coverImage,
|
||||||
|
},
|
||||||
toc,
|
toc,
|
||||||
spine,
|
spine,
|
||||||
resources,
|
resources,
|
||||||
@@ -82,7 +85,10 @@ function extractMetadata(packageDoc: XMLDocument): EbookCIF["metadata"] {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSpine(packageDoc: XMLDocument): EbookCIF["spine"] {
|
function parseSpine(
|
||||||
|
packageDoc: XMLDocument,
|
||||||
|
opfPath: string,
|
||||||
|
): EbookCIF["spine"] {
|
||||||
const spine = packageDoc.querySelector("spine");
|
const spine = packageDoc.querySelector("spine");
|
||||||
const manifest = packageDoc.querySelector("manifest");
|
const manifest = packageDoc.querySelector("manifest");
|
||||||
if (!spine || !manifest) {
|
if (!spine || !manifest) {
|
||||||
@@ -100,17 +106,18 @@ function parseSpine(packageDoc: XMLDocument): EbookCIF["spine"] {
|
|||||||
const href = manifestItem.getAttribute("href");
|
const href = manifestItem.getAttribute("href");
|
||||||
console.log("Href:", href); // Your debug log
|
console.log("Href:", href); // Your debug log
|
||||||
if (!href) return;
|
if (!href) return;
|
||||||
|
const resolvedPath = resolvePath(opfPath, href);
|
||||||
result.push({
|
result.push({
|
||||||
id: idref,
|
id: idref,
|
||||||
type: "html" as const,
|
type: "html" as const,
|
||||||
content: href || "",
|
content: resolvedPath,
|
||||||
properties: (itemref.getAttribute("properties") || "")
|
properties: (itemref.getAttribute("properties") || "")
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
index: result.length,
|
index: result.length,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return result; // FIXED - proper return, not trailing comma
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseTOC(
|
async function parseTOC(
|
||||||
|
|||||||
Reference in New Issue
Block a user