Files
bookhoard/web/src/reader/parsers/txt-parser.ts
T
john-okeefe bf455dbf97 fix: Rename duplicate extractTXTMetadata in txt-parser
Fix infinite recursion bug where exported function called itself.
Renamed to getTXTMetadata to avoid duplicate declaration.
2026-04-04 14:21:35 -04:00

122 lines
2.9 KiB
TypeScript

// 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 getTXTMetadata(
txtBlob: Blob,
): Promise<Partial<EbookCIF["metadata"]>> {
return extractTXTMetadata(txtBlob);
}