@abfcode/spine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1397 @@
1
+ import { unzipSync, strFromU8 } from 'fflate';
2
+
3
+ // src/container.ts
4
+
5
+ // src/util.ts
6
+ function posixClean(p) {
7
+ if (p === "") return ".";
8
+ const rooted = p[0] === "/";
9
+ const out = [];
10
+ for (const seg of p.split("/")) {
11
+ if (seg === "" || seg === ".") continue;
12
+ if (seg === "..") {
13
+ if (out.length && out[out.length - 1] !== "..") out.pop();
14
+ else if (!rooted) out.push("..");
15
+ } else {
16
+ out.push(seg);
17
+ }
18
+ }
19
+ const res = (rooted ? "/" : "") + out.join("/");
20
+ return res === "" ? "." : res;
21
+ }
22
+ function posixJoin(...parts) {
23
+ const joined = parts.filter((p) => p !== "").join("/");
24
+ return joined === "" ? "" : posixClean(joined);
25
+ }
26
+ function posixDir(p) {
27
+ const i = p.lastIndexOf("/");
28
+ const dir = i < 0 ? "" : p.slice(0, i);
29
+ return posixClean(dir === "" ? i < 0 ? "." : "/" : dir);
30
+ }
31
+ function posixExt(p) {
32
+ for (let i = p.length - 1; i >= 0 && p[i] !== "/"; i--) {
33
+ if (p[i] === ".") return p.slice(i);
34
+ }
35
+ return "";
36
+ }
37
+ function resolvePath(base, href) {
38
+ if (href === "") return "";
39
+ if (href.includes("://") || href.startsWith("data:") || href.startsWith("mailto:")) return href;
40
+ const hashIdx = href.indexOf("#");
41
+ let ref = hashIdx === -1 ? href : href.slice(0, hashIdx);
42
+ const frag = hashIdx === -1 ? "" : href.slice(hashIdx);
43
+ ref = ref.replace(/^\//, "");
44
+ if (base === "." || base === "/") base = "";
45
+ if (base === "") return ref === "" ? frag : posixClean(ref) + frag;
46
+ if (ref === "") return posixClean(base) + frag;
47
+ return posixClean(posixJoin(base, ref)) + frag;
48
+ }
49
+ function splitHrefAnchor(href) {
50
+ const i = href.indexOf("#");
51
+ return i === -1 ? [href, ""] : [href.slice(0, i), href.slice(i + 1)];
52
+ }
53
+ function anchorKey(href, id) {
54
+ return href === "" ? "#" + id : href + "#" + id;
55
+ }
56
+ function stableId(...parts) {
57
+ let h = 2166136261;
58
+ for (const p of parts) {
59
+ for (let i = 0; i < p.length; i++) {
60
+ h ^= p.charCodeAt(i);
61
+ h = Math.imul(h, 16777619);
62
+ }
63
+ h ^= 0;
64
+ h = Math.imul(h, 16777619);
65
+ }
66
+ return "id_" + (h >>> 0).toString(16).padStart(8, "0");
67
+ }
68
+ var EXT_TYPES = {
69
+ ".jpg": "image/jpeg",
70
+ ".jpeg": "image/jpeg",
71
+ ".png": "image/png",
72
+ ".gif": "image/gif",
73
+ ".svg": "image/svg+xml",
74
+ ".webp": "image/webp",
75
+ ".bmp": "image/bmp",
76
+ ".xhtml": "application/xhtml+xml",
77
+ ".html": "text/html",
78
+ ".htm": "text/html",
79
+ ".css": "text/css",
80
+ ".ncx": "application/x-dtbncx+xml",
81
+ ".opf": "application/oebps-package+xml"
82
+ };
83
+ function contentTypeFromHref(href) {
84
+ return EXT_TYPES[posixExt(href).toLowerCase()] ?? "";
85
+ }
86
+
87
+ // src/dom.ts
88
+ function getDOMParser() {
89
+ const P = globalThis.DOMParser;
90
+ if (!P) throw new Error("DOMParser is not available in this environment");
91
+ return P;
92
+ }
93
+ function parseXml(source) {
94
+ return new (getDOMParser())().parseFromString(source, "application/xml");
95
+ }
96
+ function parseHtml(source) {
97
+ return new (getDOMParser())().parseFromString(source, "text/html");
98
+ }
99
+ var ErrMissingContainer = new Error("missing container");
100
+ var ErrNoRootfile = new Error("no rootfile found");
101
+ var ErrNoSpine = new Error("no spine content found");
102
+
103
+ // src/container.ts
104
+ var Archive = class {
105
+ byClean = /* @__PURE__ */ new Map();
106
+ names = [];
107
+ constructor(bytes) {
108
+ const raw = unzipSync(bytes);
109
+ for (const name of Object.keys(raw)) {
110
+ if (name.endsWith("/")) continue;
111
+ this.names.push(name);
112
+ this.byClean.set(this.key(name), raw[name]);
113
+ }
114
+ }
115
+ key(p) {
116
+ return posixClean(p.replace(/^\//, ""));
117
+ }
118
+ has(p) {
119
+ return this.byClean.has(this.key(p));
120
+ }
121
+ bytes(p) {
122
+ return this.byClean.get(this.key(p));
123
+ }
124
+ text(p) {
125
+ const b = this.bytes(p);
126
+ return b ? strFromU8(b) : void 0;
127
+ }
128
+ };
129
+ var CONTAINER_PATH = "META-INF/container.xml";
130
+ function parseContainer(archive) {
131
+ const xml = archive.text(CONTAINER_PATH);
132
+ if (xml === void 0) throw ErrMissingContainer;
133
+ const doc = parseXml(xml);
134
+ const rootfiles = [...doc.getElementsByTagName("rootfile")];
135
+ const paths = [];
136
+ for (const rf of rootfiles) {
137
+ const full = rf.getAttribute("full-path");
138
+ if (!full) continue;
139
+ paths.push(posixClean(full.replace(/^\//, "")));
140
+ }
141
+ if (paths.length === 0) throw ErrNoRootfile;
142
+ return paths;
143
+ }
144
+ function findOpfs(archive) {
145
+ return archive.names.filter((n) => n.toLowerCase().endsWith(".opf"));
146
+ }
147
+
148
+ // src/opf.ts
149
+ function emptyMetadata() {
150
+ return {
151
+ title: "",
152
+ titles: [],
153
+ authors: [],
154
+ creators: [],
155
+ contributors: [],
156
+ language: "",
157
+ languages: [],
158
+ identifiers: [],
159
+ publisher: "",
160
+ publishers: [],
161
+ pubDate: "",
162
+ dates: [],
163
+ series: "",
164
+ seriesIndex: "",
165
+ subjects: [],
166
+ rights: [],
167
+ descriptions: [],
168
+ modified: "",
169
+ collections: [],
170
+ coverHref: ""
171
+ };
172
+ }
173
+ function attr(el, local) {
174
+ for (const a of Array.from(el.attributes)) {
175
+ if (a.localName === local) return a.value;
176
+ }
177
+ return "";
178
+ }
179
+ function splitProps(s) {
180
+ const fields = s.split(/\s+/).filter(Boolean);
181
+ return fields.length ? fields : [];
182
+ }
183
+ function atoiSafe(v) {
184
+ let n = 0;
185
+ for (const ch of v.trim()) {
186
+ if (ch < "0" || ch > "9") return n;
187
+ n = n * 10 + (ch.charCodeAt(0) - 48);
188
+ }
189
+ return n;
190
+ }
191
+ function childByLocal(parent, local) {
192
+ for (const c of Array.from(parent.children)) if (c.localName === local) return c;
193
+ return void 0;
194
+ }
195
+ function mkMeta(id, value, lang = "", fileAs = "", role = "") {
196
+ return { id, value, language: lang, scheme: "", fileAs, role, displaySeq: 0, refinements: {} };
197
+ }
198
+ function parseOpf(xmlText) {
199
+ const doc = parseXml(xmlText);
200
+ const pkg = doc.documentElement;
201
+ const meta = emptyMetadata();
202
+ const manifest = /* @__PURE__ */ new Map();
203
+ const spine = [];
204
+ const data = { metadata: meta, manifest, spine, spineToc: "", coverId: "", version: "" };
205
+ data.version = (attr(pkg, "version") || "").trim();
206
+ const refinements = /* @__PURE__ */ new Map();
207
+ const metaTargets = /* @__PURE__ */ new Map();
208
+ const identifierTargets = /* @__PURE__ */ new Map();
209
+ const collectionTargets = /* @__PURE__ */ new Map();
210
+ const track = (id, mv) => {
211
+ if (!id) return;
212
+ const list = metaTargets.get(id) ?? [];
213
+ list.push(mv);
214
+ metaTargets.set(id, list);
215
+ };
216
+ const metadataEl = childByLocal(pkg, "metadata");
217
+ if (metadataEl) {
218
+ for (const el of Array.from(metadataEl.children)) {
219
+ const name = el.localName;
220
+ const id = attr(el, "id");
221
+ const lang = attr(el, "lang");
222
+ const fileAs = attr(el, "file-as");
223
+ const role = attr(el, "role");
224
+ const text = (el.textContent ?? "").trim();
225
+ switch (name) {
226
+ case "title":
227
+ if (text) {
228
+ const mv = mkMeta(id, text, lang, fileAs, role);
229
+ meta.titles.push(mv);
230
+ track(id, mv);
231
+ if (!meta.title) meta.title = text;
232
+ }
233
+ break;
234
+ case "creator":
235
+ if (text) {
236
+ const mv = mkMeta(id, text, lang, fileAs, role);
237
+ meta.creators.push(mv);
238
+ track(id, mv);
239
+ meta.authors.push(text);
240
+ }
241
+ break;
242
+ case "contributor":
243
+ if (text) {
244
+ const mv = mkMeta(id, text, lang, fileAs, role);
245
+ meta.contributors.push(mv);
246
+ track(id, mv);
247
+ }
248
+ break;
249
+ case "subject":
250
+ if (text) {
251
+ const mv = mkMeta(id, text, lang);
252
+ meta.subjects.push(mv);
253
+ track(id, mv);
254
+ }
255
+ break;
256
+ case "language":
257
+ if (text) {
258
+ if (!meta.language) meta.language = text;
259
+ meta.languages.push(text);
260
+ }
261
+ break;
262
+ case "identifier":
263
+ if (text) {
264
+ const ident = { id, scheme: attr(el, "scheme"), value: text, type: "" };
265
+ meta.identifiers.push(ident);
266
+ if (id) identifierTargets.set(id, ident);
267
+ }
268
+ break;
269
+ case "publisher":
270
+ if (text) {
271
+ const mv = mkMeta(id, text, lang);
272
+ meta.publishers.push(mv);
273
+ track(id, mv);
274
+ if (!meta.publisher) meta.publisher = text;
275
+ }
276
+ break;
277
+ case "date":
278
+ if (text) {
279
+ const mv = mkMeta(id, text, lang);
280
+ meta.dates.push(mv);
281
+ track(id, mv);
282
+ if (!meta.pubDate) meta.pubDate = text;
283
+ }
284
+ break;
285
+ case "description":
286
+ if (text) {
287
+ const mv = mkMeta(id, text, lang);
288
+ meta.descriptions.push(mv);
289
+ track(id, mv);
290
+ }
291
+ break;
292
+ case "rights":
293
+ if (text) {
294
+ const mv = mkMeta(id, text, lang);
295
+ meta.rights.push(mv);
296
+ track(id, mv);
297
+ }
298
+ break;
299
+ case "meta": {
300
+ const nameAttr = attr(el, "name");
301
+ const content = attr(el, "content");
302
+ const property = attr(el, "property");
303
+ const refines = attr(el, "refines").replace(/^#/, "");
304
+ const idAttr = attr(el, "id");
305
+ if (nameAttr.toLowerCase() === "cover" && content) data.coverId = content;
306
+ if (property.endsWith("belongs-to-collection")) {
307
+ if (text) {
308
+ const col = { id: idAttr, name: text, type: "", position: "" };
309
+ meta.collections.push(col);
310
+ if (idAttr) collectionTargets.set(idAttr, col);
311
+ if (!meta.series) meta.series = text;
312
+ }
313
+ break;
314
+ }
315
+ if (property.endsWith("modified")) {
316
+ if (text) meta.modified = text;
317
+ break;
318
+ }
319
+ if (property && refines) {
320
+ if (text) {
321
+ const r = refinements.get(refines) ?? {};
322
+ r[property] = text;
323
+ refinements.set(refines, r);
324
+ }
325
+ }
326
+ break;
327
+ }
328
+ }
329
+ }
330
+ }
331
+ const manifestEl = childByLocal(pkg, "manifest");
332
+ if (manifestEl) {
333
+ for (const el of Array.from(manifestEl.children)) {
334
+ if (el.localName !== "item") continue;
335
+ const item = {
336
+ id: attr(el, "id"),
337
+ href: attr(el, "href"),
338
+ mediaType: attr(el, "media-type"),
339
+ properties: splitProps(attr(el, "properties")),
340
+ path: ""
341
+ };
342
+ if (item.id) manifest.set(item.id, item);
343
+ }
344
+ }
345
+ const spineEl = childByLocal(pkg, "spine");
346
+ if (spineEl) {
347
+ data.spineToc = attr(spineEl, "toc");
348
+ for (const el of Array.from(spineEl.children)) {
349
+ if (el.localName !== "itemref") continue;
350
+ const item = {
351
+ idRef: attr(el, "idref"),
352
+ href: "",
353
+ linear: attr(el, "linear").toLowerCase() !== "no",
354
+ properties: splitProps(attr(el, "properties"))
355
+ };
356
+ if (item.idRef) spine.push(item);
357
+ }
358
+ }
359
+ applyRefinements(metaTargets, identifierTargets, collectionTargets, refinements, meta);
360
+ return data;
361
+ }
362
+ function applyRefinements(metaTargets, identifierTargets, collectionTargets, refinements, meta) {
363
+ for (const [id, props] of refinements) {
364
+ const targets = metaTargets.get(id);
365
+ if (targets) for (const mv of targets) applyMetaProps(mv, props);
366
+ const ident = identifierTargets.get(id);
367
+ if (ident && props["identifier-type"]) ident.type = props["identifier-type"];
368
+ const col = collectionTargets.get(id);
369
+ if (col) {
370
+ if (props["collection-type"]) col.type = props["collection-type"];
371
+ if (props["group-position"]) {
372
+ col.position = props["group-position"];
373
+ if (!meta.seriesIndex) meta.seriesIndex = props["group-position"];
374
+ }
375
+ }
376
+ }
377
+ for (const col of meta.collections) {
378
+ if (col.type === "series" && !meta.series) meta.series = col.name;
379
+ if (col.type === "series" && !meta.seriesIndex) meta.seriesIndex = col.position;
380
+ }
381
+ if (!meta.title && meta.titles.length) meta.title = meta.titles[0].value;
382
+ if (!meta.language && meta.languages.length) meta.language = meta.languages[0];
383
+ if (!meta.publisher && meta.publishers.length) meta.publisher = meta.publishers[0].value;
384
+ if (!meta.pubDate && meta.dates.length) meta.pubDate = meta.dates[0].value;
385
+ }
386
+ function applyMetaProps(mv, props) {
387
+ for (const [k, v] of Object.entries(props)) mv.refinements[k] = v;
388
+ if (props["file-as"]) mv.fileAs = props["file-as"];
389
+ if (props["role"]) mv.role = props["role"];
390
+ if (props["display-seq"]) mv.displaySeq = atoiSafe(props["display-seq"]);
391
+ if (props["title-type"] && !mv.scheme) mv.scheme = props["title-type"];
392
+ if (props["date-type"] && !mv.scheme) mv.scheme = props["date-type"];
393
+ }
394
+ function resolveCover(manifest, coverId) {
395
+ if (coverId) {
396
+ const item = manifest.get(coverId);
397
+ if (item) return item.path;
398
+ }
399
+ for (const item of manifest.values()) {
400
+ if (item.properties.includes("cover-image")) return item.path;
401
+ }
402
+ return "";
403
+ }
404
+
405
+ // src/toc.ts
406
+ function resolveTocPath(manifest, spineToc) {
407
+ if (spineToc) {
408
+ const item = manifest.get(spineToc);
409
+ if (item) return item.path;
410
+ }
411
+ for (const item of manifest.values()) {
412
+ if (item.properties.includes("nav")) return item.path;
413
+ }
414
+ for (const item of manifest.values()) {
415
+ if (item.mediaType.toLowerCase() === "application/x-dtbncx+xml" || item.path.toLowerCase().endsWith(".ncx")) {
416
+ return item.path;
417
+ }
418
+ }
419
+ return "";
420
+ }
421
+ function parseToc(archive, tocPath) {
422
+ const data = archive.text(tocPath);
423
+ if (data === void 0) throw new Error("toc not found in archive: " + tocPath);
424
+ const lower = data.toLowerCase();
425
+ if (lower.includes("<ncx") || tocPath.toLowerCase().endsWith(".ncx")) {
426
+ const items = parseNcx(data);
427
+ assignTocIds(items, []);
428
+ resolveTocHrefs(items, tocPath);
429
+ return { toc: items, landmarks: [], pageList: [] };
430
+ }
431
+ const nav = parseNav(data);
432
+ assignTocIds(nav.toc, []);
433
+ assignTocIds(nav.landmarks, []);
434
+ assignTocIds(nav.pageList, []);
435
+ resolveTocHrefs(nav.toc, tocPath);
436
+ resolveTocHrefs(nav.landmarks, tocPath);
437
+ resolveTocHrefs(nav.pageList, tocPath);
438
+ return nav;
439
+ }
440
+ function childByLocal2(parent, local) {
441
+ for (const c of Array.from(parent.children)) if (c.localName === local) return c;
442
+ return void 0;
443
+ }
444
+ function childrenByLocal(parent, local) {
445
+ return Array.from(parent.children).filter((c) => c.localName === local);
446
+ }
447
+ function parseNcx(xmlText) {
448
+ const doc = parseXml(xmlText);
449
+ let navMap;
450
+ for (const el of Array.from(doc.getElementsByTagName("*"))) {
451
+ if (el.localName === "navMap") {
452
+ navMap = el;
453
+ break;
454
+ }
455
+ }
456
+ if (!navMap) return [];
457
+ const convert = (points) => points.map((p) => {
458
+ const labelEl = childByLocal2(p, "navLabel");
459
+ const textEl = labelEl ? childByLocal2(labelEl, "text") : void 0;
460
+ const label = (textEl?.textContent ?? "").trim();
461
+ const contentEl = childByLocal2(p, "content");
462
+ const src = (contentEl?.getAttribute("src") ?? "").trim();
463
+ return {
464
+ id: (p.getAttribute("id") ?? "").trim(),
465
+ label,
466
+ href: src,
467
+ children: convert(childrenByLocal(p, "navPoint"))
468
+ };
469
+ });
470
+ return convert(childrenByLocal(navMap, "navPoint"));
471
+ }
472
+ function navTypeFromAttrs(attrs) {
473
+ const epubType = attrs["epub:type"];
474
+ if (epubType) {
475
+ for (const part of epubType.toLowerCase().split(/\s+/)) {
476
+ if (part === "toc") return "toc";
477
+ if (part === "landmarks") return "landmarks";
478
+ if (part === "page-list") return "pageList";
479
+ }
480
+ }
481
+ if (attrs["role"] && attrs["role"].toLowerCase() === "doc-toc") return "toc";
482
+ return "unknown";
483
+ }
484
+ function parseNav(htmlText) {
485
+ const doc = parseHtml(htmlText);
486
+ const toc = [];
487
+ const landmarks = [];
488
+ const pageList = [];
489
+ let stack = [];
490
+ let curItem = null;
491
+ let inNav = false;
492
+ let navType = "unknown";
493
+ let inLink = false;
494
+ let linkBuf = "";
495
+ const root = () => navType === "landmarks" ? landmarks : navType === "pageList" ? pageList : toc;
496
+ const onStart = (tag, attrs) => {
497
+ if (tag === "nav") {
498
+ navType = navTypeFromAttrs(attrs);
499
+ if (navType !== "unknown") {
500
+ inNav = true;
501
+ stack = [root()];
502
+ }
503
+ return;
504
+ }
505
+ if (!inNav) return;
506
+ switch (tag) {
507
+ case "ol":
508
+ case "ul":
509
+ if (curItem) stack.push(curItem.children);
510
+ else if (stack.length >= 1) stack.push(stack[stack.length - 1]);
511
+ break;
512
+ case "li": {
513
+ const list = stack[stack.length - 1];
514
+ const item = { id: "", label: "", href: "", children: [] };
515
+ list.push(item);
516
+ curItem = item;
517
+ break;
518
+ }
519
+ case "a":
520
+ if (curItem) {
521
+ curItem.href = attrs["href"] ?? "";
522
+ inLink = true;
523
+ linkBuf = "";
524
+ }
525
+ break;
526
+ }
527
+ };
528
+ const onEnd = (tag) => {
529
+ if (tag === "nav" && inNav) {
530
+ inNav = false;
531
+ curItem = null;
532
+ navType = "unknown";
533
+ stack = [];
534
+ return;
535
+ }
536
+ if (!inNav) return;
537
+ switch (tag) {
538
+ case "ol":
539
+ case "ul":
540
+ if (stack.length > 1) stack.pop();
541
+ break;
542
+ case "li":
543
+ curItem = null;
544
+ break;
545
+ case "a":
546
+ if (inLink && curItem) {
547
+ curItem.label = linkBuf.trim();
548
+ inLink = false;
549
+ linkBuf = "";
550
+ }
551
+ break;
552
+ }
553
+ };
554
+ const onText = (t) => {
555
+ if (inLink) linkBuf += t;
556
+ };
557
+ const walk = (node) => {
558
+ for (const child of Array.from(node.childNodes)) {
559
+ if (child.nodeType === 3) {
560
+ onText(child.data);
561
+ } else if (child.nodeType === 1) {
562
+ const el = child;
563
+ const tag = el.tagName.toLowerCase();
564
+ const attrs = {};
565
+ for (const a of Array.from(el.attributes)) attrs[a.name.toLowerCase()] = a.value;
566
+ onStart(tag, attrs);
567
+ walk(el);
568
+ onEnd(tag);
569
+ }
570
+ }
571
+ };
572
+ walk(doc.documentElement);
573
+ return { toc, landmarks, pageList };
574
+ }
575
+ function pathToStrings(path) {
576
+ return path.map((v) => String.fromCharCode(97 + v % 26));
577
+ }
578
+ function assignTocIds(items, path) {
579
+ items.forEach((item, i) => {
580
+ const idxPath = [...path, i];
581
+ if (!item.id) item.id = stableId(...pathToStrings(idxPath), item.href, item.label);
582
+ if (item.children.length > 0) assignTocIds(item.children, idxPath);
583
+ });
584
+ }
585
+ function resolveTocHrefs(items, tocPath) {
586
+ const base = posixDir(tocPath);
587
+ for (const item of items) {
588
+ if (item.href) {
589
+ item.href = item.href.startsWith("#") ? tocPath + item.href : resolvePath(base, item.href);
590
+ }
591
+ if (item.children.length > 0) resolveTocHrefs(item.children, tocPath);
592
+ }
593
+ }
594
+ function flattenToc(items) {
595
+ const out = [];
596
+ const walk = (list, depth, parent) => {
597
+ for (const item of list) {
598
+ const idx = out.length;
599
+ out.push({
600
+ id: item.id,
601
+ label: item.label,
602
+ href: item.href,
603
+ depth,
604
+ parent,
605
+ target: item.target
606
+ });
607
+ if (item.children.length > 0) walk(item.children, depth + 1, idx);
608
+ }
609
+ };
610
+ walk(items, 0, -1);
611
+ return out;
612
+ }
613
+
614
+ // src/chunk.ts
615
+ var DEFAULT_TEXT_OPTIONS = {
616
+ preserveLineBreaks: true,
617
+ includeHeadings: true,
618
+ headingMarkers: false,
619
+ includeListMarkers: true,
620
+ includeHorizontalRules: true
621
+ };
622
+ var encoder = new TextEncoder();
623
+ var byteLen = (s) => encoder.encode(s).length;
624
+ function inlineToText(inline, opts) {
625
+ if (inline.kind === "image") return inline.alt ? inline.alt : "[image]";
626
+ if (inline.text === "\n" && !opts.preserveLineBreaks) return " ";
627
+ return inline.text ?? "";
628
+ }
629
+ function inlinesToText(inlines, opts) {
630
+ let s = "";
631
+ for (const inline of inlines) {
632
+ s += inlineToText(inline, opts);
633
+ if (!s.endsWith(" ")) s += " ";
634
+ }
635
+ return s.trim();
636
+ }
637
+ function tableToText(table, opts) {
638
+ const rows = table.rows.map((row) => row.cells.map((c) => inlinesToText(c.inlines, opts)).join(" "));
639
+ return rows.join("\n").trim();
640
+ }
641
+ function figureToText(fig, opts) {
642
+ const caption = inlinesToText(fig.caption, opts);
643
+ if (caption) return caption;
644
+ return fig.images.map((img) => inlineToText(img, opts)).join(" ").trim();
645
+ }
646
+ function blockToText(block, opts = DEFAULT_TEXT_OPTIONS) {
647
+ if (block.table) return tableToText(block.table, opts);
648
+ if (block.figure) return figureToText(block.figure, opts);
649
+ if (block.kind === "heading" && !opts.includeHeadings) return "";
650
+ const inlines = block.inlines ?? [];
651
+ if (block.kind === "pre" || block.kind === "table") {
652
+ let s2 = "";
653
+ for (const inline of inlines) s2 += inline.kind === "image" ? inlineToText(inline, opts) : inline.text ?? "";
654
+ return s2.replace(/[ \n\t]+$/, "");
655
+ }
656
+ let prefix = "";
657
+ if (block.kind === "list_item" && opts.includeListMarkers) {
658
+ prefix = block.ordered && (block.listIndex ?? 0) > 0 ? `${block.listIndex}. ` : "- ";
659
+ }
660
+ if (block.kind === "hr") return opts.includeHorizontalRules ? "---" : "";
661
+ if (block.kind === "heading" && opts.headingMarkers) {
662
+ let level = block.level ?? 1;
663
+ level = level <= 0 ? 1 : level > 6 ? 6 : level;
664
+ prefix = "#".repeat(level) + " ";
665
+ }
666
+ let s = prefix;
667
+ for (const inline of inlines) {
668
+ s += inlineToText(inline, opts);
669
+ if (!s.endsWith(" ") && !s.endsWith("\n")) s += " ";
670
+ }
671
+ return s.trim();
672
+ }
673
+ function chunkBlocks(blocks, href, spineIndex, chunkStart, opts, textOpts, anchorMap, anchorByID, emit) {
674
+ let current = null;
675
+ let chunkIndex = chunkStart;
676
+ let textLen = 0;
677
+ let offset = 0;
678
+ const flush = () => {
679
+ if (!current) return;
680
+ current.text = current.text.trim();
681
+ emit(current);
682
+ offset += textLen;
683
+ current = null;
684
+ textLen = 0;
685
+ };
686
+ const startChunk = (blockIndex) => {
687
+ current = {
688
+ id: stableId(href, String(spineIndex), String(chunkIndex)),
689
+ text: "",
690
+ href,
691
+ spineIndex,
692
+ blockIndexFrom: blockIndex,
693
+ blockIndexTo: blockIndex,
694
+ startOffset: offset,
695
+ endOffset: offset,
696
+ anchors: [],
697
+ blocks: []
698
+ };
699
+ chunkIndex++;
700
+ textLen = 0;
701
+ };
702
+ const appendBlock = (block, blockIndex, blockText) => {
703
+ if (!current) startChunk(blockIndex);
704
+ const c = current;
705
+ if (blockText !== "" && c.text !== "") {
706
+ c.text += "\n\n";
707
+ textLen += 2;
708
+ }
709
+ if (blockText !== "") {
710
+ c.text += blockText;
711
+ textLen += byteLen(blockText);
712
+ }
713
+ c.blockIndexTo = blockIndex;
714
+ c.endOffset = offset + textLen;
715
+ c.blocks.push(block);
716
+ if (block.anchors && block.anchors.length > 0) {
717
+ c.anchors.push(...block.anchors);
718
+ for (const id of block.anchors) {
719
+ const ref = {
720
+ spineIndex,
721
+ blockIndex,
722
+ chunkId: c.id,
723
+ offset: offset + textLen - byteLen(blockText),
724
+ href: href + "#" + id
725
+ };
726
+ const key = anchorKey(href, id);
727
+ if (!anchorMap.has(key)) anchorMap.set(key, ref);
728
+ if (!anchorByID.has(id)) anchorByID.set(id, ref);
729
+ }
730
+ }
731
+ };
732
+ blocks.forEach((block, i) => {
733
+ const text = blockToText(block, textOpts);
734
+ const blockLen = byteLen(text);
735
+ if (opts.mode === "size") {
736
+ if (textLen > 0 && textLen + blockLen > opts.maxChars) flush();
737
+ appendBlock(block, i, text);
738
+ } else if (opts.mode === "paragraph") {
739
+ appendBlock(block, i, text);
740
+ if (blockLen > 0) flush();
741
+ } else {
742
+ appendBlock(block, i, text);
743
+ }
744
+ });
745
+ flush();
746
+ return chunkIndex;
747
+ }
748
+ function splitFragment(href) {
749
+ const i = href.indexOf("#");
750
+ return i === -1 ? [href, ""] : [href.slice(0, i), href.slice(i + 1)];
751
+ }
752
+ function resolveTocAnchors(items, anchors, anchorsByID) {
753
+ for (const item of items) {
754
+ if (item.href) {
755
+ const [base, frag] = splitFragment(item.href);
756
+ if (frag) {
757
+ if (base.includes("://") || base.startsWith("data:")) ; else {
758
+ const byKey = anchors.get(anchorKey(base, frag));
759
+ const byId = anchorsByID.get(frag);
760
+ if (byKey) {
761
+ item.target = { ...byKey, href: base + "#" + frag };
762
+ } else if (byId) {
763
+ item.target = { ...byId };
764
+ }
765
+ }
766
+ }
767
+ }
768
+ if (item.children.length > 0) resolveTocAnchors(item.children, anchors, anchorsByID);
769
+ }
770
+ }
771
+
772
+ // src/xhtml.ts
773
+ function collapseWhitespace(s) {
774
+ return s.replace(/[\t\n\r\f ]+/g, " ").trim();
775
+ }
776
+ function atoiSafe2(v) {
777
+ let n = 0;
778
+ for (const ch of v.trim()) {
779
+ if (ch < "0" || ch > "9") return n;
780
+ n = n * 10 + (ch.charCodeAt(0) - 48);
781
+ }
782
+ return n;
783
+ }
784
+ function parseXhtmlBlocks(doc) {
785
+ const blocks = [];
786
+ let cur = null;
787
+ let pendingAnchors = [];
788
+ const listStack = [];
789
+ let listDepth = 0;
790
+ let emphDepth = 0;
791
+ let strongDepth = 0;
792
+ const linkStack = [];
793
+ let skipDepth = 0;
794
+ let blockQuoteDepth = 0;
795
+ let preDepth = 0;
796
+ let codeDepth = 0;
797
+ let tableDepth = 0;
798
+ let currentTable = null;
799
+ let currentRow = null;
800
+ let currentCell = null;
801
+ let currentFigure = null;
802
+ let inFigcaption = false;
803
+ const flush = () => {
804
+ if (!cur) return;
805
+ const empty = (!cur.inlines || cur.inlines.length === 0) && (!cur.anchors || cur.anchors.length === 0) && !cur.table && !cur.figure;
806
+ if (empty) {
807
+ cur = null;
808
+ return;
809
+ }
810
+ blocks.push(cur);
811
+ cur = null;
812
+ };
813
+ const ensureBlock = (kind, level) => {
814
+ if (!cur || cur.kind !== kind || kind === "heading" && (cur.level ?? 0) !== level) {
815
+ flush();
816
+ cur = { kind, inlines: [], anchors: [] };
817
+ if (kind === "heading") cur.level = level;
818
+ if (pendingAnchors.length > 0) {
819
+ cur.anchors.push(...pendingAnchors);
820
+ pendingAnchors = [];
821
+ }
822
+ }
823
+ };
824
+ const addAnchor = (id) => {
825
+ if (!id) return;
826
+ if (cur) cur.anchors.push(id);
827
+ else pendingAnchors.push(id);
828
+ };
829
+ const addInline = (inline) => {
830
+ if (currentCell) {
831
+ currentCell.inlines.push(inline);
832
+ return;
833
+ }
834
+ if (currentFigure) {
835
+ if (inline.kind === "image" && !inFigcaption) currentFigure.images.push(inline);
836
+ else currentFigure.caption.push(inline);
837
+ return;
838
+ }
839
+ if (cur) cur.inlines.push(inline);
840
+ };
841
+ const addInlineText = (text, preserve) => {
842
+ const clean = preserve ? text : collapseWhitespace(text);
843
+ if (clean.trim() === "") return;
844
+ let kind = "text";
845
+ if (emphDepth > 0) kind = "emphasis";
846
+ if (strongDepth > 0) kind = "strong";
847
+ if (codeDepth > 0) kind = "code";
848
+ const inline = { kind, text: clean, emph: emphDepth > 0, strong: strongDepth > 0 };
849
+ if (linkStack.length > 0) {
850
+ inline.kind = "link";
851
+ inline.href = linkStack[linkStack.length - 1];
852
+ }
853
+ addInline(inline);
854
+ };
855
+ const onStart = (tag, attrs) => {
856
+ if (tag === "script" || tag === "style" || tag === "head" || tag === "noscript") {
857
+ skipDepth++;
858
+ return;
859
+ }
860
+ if (skipDepth > 0) return;
861
+ if (attrs["id"]) addAnchor(attrs["id"]);
862
+ if (attrs["name"]) addAnchor(attrs["name"]);
863
+ switch (tag) {
864
+ case "p":
865
+ if (blockQuoteDepth > 0) ensureBlock("blockquote", blockQuoteDepth);
866
+ else ensureBlock("paragraph", 0);
867
+ break;
868
+ case "h1":
869
+ case "h2":
870
+ case "h3":
871
+ case "h4":
872
+ case "h5":
873
+ case "h6":
874
+ ensureBlock("heading", Number(tag[1]));
875
+ break;
876
+ case "li":
877
+ if (listStack.length > 0) listStack[listStack.length - 1].index++;
878
+ ensureBlock("list_item", listDepth);
879
+ if (cur && listStack.length > 0) {
880
+ cur.ordered = listStack[listStack.length - 1].ordered;
881
+ cur.listIndex = listStack[listStack.length - 1].index;
882
+ }
883
+ break;
884
+ case "ul":
885
+ case "ol": {
886
+ const ordered = tag === "ol";
887
+ let start = 1;
888
+ if (ordered && attrs["start"]) {
889
+ start = atoiSafe2(attrs["start"]);
890
+ if (start <= 0) start = 1;
891
+ }
892
+ listStack.push({ ordered, index: start - 1 });
893
+ listDepth = listStack.length;
894
+ break;
895
+ }
896
+ case "br":
897
+ if (!cur) ensureBlock("paragraph", 0);
898
+ addInline({ kind: "text", text: "\n" });
899
+ break;
900
+ case "img":
901
+ if (!cur) ensureBlock("paragraph", 0);
902
+ addInline({ kind: "image", src: attrs["src"] ?? "", alt: attrs["alt"] ?? "" });
903
+ break;
904
+ case "a":
905
+ if (attrs["href"]) linkStack.push(attrs["href"]);
906
+ break;
907
+ case "em":
908
+ case "i":
909
+ emphDepth++;
910
+ break;
911
+ case "strong":
912
+ case "b":
913
+ strongDepth++;
914
+ break;
915
+ case "blockquote":
916
+ blockQuoteDepth++;
917
+ break;
918
+ case "pre":
919
+ preDepth++;
920
+ ensureBlock("pre", 0);
921
+ break;
922
+ case "code":
923
+ codeDepth++;
924
+ if (!cur) ensureBlock("paragraph", 0);
925
+ break;
926
+ case "hr":
927
+ flush();
928
+ blocks.push({ kind: "hr" });
929
+ break;
930
+ case "table":
931
+ tableDepth++;
932
+ ensureBlock("table", 0);
933
+ if (!cur.table) cur.table = { rows: [] };
934
+ currentTable = cur.table;
935
+ break;
936
+ case "tr":
937
+ if (tableDepth > 0 && currentTable) {
938
+ const row = { cells: [] };
939
+ currentTable.rows.push(row);
940
+ currentRow = row;
941
+ currentCell = null;
942
+ }
943
+ break;
944
+ case "td":
945
+ case "th":
946
+ if (tableDepth > 0 && currentTable) {
947
+ if (!currentRow) {
948
+ const row = { cells: [] };
949
+ currentTable.rows.push(row);
950
+ currentRow = row;
951
+ }
952
+ const cell = { inlines: [], header: tag === "th" };
953
+ currentRow.cells.push(cell);
954
+ currentCell = cell;
955
+ }
956
+ break;
957
+ case "figure":
958
+ ensureBlock("figure", 0);
959
+ if (!cur.figure) cur.figure = { images: [], caption: [] };
960
+ currentFigure = cur.figure;
961
+ break;
962
+ case "figcaption":
963
+ if (!cur || cur.kind !== "figure") {
964
+ ensureBlock("figure", 0);
965
+ if (!cur.figure) cur.figure = { images: [], caption: [] };
966
+ }
967
+ currentFigure = cur.figure;
968
+ inFigcaption = true;
969
+ break;
970
+ }
971
+ };
972
+ const onEnd = (tag) => {
973
+ if (tag === "script" || tag === "style" || tag === "head" || tag === "noscript") {
974
+ if (skipDepth > 0) skipDepth--;
975
+ return;
976
+ }
977
+ if (skipDepth > 0) return;
978
+ switch (tag) {
979
+ case "p":
980
+ case "li":
981
+ case "h1":
982
+ case "h2":
983
+ case "h3":
984
+ case "h4":
985
+ case "h5":
986
+ case "h6":
987
+ flush();
988
+ break;
989
+ case "ul":
990
+ case "ol":
991
+ if (listStack.length > 0) listStack.pop();
992
+ listDepth = listStack.length;
993
+ break;
994
+ case "a":
995
+ if (linkStack.length > 0) linkStack.pop();
996
+ break;
997
+ case "em":
998
+ case "i":
999
+ if (emphDepth > 0) emphDepth--;
1000
+ break;
1001
+ case "strong":
1002
+ case "b":
1003
+ if (strongDepth > 0) strongDepth--;
1004
+ break;
1005
+ case "blockquote":
1006
+ if (blockQuoteDepth > 0) blockQuoteDepth--;
1007
+ if (cur && cur.kind === "blockquote") flush();
1008
+ break;
1009
+ case "pre":
1010
+ if (preDepth > 0) preDepth--;
1011
+ if (cur && cur.kind === "pre") flush();
1012
+ break;
1013
+ case "code":
1014
+ if (codeDepth > 0) codeDepth--;
1015
+ break;
1016
+ case "table":
1017
+ if (tableDepth > 0) tableDepth--;
1018
+ if (cur && cur.kind === "table") flush();
1019
+ currentTable = null;
1020
+ currentRow = null;
1021
+ currentCell = null;
1022
+ break;
1023
+ case "td":
1024
+ case "th":
1025
+ currentCell = null;
1026
+ break;
1027
+ case "tr":
1028
+ currentRow = null;
1029
+ break;
1030
+ case "figure":
1031
+ if (cur && cur.kind === "figure") flush();
1032
+ currentFigure = null;
1033
+ inFigcaption = false;
1034
+ break;
1035
+ case "figcaption":
1036
+ inFigcaption = false;
1037
+ break;
1038
+ }
1039
+ };
1040
+ const onText = (raw) => {
1041
+ if (skipDepth > 0) return;
1042
+ if (preDepth === 0 && collapseWhitespace(raw) === "") return;
1043
+ if (!cur) {
1044
+ if (blockQuoteDepth > 0) ensureBlock("blockquote", blockQuoteDepth);
1045
+ else ensureBlock("paragraph", 0);
1046
+ }
1047
+ addInlineText(raw, preDepth > 0);
1048
+ };
1049
+ const walk = (node) => {
1050
+ for (const child of Array.from(node.childNodes)) {
1051
+ if (child.nodeType === 3) {
1052
+ onText(child.data);
1053
+ } else if (child.nodeType === 1) {
1054
+ const el = child;
1055
+ const tag = el.tagName.toLowerCase();
1056
+ const attrs = {};
1057
+ for (const a of Array.from(el.attributes)) attrs[a.name.toLowerCase()] = a.value;
1058
+ onStart(tag, attrs);
1059
+ walk(el);
1060
+ onEnd(tag);
1061
+ }
1062
+ }
1063
+ };
1064
+ walk(doc.documentElement);
1065
+ flush();
1066
+ return blocks;
1067
+ }
1068
+
1069
+ // src/fallback.ts
1070
+ function scoreOpf(data) {
1071
+ if (!data) return -1;
1072
+ let score = data.spine.length * 1e3;
1073
+ score += data.manifest.size;
1074
+ if (data.metadata.title) score += 10;
1075
+ return score;
1076
+ }
1077
+ function selectRootfile(archive, rootfiles) {
1078
+ let bestPath = "";
1079
+ let bestData = null;
1080
+ let bestScore = -1;
1081
+ for (const rf of rootfiles) {
1082
+ const text = archive.text(rf);
1083
+ const data = text === void 0 ? null : parseOpf(text);
1084
+ const score = scoreOpf(data);
1085
+ if (score > bestScore) {
1086
+ bestScore = score;
1087
+ bestPath = rf;
1088
+ bestData = data;
1089
+ }
1090
+ }
1091
+ return { path: bestPath, opf: bestData };
1092
+ }
1093
+ function baseName(p) {
1094
+ const i = p.lastIndexOf("/");
1095
+ return i === -1 ? p : p.slice(i + 1);
1096
+ }
1097
+ function isHtmlMedia(media) {
1098
+ const m = media.toLowerCase();
1099
+ return m.includes("xhtml") || m.includes("html");
1100
+ }
1101
+ function fallbackManifestFromArchive(archive) {
1102
+ const items = /* @__PURE__ */ new Map();
1103
+ const paths = archive.names.filter((n) => {
1104
+ const l = n.toLowerCase();
1105
+ return l.endsWith(".xhtml") || l.endsWith(".html") || l.endsWith(".htm");
1106
+ }).sort();
1107
+ for (const p of paths) {
1108
+ const clean = posixClean(p.replace(/^\//, ""));
1109
+ const id = "item_" + stableId(clean);
1110
+ items.set(id, {
1111
+ id,
1112
+ href: baseName(clean),
1113
+ mediaType: "application/xhtml+xml",
1114
+ properties: [],
1115
+ path: clean
1116
+ });
1117
+ }
1118
+ return items;
1119
+ }
1120
+ function fallbackSpineFromManifest(manifest) {
1121
+ const items = [...manifest.values()].filter((item) => isHtmlMedia(item.mediaType)).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1122
+ return items.map((item) => ({ idRef: item.id, href: item.path, linear: true, properties: [] }));
1123
+ }
1124
+ function scanXhtmlForFallbacks(doc) {
1125
+ const titleEl = doc.querySelector("title");
1126
+ const headingEl = doc.querySelector("h1,h2,h3,h4,h5,h6");
1127
+ const imgEl = doc.querySelector("img");
1128
+ return {
1129
+ title: collapseWhitespace(titleEl?.textContent ?? ""),
1130
+ heading: collapseWhitespace(headingEl?.textContent ?? ""),
1131
+ img: (imgEl?.getAttribute("src") ?? "").trim()
1132
+ };
1133
+ }
1134
+
1135
+ // src/book.ts
1136
+ var DEFAULT_FALLBACKS = {
1137
+ scanArchive: true,
1138
+ inferSpine: true,
1139
+ generateTOC: true,
1140
+ generateMetadata: true
1141
+ };
1142
+ var DEFAULT_CONFIG = {
1143
+ chunking: { mode: "paragraph", maxChars: 4e3 }};
1144
+ var Book = class {
1145
+ metadata = emptyMetadata();
1146
+ toc = [];
1147
+ landmarks = [];
1148
+ pageList = [];
1149
+ manifest = /* @__PURE__ */ new Map();
1150
+ spine = [];
1151
+ warnings = [];
1152
+ anchors = /* @__PURE__ */ new Map();
1153
+ rootfilePath = "";
1154
+ basePath = "";
1155
+ anchorsById = /* @__PURE__ */ new Map();
1156
+ archive;
1157
+ cfg;
1158
+ chunksCache = null;
1159
+ blockCache = /* @__PURE__ */ new Map();
1160
+ constructor(archive, cfg) {
1161
+ this.archive = archive;
1162
+ this.cfg = cfg;
1163
+ }
1164
+ warn(code, message, path = "") {
1165
+ this.warnings.push({ code, message, path });
1166
+ }
1167
+ cleanHref(href) {
1168
+ return splitHrefAnchor(href)[0];
1169
+ }
1170
+ openResource(href) {
1171
+ if (!href) return void 0;
1172
+ return this.archive.bytes(this.cleanHref(href));
1173
+ }
1174
+ documentFor(href) {
1175
+ const text = this.archive.text(this.cleanHref(href));
1176
+ return text === void 0 ? void 0 : parseHtml(text);
1177
+ }
1178
+ blocks(spineIndex) {
1179
+ if (spineIndex < 0 || spineIndex >= this.spine.length) return [];
1180
+ return this.blocksByHref(this.spine[spineIndex].href);
1181
+ }
1182
+ blocksByHref(href) {
1183
+ const clean = this.cleanHref(href);
1184
+ if (!clean) return [];
1185
+ const cached = this.blockCache.get(clean);
1186
+ if (cached) return cached;
1187
+ const text = this.archive.text(clean);
1188
+ if (text === void 0) {
1189
+ this.warn("content", "spine document not found", clean);
1190
+ return [];
1191
+ }
1192
+ const blocks = parseXhtmlBlocks(parseHtml(text));
1193
+ this.blockCache.set(clean, blocks);
1194
+ return blocks;
1195
+ }
1196
+ cover() {
1197
+ const href = this.metadata.coverHref;
1198
+ if (!href) return void 0;
1199
+ const bytes = this.openResource(href);
1200
+ if (!bytes) return void 0;
1201
+ const item = this.manifestItemByPath(href);
1202
+ const contentType = item?.mediaType || contentTypeFromHref(href);
1203
+ return { href, contentType, bytes };
1204
+ }
1205
+ manifestItemByPath(href) {
1206
+ for (const item of this.manifest.values()) {
1207
+ if (item.path === href || item.href === href) return item;
1208
+ }
1209
+ return void 0;
1210
+ }
1211
+ chunks(opts) {
1212
+ if (this.chunksCache) return this.chunksCache;
1213
+ const chunkOpts = opts ?? this.cfg.chunking;
1214
+ const out = [];
1215
+ const anchorMap = /* @__PURE__ */ new Map();
1216
+ const anchorById = /* @__PURE__ */ new Map();
1217
+ let chunkIndex = 0;
1218
+ for (let spineIndex = 0; spineIndex < this.spine.length; spineIndex++) {
1219
+ const item = this.spine[spineIndex];
1220
+ if (!item.href) {
1221
+ this.warn("spine", "missing href for spine item");
1222
+ continue;
1223
+ }
1224
+ const blocks = this.blocksByHref(item.href);
1225
+ chunkIndex = chunkBlocks(
1226
+ blocks,
1227
+ item.href,
1228
+ spineIndex,
1229
+ chunkIndex,
1230
+ chunkOpts,
1231
+ DEFAULT_TEXT_OPTIONS,
1232
+ anchorMap,
1233
+ anchorById,
1234
+ (c) => out.push(c)
1235
+ );
1236
+ }
1237
+ this.anchors = anchorMap;
1238
+ this.anchorsById = anchorById;
1239
+ resolveTocAnchors(this.toc, anchorMap, anchorById);
1240
+ resolveTocAnchors(this.landmarks, anchorMap, anchorById);
1241
+ resolveTocAnchors(this.pageList, anchorMap, anchorById);
1242
+ this.chunksCache = out;
1243
+ return out;
1244
+ }
1245
+ resolveAnchor(href) {
1246
+ if (href.includes("#")) {
1247
+ const direct = this.anchors.get(href);
1248
+ if (direct) return direct;
1249
+ const frag = href.split("#", 2)[1];
1250
+ if (frag !== void 0) return this.anchorsById.get(frag);
1251
+ return void 0;
1252
+ }
1253
+ return this.anchors.get(href) ?? this.anchorsById.get(href);
1254
+ }
1255
+ /** Port of deriveTOCFromHeadings — build a TOC from heading blocks. */
1256
+ deriveTocFromHeadings() {
1257
+ const toc = [];
1258
+ const stack = [{ level: 0, items: toc }];
1259
+ for (const item of this.spine) {
1260
+ if (!item.href) continue;
1261
+ const blocks = this.blocksByHref(item.href);
1262
+ for (const block of blocks) {
1263
+ if (block.kind !== "heading") continue;
1264
+ let label = blockToText(block, DEFAULT_TEXT_OPTIONS);
1265
+ if (!label) label = "Untitled";
1266
+ let level = block.level ?? 0;
1267
+ if (level <= 0) level = 1;
1268
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
1269
+ const parent = stack[stack.length - 1].items;
1270
+ let href = item.href;
1271
+ if (block.anchors && block.anchors.length > 0) href = anchorKey(item.href, block.anchors[0]);
1272
+ const newItem = { id: "", label, href, children: [] };
1273
+ parent.push(newItem);
1274
+ stack.push({ level, items: newItem.children });
1275
+ }
1276
+ }
1277
+ assignTocIds(toc, []);
1278
+ return toc;
1279
+ }
1280
+ /** Port of fillMissingMetadata. */
1281
+ fillMissingMetadata() {
1282
+ if (this.metadata.title && this.metadata.coverHref) return;
1283
+ if (this.spine.length === 0 || !this.spine[0].href) return;
1284
+ const doc = this.documentFor(this.spine[0].href);
1285
+ if (!doc) return;
1286
+ const { title, heading, img } = scanXhtmlForFallbacks(doc);
1287
+ if (!this.metadata.title) {
1288
+ if (title) this.metadata.title = title;
1289
+ else if (heading) this.metadata.title = heading;
1290
+ else {
1291
+ this.metadata.title = "Unknown";
1292
+ this.warn("metadata", "missing title", this.spine[0].href);
1293
+ }
1294
+ }
1295
+ if (this.metadata.title && this.metadata.titles.length === 0) {
1296
+ this.metadata.titles.push({
1297
+ id: "",
1298
+ value: this.metadata.title,
1299
+ language: "",
1300
+ scheme: "",
1301
+ fileAs: "",
1302
+ role: "",
1303
+ displaySeq: 0,
1304
+ refinements: {}
1305
+ });
1306
+ }
1307
+ if (!this.metadata.coverHref && img) {
1308
+ this.metadata.coverHref = resolvePath(posixDir(this.spine[0].href), img);
1309
+ }
1310
+ }
1311
+ };
1312
+ function mergeConfig(opts) {
1313
+ return {
1314
+ chunking: opts?.chunking ?? DEFAULT_CONFIG.chunking,
1315
+ rootfilePath: opts?.rootfilePath,
1316
+ fallbacks: { ...DEFAULT_FALLBACKS, ...opts?.fallbacks }
1317
+ };
1318
+ }
1319
+ function parse(bytes, opts) {
1320
+ const cfg = mergeConfig(opts);
1321
+ const archive = new Archive(bytes);
1322
+ const book = new Book(archive, cfg);
1323
+ let rootfiles = [];
1324
+ try {
1325
+ rootfiles = parseContainer(archive);
1326
+ } catch (e) {
1327
+ book.warn("container", String(e), "META-INF/container.xml");
1328
+ if (cfg.fallbacks.scanArchive) rootfiles = findOpfs(archive);
1329
+ else throw e;
1330
+ }
1331
+ let rootfilePath = cfg.rootfilePath || "";
1332
+ let opf = null;
1333
+ if (!rootfilePath && rootfiles.length > 0) {
1334
+ if (rootfiles.length > 1) {
1335
+ const sel = selectRootfile(archive, rootfiles);
1336
+ rootfilePath = sel.path || rootfiles[0];
1337
+ opf = sel.opf;
1338
+ } else {
1339
+ rootfilePath = rootfiles[0];
1340
+ }
1341
+ }
1342
+ if (rootfilePath && !opf) {
1343
+ const text = archive.text(rootfilePath);
1344
+ opf = text === void 0 ? null : parseOpf(text);
1345
+ }
1346
+ if (!rootfilePath && rootfiles.length === 0) {
1347
+ if (!cfg.fallbacks.scanArchive) throw ErrNoRootfile;
1348
+ book.warn("container", "no rootfile found; scanning archive", "");
1349
+ book.manifest = fallbackManifestFromArchive(archive);
1350
+ if (cfg.fallbacks.inferSpine) book.spine = fallbackSpineFromManifest(book.manifest);
1351
+ if (book.spine.length === 0) throw ErrNoSpine;
1352
+ if (cfg.fallbacks.generateMetadata) book.fillMissingMetadata();
1353
+ if (cfg.fallbacks.generateTOC) book.toc = book.deriveTocFromHeadings();
1354
+ return book;
1355
+ }
1356
+ if (!opf) {
1357
+ opf = { metadata: emptyMetadata(), manifest: /* @__PURE__ */ new Map(), spine: [], spineToc: "", coverId: "", version: "" };
1358
+ }
1359
+ book.rootfilePath = rootfilePath;
1360
+ book.basePath = posixDir(rootfilePath);
1361
+ book.metadata = opf.metadata;
1362
+ book.manifest = opf.manifest;
1363
+ book.spine = opf.spine;
1364
+ for (const [id, item] of book.manifest) {
1365
+ if (!item.path) item.path = resolvePath(book.basePath, item.href);
1366
+ book.manifest.set(id, item);
1367
+ }
1368
+ for (const s of book.spine) {
1369
+ if (!s.href && s.idRef) s.href = book.manifest.get(s.idRef)?.path ?? "";
1370
+ }
1371
+ if (book.manifest.size === 0 && cfg.fallbacks.scanArchive) {
1372
+ book.manifest = fallbackManifestFromArchive(archive);
1373
+ }
1374
+ if (book.spine.length === 0 && cfg.fallbacks.inferSpine) {
1375
+ book.spine = fallbackSpineFromManifest(book.manifest);
1376
+ }
1377
+ const cover = resolveCover(book.manifest, opf.coverId);
1378
+ if (cover) book.metadata.coverHref = cover;
1379
+ if (cfg.fallbacks.generateMetadata) book.fillMissingMetadata();
1380
+ const tocPath = resolveTocPath(book.manifest, opf.spineToc);
1381
+ if (tocPath) {
1382
+ try {
1383
+ const nav = parseToc(archive, tocPath);
1384
+ book.toc = nav.toc;
1385
+ book.landmarks = nav.landmarks;
1386
+ book.pageList = nav.pageList;
1387
+ } catch (e) {
1388
+ book.warn("toc", String(e), tocPath);
1389
+ }
1390
+ }
1391
+ if (book.toc.length === 0 && cfg.fallbacks.generateTOC) {
1392
+ book.toc = book.deriveTocFromHeadings();
1393
+ }
1394
+ return book;
1395
+ }
1396
+
1397
+ export { Book, blockToText, contentTypeFromHref, flattenToc, parse, posixClean, posixDir, posixJoin, resolvePath };