mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
fix(import): isolate bad chapters and bound nesting depth
A deeply nested document could overflow the stack and abort the entire import. Bound block recursion depth and wrap each chapter build in a try/catch so one unreadable chapter is skipped instead of failing the whole import. Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
This commit is contained in:
1 parent
b94b51140a
commit
457209f934
1 file changed
+14
-9
+14
-9
@@ -184,12 +184,12 @@ function hasBlockChild(el: Element): boolean {
|
||||
return Array.from(el.children).some((c) => BLOCK_TAGS.has(c.localName.toLowerCase()));
|
||||
}
|
||||
|
||||
function listItems(el: Element): JSONContent[] {
|
||||
function listItems(el: Element, depth: number): JSONContent[] {
|
||||
const items: JSONContent[] = [];
|
||||
Array.from(el.children)
|
||||
.filter((c) => c.localName.toLowerCase() === "li")
|
||||
.forEach((li) => {
|
||||
const content = blocksFrom(li);
|
||||
const content = blocksFrom(li, depth + 1);
|
||||
items.push({ type: "listItem", content: content.length ? content : [{ type: "paragraph" }] });
|
||||
});
|
||||
return items;
|
||||
@@ -212,7 +212,7 @@ function tableBlocks(el: Element): JSONContent[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
function blockFromElement(el: Element): JSONContent[] {
|
||||
function blockFromElement(el: Element, depth: number): JSONContent[] {
|
||||
const tag = el.localName.toLowerCase();
|
||||
switch (tag) {
|
||||
case "p":
|
||||
@@ -228,15 +228,15 @@ function blockFromElement(el: Element): JSONContent[] {
|
||||
return meaningful(content) ? [{ type: "heading", attrs: { level }, content }] : [];
|
||||
}
|
||||
case "blockquote": {
|
||||
const inner = blocksFrom(el);
|
||||
const inner = blocksFrom(el, depth + 1);
|
||||
return [{ type: "blockquote", content: inner.length ? inner : [{ type: "paragraph" }] }];
|
||||
}
|
||||
case "ul": {
|
||||
const items = listItems(el);
|
||||
const items = listItems(el, depth);
|
||||
return items.length ? [{ type: "bulletList", content: items }] : [];
|
||||
}
|
||||
case "ol": {
|
||||
const items = listItems(el);
|
||||
const items = listItems(el, depth);
|
||||
return items.length ? [{ type: "orderedList", content: items }] : [];
|
||||
}
|
||||
case "hr":
|
||||
@@ -253,18 +253,19 @@ function blockFromElement(el: Element): JSONContent[] {
|
||||
case "style":
|
||||
return [];
|
||||
default:
|
||||
return hasBlockChild(el) ? blocksFrom(el) : paragraphAndFigures(el);
|
||||
return hasBlockChild(el) ? blocksFrom(el, depth + 1) : paragraphAndFigures(el);
|
||||
}
|
||||
}
|
||||
|
||||
function blocksFrom(parent: Node): JSONContent[] {
|
||||
function blocksFrom(parent: Node, depth = 0): JSONContent[] {
|
||||
if (depth > 64) return [];
|
||||
const out: JSONContent[] = [];
|
||||
parent.childNodes.forEach((child) => {
|
||||
if (child.nodeType === 3) {
|
||||
const text = (child.nodeValue ?? "").replace(/\s+/g, " ").trim();
|
||||
if (text !== "") out.push({ type: "paragraph", content: [{ type: "text", text }] });
|
||||
} else if (child.nodeType === 1) {
|
||||
out.push(...blockFromElement(child as Element));
|
||||
out.push(...blockFromElement(child as Element, depth));
|
||||
}
|
||||
});
|
||||
return out;
|
||||
@@ -491,9 +492,13 @@ export function filesToBook(files: RawFile[], fallbackName = "Imported book"): B
|
||||
spine.forEach((item, i) => {
|
||||
const file = byPath.get(normalize(item.href));
|
||||
if (!file) return;
|
||||
try {
|
||||
chapters.push(
|
||||
buildChapter(file, dirOf(item.href), navTitles.get(item.href), i, byPath, manifest),
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (!chapters.length) throw new Error("Not a valid EPUB: no readable chapters found.");
|
||||
|
||||
|
||||
Reference in new issue
Block a user