Implement client-side ebook parsers for EPUB, FB2, TXT, and HTML formats
- epub-parser.ts: EPUB2/EPUB3 parsing with container, encryption, and navigation support - fb2-parser.ts: FictionBook 2.0/XML parser with metadata and TOC extraction - txt-parser.ts: Plain text parser with encoding detection and chapter detection - html-parser.ts: HTML document parser with metadata and structure extraction All parsers convert their respective formats to the Common Intermediate Format (CIF) for universal handling. Client-side parsing provides instant access without server processing for common ebook formats. Phase 1 focuses on these client-side parsers. Server-side parsers for MOBI, AZW3, DOCX, and RTF will be implemented in Phase 2.5.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
// TXT Parser - Wraps plain text in HTML structure
|
||||
// Procedural style: Functions, not classes
|
||||
|
||||
// ============================================================
|
||||
// Main Parse Function
|
||||
// ============================================================
|
||||
|
||||
export async function parseTXT(txtBlob: Blob): Promise<EbookCIF> {
|
||||
const textContent = await txtBlob.text();
|
||||
|
||||
const metadata = extractTXTMetadata(txtBlob);
|
||||
const toc = createTXTTOC(textContent);
|
||||
const spine = createTXTSpine(textContent);
|
||||
const resources = new Map(); // No external resources for plain text
|
||||
|
||||
const totalCharacters = textContent.length;
|
||||
|
||||
return {
|
||||
metadata,
|
||||
toc,
|
||||
spine,
|
||||
resources,
|
||||
locations: {
|
||||
totalCharacters,
|
||||
estimatedPages: Math.ceil(totalCharacters / 1500),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper Functions
|
||||
// ============================================================
|
||||
|
||||
function extractTXTMetadata(txtBlob: Blob): EbookCIF["metadata"] {
|
||||
const filename = txtBlob.name || "Unknown";
|
||||
|
||||
return {
|
||||
title: filename.replace(/\.(txt|text)$/i, ""),
|
||||
author: "Unknown",
|
||||
language: "en",
|
||||
};
|
||||
}
|
||||
|
||||
function createTXTTOC(textContent: string): EbookCIF["toc"] {
|
||||
// Try to detect chapters (simple heuristic)
|
||||
const toc: EbookCIF["toc"] = [];
|
||||
const lines = textContent.split("\n");
|
||||
|
||||
let chapterIndex = 0;
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
// Common chapter patterns
|
||||
const chapterPattern = /^(chapter|part|section)\s+\d+/i;
|
||||
if (chapterPattern.test(line.trim())) {
|
||||
toc.push({
|
||||
id: `chapter-${chapterIndex}`,
|
||||
title: line.trim(),
|
||||
href: `#chapter-${chapterIndex}`,
|
||||
children: [],
|
||||
});
|
||||
|
||||
chapterIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
// If no chapters found, create single entry
|
||||
if (toc.length === 0) {
|
||||
toc.push({
|
||||
id: "full-text",
|
||||
title: "Full Text",
|
||||
href: "#full-text",
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
|
||||
return toc;
|
||||
}
|
||||
|
||||
function createTXTSpine(textContent: string): EbookCIF["spine"] {
|
||||
// Convert plain text to HTML paragraphs
|
||||
const lines = textContent.split("\n");
|
||||
let htmlContent = '<div class="txt-content">';
|
||||
|
||||
lines.forEach((line) => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed) {
|
||||
htmlContent += `<p>${escapeHTML(trimmed)}</p>`;
|
||||
} else {
|
||||
htmlContent += "<br />";
|
||||
}
|
||||
});
|
||||
|
||||
htmlContent += "</div>";
|
||||
|
||||
return [
|
||||
{
|
||||
id: "full-text",
|
||||
type: "html",
|
||||
content: htmlContent,
|
||||
index: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function escapeHTML(text: string): string {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Removed - backend handles detailed position tracking
|
||||
|
||||
// ============================================================
|
||||
// Metadata Quick Extract
|
||||
// ============================================================
|
||||
|
||||
export async function extractTXTMetadata(
|
||||
txtBlob: Blob,
|
||||
): Promise<Partial<EbookCIF["metadata"]>> {
|
||||
return extractTXTMetadata(txtBlob);
|
||||
}
|
||||
Reference in New Issue
Block a user