@prose-reader/cfi 1.209.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.
@@ -0,0 +1,2 @@
1
+ import { ParsedCfi } from './parse';
2
+ export declare function compare(a: ParsedCfi | string, b: ParsedCfi | string): number;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ export interface GenerateOptions {
2
+ includeTextAssertions?: boolean;
3
+ textAssertionLength?: number;
4
+ includeSideBias?: "before" | "after";
5
+ spatialOffset?: [number, number];
6
+ extensions?: Record<string, string>;
7
+ }
8
+ export interface CfiPosition {
9
+ node: Node;
10
+ offset?: number;
11
+ temporal?: number;
12
+ spatial?: [number, number];
13
+ }
14
+ export declare function generate(position: Node | CfiPosition | {
15
+ start: CfiPosition;
16
+ end: CfiPosition;
17
+ }, options?: GenerateOptions): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ export { parse } from './parse';
2
+ export { resolve } from './resolve';
3
+ export { generate } from './generate';
4
+ export { compare } from './compare';
5
+ export { isParsedCfiRange, resolveExtensions } from './resolve';
6
+ export { serialize } from './serialize';
package/dist/index.js ADDED
@@ -0,0 +1,672 @@
1
+ function G(e) {
2
+ const t = [e];
3
+ let n = e;
4
+ for (; n.parentNode; )
5
+ t.push(n.parentNode), n = n.parentNode;
6
+ return t;
7
+ }
8
+ function J(e, t) {
9
+ if (e === t) return e;
10
+ const n = G(e), r = new Set(n);
11
+ let s = t;
12
+ for (; s; ) {
13
+ if (r.has(s))
14
+ return s;
15
+ s = s.parentNode;
16
+ }
17
+ return null;
18
+ }
19
+ const K = /[\[\]\^,();]/g;
20
+ function N(e) {
21
+ return e.replace(K, "^$&");
22
+ }
23
+ const Q = /^epubcfi\((.*)\)$/, R = (e) => e.nodeType === Node.ELEMENT_NODE, M = (e) => typeof e == "object" && e !== null && "nodeType" in e && (e.nodeType === Node.ELEMENT_NODE || e.nodeType === Node.TEXT_NODE), _ = (e) => e.nodeType === Node.TEXT_NODE;
24
+ function V(e) {
25
+ const t = e.match(Q);
26
+ return t && t[1] || e;
27
+ }
28
+ function Z(e) {
29
+ const t = [];
30
+ let n = null, r = !1, s = "";
31
+ const o = (c) => {
32
+ t.push(c), n = null, s = "";
33
+ }, l = (c) => {
34
+ s += c, r = !1;
35
+ }, a = V(e).trim(), h = Array.from(a).concat("");
36
+ for (let c = 0; c < h.length; c++) {
37
+ const f = h[c];
38
+ if (!f) {
39
+ n === "/" || n === ":" ? o([n, parseInt(s, 10)]) : n === "~" ? o(["~", parseFloat(s)]) : n === "@" ? o(["@", parseFloat(s)]) : n === "[" ? o(["[", s]) : (n === ";" || n != null && n.startsWith(";")) && o([n, s]);
40
+ break;
41
+ }
42
+ if (f === "^" && !r) {
43
+ r = !0;
44
+ continue;
45
+ }
46
+ if (n === "!")
47
+ o(["!", 0]);
48
+ else if (n === ",")
49
+ o([",", 0]);
50
+ else if (n === "/" || n === ":") {
51
+ if (/^\d$/.test(f)) {
52
+ l(f);
53
+ continue;
54
+ }
55
+ o([n, parseInt(s, 10)]);
56
+ } else if (n === "~") {
57
+ if (/^\d$/.test(f) || f === ".") {
58
+ l(f);
59
+ continue;
60
+ }
61
+ o(["~", parseFloat(s)]);
62
+ } else if (n === "@") {
63
+ if (f === ":") {
64
+ o(["@", parseFloat(s)]), n = "@";
65
+ continue;
66
+ }
67
+ if (/^\d$/.test(f) || f === ".") {
68
+ l(f);
69
+ continue;
70
+ }
71
+ o(["@", parseFloat(s)]);
72
+ } else if (n === "[")
73
+ if (f === ";" && !r)
74
+ o(["[", s]), n = ";";
75
+ else if (f === "]" && !r)
76
+ o(["[", s]);
77
+ else {
78
+ l(f);
79
+ continue;
80
+ }
81
+ else if (n === ";")
82
+ if (f === "=" && !r)
83
+ n = `;${s}`, s = "";
84
+ else if (f === ";" && !r)
85
+ o([n, s]), n = ";";
86
+ else if (f === "]" && !r)
87
+ o([n, s]);
88
+ else {
89
+ l(f);
90
+ continue;
91
+ }
92
+ else if (n != null && n.startsWith(";"))
93
+ if (f === ";" && !r)
94
+ o([n, s]), n = ";";
95
+ else if (f === "]" && !r)
96
+ o([n, s]);
97
+ else {
98
+ l(f);
99
+ continue;
100
+ }
101
+ else n === null && f === ";" && (n = ";");
102
+ (f === "/" || f === ":" || f === "~" || f === "@" || f === "[" || f === "!" || f === ",") && (n = f);
103
+ }
104
+ return t;
105
+ }
106
+ function z(e, t) {
107
+ return e ? e.map((n, r) => n[0] === t ? r : null).filter((n) => n !== null) : [];
108
+ }
109
+ function Y(e, t) {
110
+ const n = [];
111
+ let r = 0;
112
+ for (const s of t)
113
+ n.push(e.slice(r, s)), r = s;
114
+ return n.push(e.slice(r)), n;
115
+ }
116
+ function P(e) {
117
+ var s;
118
+ const t = [], n = {};
119
+ let r = -1;
120
+ for (let o = 0; o < e.length; o++) {
121
+ const l = e[o];
122
+ if (!l) continue;
123
+ const [a, h] = l;
124
+ a === "/" ? (r++, t[r] = { index: h }, n[r] = []) : r >= 0 && ((s = n[r]) == null || s.push(l));
125
+ }
126
+ for (let o = 0; o < t.length; o++) {
127
+ const l = t[o];
128
+ if (!l) continue;
129
+ const a = n[o] || [];
130
+ for (let h = 0; h < a.length; h++) {
131
+ const c = a[h];
132
+ if (!c) continue;
133
+ const [f, d] = c;
134
+ if (f === ":")
135
+ l.offset = d;
136
+ else if (f === "~")
137
+ l.temporal = d;
138
+ else if (f === "@")
139
+ l.spatial = (l.spatial || []).concat(d);
140
+ else if (f === ";s")
141
+ l.side = d;
142
+ else if (f.startsWith(";") && f !== ";s") {
143
+ const g = f.substring(1);
144
+ l.extensions || (l.extensions = {}), l.extensions[g] = d;
145
+ } else if (f === "[") {
146
+ const g = typeof d == "string" && !d.includes(" ") && d.length < 50;
147
+ if (h === 0 && g && !l.id)
148
+ l.id = d;
149
+ else if (d !== "") {
150
+ const i = d.split(",").map((u) => u.trim());
151
+ l.text = i;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ return t;
157
+ }
158
+ function b(e) {
159
+ const t = z(e, "!");
160
+ return Y(e, t).map(P);
161
+ }
162
+ function A(e) {
163
+ if (!e)
164
+ throw new Error("CFI string cannot be empty");
165
+ const t = Z(e);
166
+ if (!t || t.length === 0)
167
+ throw new Error("Failed to tokenize CFI string");
168
+ const n = z(t, ",");
169
+ if (n.length === 0)
170
+ return b(t);
171
+ const [r, s, o] = Y(t, n);
172
+ return {
173
+ parent: b(r || []),
174
+ start: b(s || []),
175
+ end: b(o || [])
176
+ };
177
+ }
178
+ function O(e) {
179
+ return e !== null && typeof e == "object" && "parent" in e && "start" in e && "end" in e;
180
+ }
181
+ function se(e, t, n = {}) {
182
+ try {
183
+ if (typeof e != "string")
184
+ return D(e, t, n);
185
+ const r = A(e);
186
+ return n.asRange && O(r) ? U(r, t) : D(r, t, n);
187
+ } catch (r) {
188
+ if (n.throwOnError)
189
+ throw r;
190
+ return { node: null, isRange: !1 };
191
+ }
192
+ }
193
+ function D(e, t, n = { asRange: !1 }) {
194
+ const { asRange: r = !1 } = n;
195
+ if (O(e)) {
196
+ if (r)
197
+ return U(e, t);
198
+ const s = e.start[0] || [];
199
+ return v(s, t, n);
200
+ }
201
+ if (e[0])
202
+ return v(e[0], t, n);
203
+ throw new Error("Invalid CFI structure");
204
+ }
205
+ function U(e, t) {
206
+ const n = e.parent[0] || [], r = e.start[0] || [], s = e.end[0] || [];
207
+ let o = t.documentElement;
208
+ if (n.length > 0) {
209
+ const d = v(n, t);
210
+ M(d.node) && (o = d.node);
211
+ }
212
+ if (!o)
213
+ throw new Error("Failed to resolve parent node in CFI range");
214
+ const l = v(r, t), a = v(s, t);
215
+ if (!M(l.node) || !M(a.node))
216
+ throw new Error("Failed to resolve start or end node in CFI range");
217
+ const h = l.node, c = a.node, f = t.createRange();
218
+ return f.setStart(
219
+ h,
220
+ (Array.isArray(l.offset) ? l.offset[0] : l.offset) ?? 0
221
+ ), f.setEnd(
222
+ c,
223
+ (Array.isArray(a.offset) ? a.offset[0] : a.offset) ?? 0
224
+ ), {
225
+ node: f,
226
+ isRange: !0,
227
+ offset: l.offset,
228
+ temporal: l.temporal,
229
+ spatial: l.spatial,
230
+ side: l.side,
231
+ extensions: l.extensions
232
+ };
233
+ }
234
+ function y(e) {
235
+ if (e != null && e.side) return e.side;
236
+ if (e != null && e.text && e.text.length > 0) {
237
+ const t = e.text[0];
238
+ if (t) {
239
+ const n = t.match(/^([ab])$/);
240
+ if (n)
241
+ return n[1];
242
+ }
243
+ }
244
+ }
245
+ function w(e) {
246
+ return e.index % 2 !== 0;
247
+ }
248
+ function ie(e) {
249
+ const n = (O(e) ? e.end : e).at(-1), r = n == null ? void 0 : n.at(-1);
250
+ return r == null ? void 0 : r.extensions;
251
+ }
252
+ function v(e, t, n = {}) {
253
+ const { throwOnError: r = !1, asRange: s = !1 } = n;
254
+ if (!t) {
255
+ if (r)
256
+ throw new Error("Document is not available");
257
+ return { node: null, isRange: !1 };
258
+ }
259
+ const { node: o, remainingPathIndex: l } = ee(t, e);
260
+ if (o && l >= e.length) {
261
+ const i = e.at(-1);
262
+ if (i && w(i)) {
263
+ const $ = i.index - 1;
264
+ if ($ >= 0 && $ < o.childNodes.length) {
265
+ const m = o.childNodes[$], p = y(i), E = i == null ? void 0 : i.extensions;
266
+ return {
267
+ node: m,
268
+ isRange: !1,
269
+ offset: i == null ? void 0 : i.offset,
270
+ temporal: i == null ? void 0 : i.temporal,
271
+ spatial: i == null ? void 0 : i.spatial,
272
+ side: p,
273
+ extensions: E
274
+ };
275
+ }
276
+ }
277
+ const u = y(i), x = i == null ? void 0 : i.extensions;
278
+ if (s) {
279
+ const $ = t.createRange();
280
+ if ($.selectNodeContents(o), (i == null ? void 0 : i.offset) !== void 0) {
281
+ const m = Array.isArray(i.offset) ? i.offset[0] : i.offset;
282
+ _(o) && $.setStart(o, m || 0);
283
+ }
284
+ return $.setEnd(o, (i == null ? void 0 : i.offset) || 0), {
285
+ node: $,
286
+ isRange: !0,
287
+ offset: i == null ? void 0 : i.offset,
288
+ temporal: i == null ? void 0 : i.temporal,
289
+ spatial: i == null ? void 0 : i.spatial,
290
+ side: u,
291
+ extensions: x
292
+ };
293
+ }
294
+ return {
295
+ node: o,
296
+ isRange: !1,
297
+ offset: i == null ? void 0 : i.offset,
298
+ temporal: i == null ? void 0 : i.temporal,
299
+ spatial: i == null ? void 0 : i.spatial,
300
+ side: u,
301
+ extensions: x
302
+ };
303
+ }
304
+ let a = o || t.documentElement;
305
+ const h = o ? l : 0;
306
+ if (s && e.length > 0) {
307
+ const i = e[e.length - 1];
308
+ if (i && !w(i)) {
309
+ if (i.index === 0 && a) {
310
+ const x = t.createRange();
311
+ return x.setStart(a, 0), x.setEnd(a, 0), {
312
+ node: x,
313
+ isRange: !0,
314
+ offset: i.offset,
315
+ temporal: i.temporal,
316
+ spatial: i.spatial,
317
+ side: y(i),
318
+ extensions: i.extensions
319
+ };
320
+ }
321
+ let u = a;
322
+ for (let x = h; x < e.length - 1; x++) {
323
+ const $ = e[x];
324
+ if (!u || !$) break;
325
+ if (w($)) {
326
+ const m = $.index - 1;
327
+ if (m >= 0 && m < u.childNodes.length)
328
+ u = u.childNodes[m];
329
+ else {
330
+ u = null;
331
+ break;
332
+ }
333
+ } else {
334
+ const m = Array.from(
335
+ u.childNodes
336
+ ).filter((E) => E.nodeType === Node.ELEMENT_NODE), p = Math.floor($.index / 2) - 1;
337
+ if (p >= 0 && p < m.length) {
338
+ const E = m[p];
339
+ if (E)
340
+ u = E;
341
+ else {
342
+ u = null;
343
+ break;
344
+ }
345
+ } else {
346
+ u = null;
347
+ break;
348
+ }
349
+ }
350
+ }
351
+ if (u) {
352
+ const x = Array.from(u.childNodes).filter(
353
+ (m) => m.nodeType === Node.ELEMENT_NODE
354
+ );
355
+ if (Math.floor(i.index / 2) - 1 === x.length) {
356
+ const m = t.createRange();
357
+ return m.selectNodeContents(u), m.collapse(!1), {
358
+ node: m,
359
+ isRange: !0,
360
+ offset: i.offset,
361
+ temporal: i.temporal,
362
+ spatial: i.spatial,
363
+ side: y(i),
364
+ extensions: i.extensions
365
+ };
366
+ }
367
+ }
368
+ }
369
+ }
370
+ for (let i = h; i < e.length; i++) {
371
+ const u = e[i];
372
+ if (!a || !u) break;
373
+ if (w(u)) {
374
+ const x = u.index - 1;
375
+ if (x >= 0 && x < a.childNodes.length)
376
+ a = a.childNodes[x];
377
+ else {
378
+ if (r)
379
+ throw new Error(`Invalid text node index: ${u.index}`);
380
+ a = null;
381
+ break;
382
+ }
383
+ } else {
384
+ const x = Array.from(a.childNodes).filter(
385
+ (m) => m.nodeType === Node.ELEMENT_NODE
386
+ ), $ = Math.floor(u.index / 2) - 1;
387
+ if ($ >= 0 && $ < x.length) {
388
+ const m = x[$];
389
+ m && (a = m);
390
+ } else {
391
+ if (r)
392
+ throw new Error(`Invalid element step index: ${u.index}`);
393
+ a = null;
394
+ break;
395
+ }
396
+ }
397
+ }
398
+ if (!a) {
399
+ if (r)
400
+ throw new Error("Failed to resolve CFI path");
401
+ return { node: null, isRange: !1 };
402
+ }
403
+ const c = e.at(-1), f = y(c), d = c == null ? void 0 : c.extensions, g = {
404
+ node: a,
405
+ isRange: !1,
406
+ offset: c == null ? void 0 : c.offset,
407
+ temporal: c == null ? void 0 : c.temporal,
408
+ spatial: c == null ? void 0 : c.spatial,
409
+ side: f,
410
+ extensions: d
411
+ };
412
+ if (s) {
413
+ const i = t.createRange();
414
+ if (i.selectNodeContents(a), (c == null ? void 0 : c.offset) !== void 0) {
415
+ const u = Array.isArray(c.offset) ? c.offset[0] : c.offset;
416
+ _(a) && i.setStart(a, u || 0);
417
+ }
418
+ return {
419
+ ...g,
420
+ node: i,
421
+ isRange: !0
422
+ };
423
+ }
424
+ return g;
425
+ }
426
+ function ee(e, t) {
427
+ for (let n = t.length - 1; n >= 0; n--) {
428
+ const r = t[n];
429
+ if (r != null && r.id) {
430
+ const s = e.getElementById(r.id);
431
+ if (s) return { node: s, remainingPathIndex: n + 1 };
432
+ }
433
+ }
434
+ return { node: null, remainingPathIndex: 0 };
435
+ }
436
+ function q(e, t, n = {}) {
437
+ if (!e.textContent || e.textContent.trim() === "")
438
+ return null;
439
+ const r = e.textContent, s = n.textAssertionLength || 10;
440
+ if (t !== void 0 && t <= r.length) {
441
+ const o = Math.floor(s / 2), l = Math.max(0, t - o), a = Math.min(r.length, t + o);
442
+ return r.substring(l, a);
443
+ }
444
+ return r.substring(0, Math.min(r.length, s));
445
+ }
446
+ function H(e, t) {
447
+ if (!t || Object.keys(t).length === 0)
448
+ return e;
449
+ const n = Object.entries(t).map(
450
+ ([r, s]) => `${r}=${encodeURIComponent(N(s))}`
451
+ );
452
+ return e.endsWith("]") ? `${e.substring(0, e.length - 1)}[;${n.join(";")}]` : `${e}[;${n.join(";")}]`;
453
+ }
454
+ function C(e, t, n = {}, r) {
455
+ var f;
456
+ let s = "", o = e, l = null;
457
+ if (e.nodeType === Node.TEXT_NODE) {
458
+ l = e;
459
+ const d = e.parentNode;
460
+ if (!d)
461
+ throw new Error("Text node doesn't have a parent");
462
+ const i = Array.from(d.childNodes).indexOf(e);
463
+ if (i === -1)
464
+ throw new Error("Node not found in parent's children");
465
+ if (s = `/${i + 1}`, R(d) && d.id) {
466
+ const u = d.id, x = Array.from(((f = d.parentNode) == null ? void 0 : f.childNodes) || []);
467
+ s = `/${x.slice(0, x.indexOf(d) + 1).filter((p) => p.nodeType === Node.ELEMENT_NODE).length * 2}[${N(u)}]${s}`, o = d.parentNode;
468
+ } else
469
+ o = d;
470
+ }
471
+ let a = null;
472
+ for (l && n.includeTextAssertions && (a = q(l, t, n)); o != null && o.parentNode; ) {
473
+ if (!(l && o === l.parentNode && s.includes(`[${R(o) ? o.id : ""}]`))) {
474
+ const d = o.parentNode, g = Array.from(d.childNodes), i = g.indexOf(o);
475
+ if (i === -1)
476
+ throw new Error("Node not found in parent's children");
477
+ const u = g.slice(0, i + 1).filter((m) => m.nodeType === Node.ELEMENT_NODE);
478
+ let x;
479
+ o.nodeType, Node.ELEMENT_NODE, x = u.length;
480
+ const $ = x * 2;
481
+ R(o) && o.id ? s = `/${$}[${N(o.id)}]${s}` : s = `/${$}${s}`;
482
+ }
483
+ if (o.parentNode.nodeName.toLowerCase() === "html")
484
+ break;
485
+ o = o.parentNode;
486
+ }
487
+ t !== void 0 && (s += `:${t}`);
488
+ const h = r == null ? void 0 : r.temporal;
489
+ h !== void 0 && (s += `~${h}`);
490
+ const c = (r == null ? void 0 : r.spatial) || n.spatialOffset;
491
+ if (c !== void 0) {
492
+ const [d, g] = c, i = Math.max(0, Math.min(100, d)), u = Math.max(0, Math.min(100, g));
493
+ s += `@${i}:${u}`;
494
+ }
495
+ if (a && (s += `[${N(a)}]`), n.includeSideBias) {
496
+ const d = n.includeSideBias === "before" ? "b" : "a";
497
+ a ? s = `${s.substring(0, s.length - 1)};s=${d}]` : s += `[;s=${d}]`;
498
+ }
499
+ return s = H(s, n.extensions), s;
500
+ }
501
+ function L(e, t, n, r = {}, s) {
502
+ if (e === t) {
503
+ let h = n !== void 0 ? `:${n}` : "";
504
+ if ((s == null ? void 0 : s.temporal) !== void 0 && (h += `~${s.temporal}`), s != null && s.spatial) {
505
+ const [c, f] = s.spatial, d = Math.max(0, Math.min(100, c)), g = Math.max(0, Math.min(100, f));
506
+ s.temporal !== void 0 ? h += `@${d}:${g}` : h += `@${d}:${g}`;
507
+ }
508
+ return h;
509
+ }
510
+ const o = [];
511
+ let l = t;
512
+ for (; l && l !== e; ) {
513
+ const h = l.parentNode;
514
+ if (!h) break;
515
+ const c = h.childNodes;
516
+ let f = -1;
517
+ for (let g = 0; g < c.length; g++)
518
+ if (c[g] === l) {
519
+ f = g;
520
+ break;
521
+ }
522
+ if (f === -1)
523
+ throw new Error("Node not found in parent's children");
524
+ const d = f + 1;
525
+ R(l) && l.id ? o.unshift(`/${d}[${N(l.id)}]`) : o.unshift(`/${d}`), l = h;
526
+ }
527
+ let a = o.join("");
528
+ if (n !== void 0 && (a += `:${n}`), (s == null ? void 0 : s.temporal) !== void 0 && (a += `~${s.temporal}`), s != null && s.spatial) {
529
+ const [h, c] = s.spatial, f = Math.max(0, Math.min(100, h)), d = Math.max(0, Math.min(100, c));
530
+ a += `@${f}:${d}`;
531
+ }
532
+ if (r.includeTextAssertions && t.nodeType === Node.TEXT_NODE) {
533
+ const h = q(t, n, r);
534
+ h && (a += `[${N(h)}]`);
535
+ }
536
+ if (r.includeSideBias) {
537
+ const h = r.includeSideBias === "before" ? "b" : "a";
538
+ r.includeTextAssertions && t.nodeType === Node.TEXT_NODE ? a = a.replace(/\]$/, `;s=${h}]`) : a += `[;s=${h}]`;
539
+ }
540
+ return a = H(a, r.extensions), a;
541
+ }
542
+ function ne(e, t, n, r, s = {}, o, l) {
543
+ const a = J(e, n);
544
+ if (!a)
545
+ throw new Error("No common ancestor found");
546
+ const h = C(a, void 0, s), c = L(
547
+ a,
548
+ e,
549
+ t,
550
+ s,
551
+ o
552
+ ), f = L(
553
+ a,
554
+ n,
555
+ r,
556
+ s,
557
+ l
558
+ );
559
+ if (s.extensions && Object.keys(s.extensions).length > 0) {
560
+ const d = Object.entries(s.extensions).map(([u, x]) => `${u}=${encodeURIComponent(N(x))}`).join(";"), g = c.includes("[") ? c.replace(/\]$/, `;${d}]`) : `${c}[;${d}]`, i = f.includes("[") ? f.replace(/\]$/, `;${d}]`) : `${f}[;${d}]`;
561
+ return `${h},${g},${i}`;
562
+ }
563
+ return `${h},${c},${f}`;
564
+ }
565
+ function re(e, t = {}) {
566
+ if (M(e))
567
+ return `epubcfi(${C(e, void 0, t)})`;
568
+ if ("node" in e && !("start" in e))
569
+ return `epubcfi(${C(
570
+ e.node,
571
+ e.offset,
572
+ t,
573
+ e
574
+ )})`;
575
+ if ("start" in e && "end" in e) {
576
+ const { start: n, end: r } = e;
577
+ return `epubcfi(${ne(
578
+ n.node,
579
+ n.offset ?? 0,
580
+ r.node,
581
+ r.offset ?? 0,
582
+ t,
583
+ n,
584
+ r
585
+ )})`;
586
+ }
587
+ throw new Error(
588
+ "Invalid argument: expected Node, CfiPosition, or {start, end} object"
589
+ );
590
+ }
591
+ function T(e, t = !1) {
592
+ return typeof e == "string" ? T(A(e), t) : "parent" in e ? t ? e.parent.concat(e.end) : e.parent.concat(e.start) : e;
593
+ }
594
+ function X(e) {
595
+ return e.offset !== void 0 ? 1 : e.index !== void 0 ? 2 : e.temporal !== void 0 || e.spatial !== void 0 ? 3 : 4;
596
+ }
597
+ function W(e, t) {
598
+ var s, o, l, a;
599
+ const n = typeof e == "string" ? A(e) : e, r = typeof t == "string" ? A(t) : t;
600
+ if ("parent" in n || "parent" in r)
601
+ return W(T(n), T(r)) || W(T(n, !0), T(r, !0));
602
+ for (let h = 0; h < Math.max(n.length, r.length); h++) {
603
+ const c = n[h] || [], f = r[h] || [], d = Math.max(c.length, f.length) - 1;
604
+ for (let g = 0; g <= d; g++) {
605
+ const i = c[g], u = f[g];
606
+ if (!i) return -1;
607
+ if (!u) return 1;
608
+ const x = X(i), $ = X(u);
609
+ if (x !== $)
610
+ return x - $;
611
+ if (i.index > u.index) return 1;
612
+ if (i.index < u.index) return -1;
613
+ const m = i.temporal !== void 0, p = u.temporal !== void 0;
614
+ if (m && !p) return 1;
615
+ if (!m && p) return -1;
616
+ if (m && p) {
617
+ if ((i.temporal ?? 0) > (u.temporal ?? 0)) return 1;
618
+ if ((i.temporal ?? 0) < (u.temporal ?? 0)) return -1;
619
+ }
620
+ const E = i.spatial !== void 0, k = u.spatial !== void 0;
621
+ if (E && !k) return 1;
622
+ if (!E && k) return -1;
623
+ if (E && k) {
624
+ const S = ((s = i.spatial) == null ? void 0 : s[1]) ?? 0, j = ((o = u.spatial) == null ? void 0 : o[1]) ?? 0;
625
+ if (S > j) return 1;
626
+ if (S < j) return -1;
627
+ const B = ((l = i.spatial) == null ? void 0 : l[0]) ?? 0, F = ((a = u.spatial) == null ? void 0 : a[0]) ?? 0;
628
+ if (B > F) return 1;
629
+ if (B < F) return -1;
630
+ }
631
+ if (g === d) {
632
+ if ((i.offset ?? 0) > (u.offset ?? 0)) return 1;
633
+ if ((i.offset ?? 0) < (u.offset ?? 0)) return -1;
634
+ }
635
+ }
636
+ }
637
+ return 0;
638
+ }
639
+ function te(e) {
640
+ let t = `/${e.index}`;
641
+ if (e.id) {
642
+ if (t += `[${N(e.id)}`, e.extensions)
643
+ for (const [r, s] of Object.entries(e.extensions))
644
+ t += `;${r}=${N(s)}`;
645
+ t += "]";
646
+ }
647
+ e.offset !== void 0 && (t += `:${e.offset}`), e.temporal !== void 0 && (t += `~${e.temporal}`), e.spatial && e.spatial.length > 0 && (t += `@${e.spatial.join(":")}`);
648
+ const n = [];
649
+ if (console.log(e.text), e.text && e.text.length > 0 && n.push(e.text.map(N).join(",")), (e.side || e.extensions && !e.id) && (e.side && n.push(`;s=${e.side}`), e.extensions && !e.id))
650
+ for (const [r, s] of Object.entries(e.extensions))
651
+ n.push(`;${r}=${N(s)}`);
652
+ return n.length > 0 && (t += `[${n.join("")}]`), t;
653
+ }
654
+ function I(e) {
655
+ return e.map((t) => te(t)).join("");
656
+ }
657
+ function oe(e) {
658
+ if (Array.isArray(e))
659
+ return `epubcfi(${e.map(I).join("!")})`;
660
+ const t = e.parent.map(I).join("!"), n = e.start.map(I).join("!"), r = e.end.map(I).join("!");
661
+ return `epubcfi(${t},${n},${r})`;
662
+ }
663
+ export {
664
+ W as compare,
665
+ re as generate,
666
+ O as isParsedCfiRange,
667
+ A as parse,
668
+ se as resolve,
669
+ ie as resolveExtensions,
670
+ oe as serialize
671
+ };
672
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/utils.ts","../src/parse.ts","../src/resolve.ts","../src/generate.ts","../src/compare.ts","../src/serialize.ts"],"sourcesContent":["/**\n * Utility functions for EPUB CFI operations\n */\n\n/**\n * Get all ancestors of a node, including the node itself\n */\nexport function getAncestors(node: Node): Node[] {\n const ancestors: Node[] = [node]\n let current: Node | null = node\n\n while (current.parentNode) {\n ancestors.push(current.parentNode)\n current = current.parentNode\n }\n\n return ancestors\n}\n\n/**\n * Find the closest common ancestor of two nodes\n */\nexport function findCommonAncestor(nodeA: Node, nodeB: Node): Node | null {\n if (nodeA === nodeB) return nodeA\n\n const ancestorsA = getAncestors(nodeA)\n const ancestorsSet = new Set(ancestorsA)\n\n // Start with nodeB and traverse up until we find a common ancestor\n let current: Node | null = nodeB\n while (current) {\n if (ancestorsSet.has(current)) {\n return current\n }\n current = current.parentNode\n }\n\n return null\n}\n\n/**\n * Special characters in CFI that need to be escaped according to the spec\n * These are: [ ] ^ , ( ) ;\n */\nexport const CFI_SPECIAL_CHARS = /[\\[\\]\\^,();]/g\n\n/**\n * Escape special characters in a CFI string\n * @param str The string to escape\n * @returns The escaped string\n */\nexport function cfiEscape(str: string): string {\n return str.replace(CFI_SPECIAL_CHARS, `^$&`)\n}\n\n/**\n * Regular expression to check if a string is a valid CFI\n */\nexport const isCFI = /^epubcfi\\((.*)\\)$/\n\n/**\n * Wrap a CFI string in the epubcfi() function\n * @param cfi The CFI string to wrap\n * @returns The wrapped CFI string\n */\nexport function wrapCfi(cfi: string): string {\n return isCFI.test(cfi) ? cfi : `epubcfi(${cfi})`\n}\n\n/**\n * @important Make it non browser runtime specific\n */\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === Node.ELEMENT_NODE\n\n/**\n * @important Make it non browser runtime specific\n */\n// biome-ignore lint/suspicious/noExplicitAny: <explanation>\nexport const isNode = (node: any): node is Node =>\n typeof node === \"object\" &&\n node !== null &&\n \"nodeType\" in node &&\n (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE)\n\n/**\n * Check if a node is a text node\n */\nexport const isTextNode = (node: Node): boolean =>\n node.nodeType === Node.TEXT_NODE\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n * Based on the EPUB CFI 1.1 specification: https://idpf.org/epub/linking/cfi/epub-cfi.html\n */\n\nimport { isCFI } from \"./utils\"\n\n/**\n * Interface for a parsed CFI part\n *\n * According to the EPUB CFI 1.1 specification, each step in a CFI path can have\n * its own properties including ID assertions, character offsets, and extension parameters.\n * Extensions are parameters in the form of key-value pairs that can be attached to any\n * step in the CFI path to provide additional information or metadata about that specific step.\n */\nexport interface CfiPart {\n index: number\n id?: string\n offset?: number\n temporal?: number\n spatial?: number[]\n text?: string[]\n side?: string\n /**\n * Extension parameters for this CFI path step\n *\n * The EPUB CFI spec allows for extension parameters to be attached to any step in the path.\n * These are key-value pairs that provide additional information or metadata.\n * For example, in /6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[;vnd.example.param=value],\n * the extension parameter is \"vnd.example.param\" with value \"value\".\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Interface for a parsed CFI range\n */\nexport interface CfiRange {\n parent: CfiPart[][]\n start: CfiPart[][]\n end: CfiPart[][]\n}\n\n/**\n * Interface for a parsed CFI\n */\nexport type ParsedCfi = CfiPart[][] | CfiRange\n\n/**\n * Unwrap a CFI string from the epubcfi() function\n * @param cfi The CFI string to unwrap\n * @returns The unwrapped CFI string\n */\nexport function unwrapCfi(cfi: string): string {\n const match = cfi.match(isCFI)\n return match ? match[1] || cfi : cfi\n}\n\n/**\n * Token type for CFI parsing\n */\ntype CfiToken = [string, string | number]\n\n/**\n * Tokenize a CFI string into an array of tokens\n * @param cfi The CFI string to tokenize\n * @returns An array of tokens\n */\nfunction tokenize(cfi: string): CfiToken[] {\n const tokens: CfiToken[] = []\n let state: string | null = null\n let isEscaped = false\n let value = \"\"\n\n const push = (token: CfiToken) => {\n tokens.push(token)\n state = null\n value = \"\"\n }\n\n const cat = (c: string) => {\n value += c\n isEscaped = false\n }\n\n const unwrappedCfi = unwrapCfi(cfi).trim()\n const chars = Array.from(unwrappedCfi).concat(\"\")\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]\n\n if (!char) {\n // End of string, push any pending token\n if (state === \"/\" || state === \":\") {\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n push([\"[\", value])\n } else if (state === \";\" || state?.startsWith(\";\")) {\n push([state, value])\n }\n break\n }\n\n // Handle escape characters\n if (char === \"^\" && !isEscaped) {\n isEscaped = true\n continue\n }\n\n if (state === \"!\") {\n push([\"!\", 0])\n } else if (state === \",\") {\n push([\",\", 0])\n } else if (state === \"/\" || state === \":\") {\n if (/^\\d$/.test(char)) {\n cat(char)\n continue\n }\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n if (char === \":\") {\n push([\"@\", parseFloat(value)])\n state = \"@\"\n continue\n }\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n if (char === \";\" && !isEscaped) {\n push([\"[\", value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([\"[\", value])\n } else {\n cat(char)\n continue\n }\n } else if (state === \";\") {\n // Handle extension parameter key\n if (char === \"=\" && !isEscaped) {\n state = `;${value}`\n value = \"\"\n } else if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state?.startsWith(\";\")) {\n // Handle extension parameter value\n if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state === null && char === \";\") {\n // Handle standalone extension parameters (not inside brackets)\n state = \";\"\n }\n\n if (\n char === \"/\" ||\n char === \":\" ||\n char === \"~\" ||\n char === \"@\" ||\n char === \"[\" ||\n char === \"!\" ||\n char === \",\"\n ) {\n state = char\n }\n }\n\n return tokens\n}\n\n/**\n * Find indices of tokens with a specific type\n * @param tokens The tokens to search\n * @param type The type to find\n * @returns An array of indices\n */\nfunction findTokenIndices(\n tokens: CfiToken[] | undefined,\n type: string,\n): number[] {\n if (!tokens) {\n return []\n }\n\n return tokens\n .map((token, i) => (token[0] === type ? i : null))\n .filter((i): i is number => i !== null)\n}\n\n/**\n * Split an array at specific indices\n * @param arr The array to split\n * @param indices The indices to split at\n * @returns An array of arrays\n */\nfunction splitAt<T>(arr: T[], indices: number[]): T[][] {\n const result: T[][] = []\n let start = 0\n\n for (const index of indices) {\n result.push(arr.slice(start, index))\n start = index\n }\n\n result.push(arr.slice(start))\n return result\n}\n\n/**\n * Parse a single part of a CFI\n * @param tokens The tokens to parse\n * @returns An array of CFI parts\n */\nfunction parsePart(tokens: CfiToken[]): CfiPart[] {\n const parts: CfiPart[] = []\n\n // Group tokens by path step\n const pathStepTokens: { [key: number]: CfiToken[] } = {}\n let currentPathStep = -1\n\n // First pass: group tokens by path step\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \"/\") {\n currentPathStep++\n parts[currentPathStep] = { index: val as number }\n pathStepTokens[currentPathStep] = []\n } else if (currentPathStep >= 0) {\n pathStepTokens[currentPathStep]?.push(token)\n }\n }\n\n // Second pass: process tokens for each path step\n for (let stepIndex = 0; stepIndex < parts.length; stepIndex++) {\n const currentPart = parts[stepIndex]\n if (!currentPart) continue\n\n const stepsTokens = pathStepTokens[stepIndex] || []\n\n for (let i = 0; i < stepsTokens.length; i++) {\n const token = stepsTokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \":\") {\n currentPart.offset = val as number\n } else if (type === \"~\") {\n currentPart.temporal = val as number\n } else if (type === \"@\") {\n currentPart.spatial = (currentPart.spatial || []).concat(val as number)\n } else if (type === \";s\") {\n currentPart.side = val as string\n } else if (type.startsWith(\";\") && type !== \";s\") {\n // This is an extension parameter\n const paramName = type.substring(1)\n if (!currentPart.extensions) {\n currentPart.extensions = {}\n }\n currentPart.extensions[paramName] = val as string\n } else if (type === \"[\") {\n // Determine if this is an ID or text assertion\n const looksLikeId =\n typeof val === \"string\" && !val.includes(\" \") && val.length < 50\n\n if (i === 0 && looksLikeId && !currentPart.id) {\n currentPart.id = val as string\n } else {\n // Otherwise, it's a text assertion\n if (val !== \"\") {\n // Split on comma and trim each part\n const parts = (val as string).split(\",\").map((part) => part.trim())\n currentPart.text = parts\n }\n }\n }\n }\n }\n\n return parts\n}\n\n/**\n * Parse a CFI with indirections\n * @param tokens The tokens to parse\n * @returns An array of arrays of CFI parts\n */\nfunction parseIndirection(tokens: CfiToken[]): CfiPart[][] {\n const indirectionIndices = findTokenIndices(tokens, \"!\")\n return splitAt(tokens, indirectionIndices).map(parsePart)\n}\n\n/**\n * Parse a CFI string into a structured representation\n * @param cfi The CFI string to parse\n * @returns A parsed CFI\n */\nexport function parse(cfi: string): ParsedCfi {\n if (!cfi) {\n throw new Error(\"CFI string cannot be empty\")\n }\n\n const tokens = tokenize(cfi)\n if (!tokens || tokens.length === 0) {\n throw new Error(\"Failed to tokenize CFI string\")\n }\n\n const commaIndices = findTokenIndices(tokens, \",\")\n\n if (commaIndices.length === 0) {\n return parseIndirection(tokens)\n }\n\n const [parentTokens, startTokens, endTokens] = splitAt(tokens, commaIndices)\n\n return {\n parent: parseIndirection(parentTokens || []),\n start: parseIndirection(startTokens || []),\n end: parseIndirection(endTokens || []),\n }\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport { isNode, isTextNode } from \"./utils\"\n\n/**\n * Options for resolving a CFI\n */\ninterface ResolveOptions {\n /**\n * Whether to throw an error if the CFI cannot be resolved\n * @default false\n */\n throwOnError?: boolean\n\n /**\n * Whether to return a range instead of a single node\n * @default false\n */\n asRange?: boolean\n}\n\n/**\n * Result of resolving a CFI\n */\ninterface ResolveRangeResult extends ResolveResultBase {\n node: Range | null\n isRange: true\n}\n\ninterface ResolveResultBase {\n offset?: number[] | number\n\n /**\n * The temporal offset if applicable\n */\n temporal?: number\n\n /**\n * The spatial offset if applicable\n */\n spatial?: number[]\n\n /**\n * The side bias if applicable\n */\n side?: string\n\n /**\n * Any extension parameters in the CFI\n */\n extensions?: Record<string, string>\n}\n\ninterface ResolveNodeResult extends ResolveResultBase {\n node: Node | null\n isRange: false\n}\n\ntype ResolveResult = ResolveNodeResult | ResolveRangeResult\n\n/**\n * Check if a parsed CFI is a range\n */\nexport function isParsedCfiRange(parsed: ParsedCfi): parsed is CfiRange {\n return (\n parsed !== null &&\n typeof parsed === \"object\" &&\n \"parent\" in parsed &&\n \"start\" in parsed &&\n \"end\" in parsed\n )\n}\n\n/**\n * Resolves a CFI string to a DOM node or range\n */\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: Omit<ResolveOptions, \"asRange\"> & { asRange: true },\n): ResolveRangeResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: ResolveOptions,\n): ResolveResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n try {\n // If already parsed, use it directly\n if (typeof cfi !== \"string\") {\n return resolveParsed(cfi, document, options)\n }\n\n // Parse the CFI once and use the parsed object for all checks\n const parsedCfi = parse(cfi)\n\n // Handle range CFIs when asRange is true\n if (options.asRange && isParsedCfiRange(parsedCfi)) {\n return resolveRange(parsedCfi, document)\n }\n\n // For all other cases, use the parsed CFI\n return resolveParsed(parsedCfi, document, options)\n } catch (error) {\n if (options.throwOnError) {\n throw error\n }\n\n return { node: null, isRange: false }\n }\n}\n\n/**\n * Resolves a parsed CFI to a DOM node or range\n */\nfunction resolveParsed(\n parsed: ParsedCfi,\n document: Document,\n options: { asRange?: boolean } = { asRange: false },\n): ResolveResult {\n const { asRange = false } = options\n\n // Handle range CFIs\n if (isParsedCfiRange(parsed)) {\n if (asRange) {\n return resolveRange(parsed, document)\n }\n\n // If not as range, use the start point\n const firstPath = parsed.start[0] || []\n return resolvePath(firstPath, document, options)\n }\n\n // Handle path CFI (indirection)\n if (parsed[0]) {\n return resolvePath(parsed[0], document, options)\n }\n\n throw new Error(\"Invalid CFI structure\")\n}\n\n/**\n * Resolves a CFI range to a DOM range\n */\nfunction resolveRange(range: CfiRange, document: Document): ResolveResult {\n // Get the parent path and start/end paths\n const parentPath = range.parent[0] || []\n const startPath = range.start[0] || []\n const endPath = range.end[0] || []\n\n // Find the parent node\n let parentNode: Node | null = document.documentElement\n\n if (parentPath.length > 0) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // Find the start and end nodes\n const startResult = resolvePath(startPath, document)\n const endResult = resolvePath(endPath, document)\n\n // Check that we have valid Node objects\n if (!isNode(startResult.node) || !isNode(endResult.node)) {\n throw new Error(\"Failed to resolve start or end node in CFI range\")\n }\n\n const startNode = startResult.node\n const endNode = endResult.node\n\n const domRange = document.createRange()\n domRange.setStart(\n startNode,\n (Array.isArray(startResult.offset)\n ? startResult.offset[0]\n : startResult.offset) ?? 0,\n )\n domRange.setEnd(\n endNode,\n (Array.isArray(endResult.offset)\n ? endResult.offset[0]\n : endResult.offset) ?? 0,\n )\n\n return {\n node: domRange,\n isRange: true,\n offset: startResult.offset,\n temporal: startResult.temporal,\n spatial: startResult.spatial,\n side: startResult.side,\n extensions: startResult.extensions,\n }\n}\n\n/**\n * Extracts side bias from a CFI part\n */\nfunction extractSideBias(part: CfiPart | undefined): string | undefined {\n // Return the side if it exists\n if (part?.side) return part.side\n\n // Look for side bias in text assertions\n if (part?.text && part.text.length > 0) {\n const text = part.text[0]\n // The CFI spec says side bias can be a=after or b=before\n if (text) {\n const sideBiasMatch = text.match(/^([ab])$/)\n if (sideBiasMatch) {\n return sideBiasMatch[1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Determines if a step in a CFI path is for a text node\n * Text nodes have indices that are not doubled (odd numbers in CFI)\n */\nfunction isTextNodeStep(part: CfiPart): boolean {\n // Per the CFI spec, element indices are always even numbers\n // So if we have an odd number, it's likely a text node or other non-element node\n return part.index % 2 !== 0\n}\n\n/**\n * Returns the extensions from the last valid part of a parsed CFI\n */\nexport function resolveExtensions(parsedCfi: ParsedCfi) {\n const parts = isParsedCfiRange(parsedCfi) ? parsedCfi.end : parsedCfi\n const lastPart = parts.at(-1)\n const lastPartPath = lastPart?.at(-1)\n\n return lastPartPath?.extensions\n}\n\n/**\n * Resolves a CFI path to a DOM node\n */\nfunction resolvePath(\n path: CfiPart[],\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n const { throwOnError = false, asRange = false } = options\n\n if (!document) {\n if (throwOnError) {\n throw new Error(\"Document is not available\")\n }\n return { node: null, isRange: false }\n }\n\n // Look for an element with an ID first\n const { node: nodeById, remainingPathIndex } = findNodeById(document, path)\n\n // If there's no remaining path to process after the ID node, return the ID node\n if (nodeById && remainingPathIndex >= path.length) {\n const lastPart = path.at(-1)\n\n // Handle case where we have element with ID and we need to get to a text node child\n if (lastPart && isTextNodeStep(lastPart)) {\n // Text nodes use 1-based indexing without doubling\n const childIndex = lastPart.index - 1\n\n if (childIndex >= 0 && childIndex < nodeById.childNodes.length) {\n const childNode = nodeById.childNodes[childIndex] as Node\n\n const sideBias = extractSideBias(lastPart)\n const extensions = lastPart?.extensions\n\n return {\n node: childNode,\n isRange: false,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n }\n }\n\n // If no text node reference or it couldn't be found, use the node found by ID\n const sideBias = extractSideBias(lastPart)\n const extensions = lastPart?.extensions\n\n if (asRange) {\n // Create a range\n const range = document.createRange()\n range.selectNodeContents(nodeById)\n\n // Adjust for offset\n if (lastPart?.offset !== undefined) {\n const offset = Array.isArray(lastPart.offset)\n ? lastPart.offset[0]\n : lastPart.offset\n if (isTextNode(nodeById)) {\n range.setStart(nodeById, offset || 0)\n }\n }\n\n range.setEnd(nodeById, lastPart?.offset || 0)\n\n return {\n node: range,\n isRange: true,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n }\n\n return {\n node: nodeById,\n isRange: false,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n }\n\n // Start traversal from the ID node if found, otherwise start from the document root\n let currentNode: Node | null = nodeById || document.documentElement\n\n // Get the starting index for path traversal\n const startIndex = nodeById ? remainingPathIndex : 0\n\n // Special case: Virtual positions (before first element or after last element)\n // only applicable when asRange is true\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\n\n // Check if the last part might be a virtual position indicator\n if (lastPart && !isTextNodeStep(lastPart)) {\n // Handle position before first element (index 0)\n if (lastPart.index === 0 && currentNode) {\n const range = document.createRange()\n range.setStart(currentNode, 0)\n range.setEnd(currentNode, 0)\n\n return {\n node: range,\n isRange: true,\n offset: lastPart.offset,\n temporal: lastPart.temporal,\n spatial: lastPart.spatial,\n side: extractSideBias(lastPart),\n extensions: lastPart.extensions,\n }\n }\n\n // Parent node for navigation\n let parentNode: Node | null = currentNode\n\n // Navigate through all parts except the last one\n for (let i = startIndex; i < path.length - 1; i++) {\n const part = path[i]\n if (!parentNode || !part) break\n\n if (isTextNodeStep(part)) {\n const nodeIndex = part.index - 1\n if (nodeIndex >= 0 && nodeIndex < parentNode.childNodes.length) {\n parentNode = parentNode.childNodes[nodeIndex] as Node\n } else {\n parentNode = null\n break\n }\n } else {\n const childElements: Node[] = Array.from(\n parentNode.childNodes,\n ).filter((node) => node.nodeType === Node.ELEMENT_NODE)\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n parentNode = nextNode\n } else {\n parentNode = null\n break\n }\n } else {\n parentNode = null\n break\n }\n }\n }\n\n // If we still have a parent node, handle possible after-last-element position\n if (parentNode) {\n const childElements: Node[] = Array.from(parentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(lastPart.index / 2) - 1\n\n // If the index is equal to the number of child elements, it's a position after the last element\n if (index === childElements.length) {\n const range = document.createRange()\n range.selectNodeContents(parentNode)\n range.collapse(false) // Collapse to end\n\n return {\n node: range,\n isRange: true,\n offset: lastPart.offset,\n temporal: lastPart.temporal,\n spatial: lastPart.spatial,\n side: extractSideBias(lastPart),\n extensions: lastPart.extensions,\n }\n }\n }\n }\n }\n\n // For each part in the path\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!currentNode || !part) break\n\n // Handle text nodes differently than element nodes\n if (isTextNodeStep(part)) {\n // For text nodes, we use the raw index (minus 1 for 0-based indexing)\n const nodeIndex = part.index - 1\n\n if (nodeIndex >= 0 && nodeIndex < currentNode.childNodes.length) {\n // We know this is a valid index, so the child node must exist\n currentNode = currentNode.childNodes[nodeIndex] as Node\n } else {\n if (throwOnError) {\n throw new Error(`Invalid text node index: ${part.index}`)\n }\n currentNode = null\n break\n }\n } else {\n // For element nodes, we need to filter to just element nodes\n // Get child nodes and try to navigate to the right one\n const childElements: Node[] = Array.from(currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n\n // Calculate the actual index (CFI indices are 1-based and doubled for elements)\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n currentNode = nextNode\n }\n } else {\n if (throwOnError) {\n throw new Error(`Invalid element step index: ${part.index}`)\n }\n\n currentNode = null\n break\n }\n }\n }\n\n if (!currentNode) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n\n return { node: null, isRange: false }\n }\n\n // Prepare the result\n const lastPart = path.at(-1)\n const sideBias = extractSideBias(lastPart)\n const extensions = lastPart?.extensions\n\n const result: ResolveResult = {\n node: currentNode,\n isRange: false,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n\n if (asRange) {\n const range = document.createRange()\n range.selectNodeContents(currentNode)\n\n // Adjust for offset\n if (lastPart?.offset !== undefined) {\n const offset = Array.isArray(lastPart.offset)\n ? lastPart.offset[0]\n : lastPart.offset\n if (isTextNode(currentNode)) {\n range.setStart(currentNode, offset || 0)\n }\n }\n\n return {\n ...result,\n node: range,\n isRange: true,\n }\n }\n\n return result\n}\n\n/**\n * Find a node by ID from a CFI path.\n * Starting from the last part and working backwards.\n * Returns the node and the remaining path that needs to be processed.\n */\nfunction findNodeById(\n document: Document,\n parts: CfiPart[],\n): { node: Node | null; remainingPathIndex: number } {\n for (let i = parts.length - 1; i >= 0; i--) {\n const part = parts[i]\n if (part?.id) {\n const node = document.getElementById(part.id)\n if (node) return { node, remainingPathIndex: i + 1 }\n }\n }\n\n return { node: null, remainingPathIndex: 0 }\n}\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n */\n\nimport { cfiEscape, findCommonAncestor, isElement, isNode } from \"./utils\"\n\n/**\n * Options for generating CFIs\n */\nexport interface GenerateOptions {\n /**\n * Whether to include text assertions for more robust CFIs\n */\n includeTextAssertions?: boolean\n\n /**\n * The maximum length of text to use for text assertions\n * Default is 10 characters\n */\n textAssertionLength?: number\n\n /**\n * Whether to include a side bias assertion\n */\n includeSideBias?: \"before\" | \"after\"\n\n /**\n * Whether to include spatial coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatialOffset?: [number, number]\n\n /**\n * Extension parameters to include in the CFI\n * Keys should be parameter names, values should be the parameter values\n * Vendor-specific parameters should be prefixed with 'vnd.' followed by the vendor name\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Position in a document, consisting of a node and optional offset\n */\nexport interface CfiPosition {\n /**\n * The DOM node\n */\n node: Node\n\n /**\n * Character offset within the node (for text nodes)\n */\n offset?: number\n\n /**\n * Temporal position in seconds (for audio/video content)\n */\n temporal?: number\n\n /**\n * Spatial position as [x,y] coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatial?: [number, number]\n}\n\n/**\n * Extract a suitable text assertion from a text node\n */\nfunction extractTextAssertion(\n textNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n): string | null {\n if (!textNode.textContent || textNode.textContent.trim() === \"\") {\n return null\n }\n\n const textContent = textNode.textContent\n const maxLength = options.textAssertionLength || 10\n\n // If we have an offset, use the text around that position\n if (offset !== undefined && offset <= textContent.length) {\n // We'll take a portion before and after the offset\n const halfLength = Math.floor(maxLength / 2)\n const start = Math.max(0, offset - halfLength)\n const end = Math.min(textContent.length, offset + halfLength)\n return textContent.substring(start, end)\n }\n\n // Otherwise, just take the first part of the text\n return textContent.substring(0, Math.min(textContent.length, maxLength))\n}\n\n/**\n * Add extension parameters to a CFI path\n */\nfunction addExtensions(\n cfi: string,\n extensions?: Record<string, string>,\n): string {\n if (!extensions || Object.keys(extensions).length === 0) {\n return cfi\n }\n\n // Build the extension string\n const extensionParts = Object.entries(extensions).map(\n ([key, value]) => `${key}=${encodeURIComponent(cfiEscape(value))}`,\n )\n\n // If we're dealing with a bracket at end, insert our extensions before the closing bracket\n if (cfi.endsWith(\"]\")) {\n return `${cfi.substring(0, cfi.length - 1)}[;${extensionParts.join(\";\")}]`\n }\n\n // No bracket at the end - just add with new brackets\n return `${cfi}[;${extensionParts.join(\";\")}]`\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n let cfi = \"\"\n let currentNode: Node | null = node\n let textNode: Node | null = null\n\n // Handle text nodes specially\n if (node.nodeType === Node.TEXT_NODE) {\n // If this is a text node, we need to remember it for text assertions\n // but for path construction, we'll work with the parent\n textNode = node\n\n // Store the offset value for later\n const parentNode = node.parentNode\n if (!parentNode) {\n throw new Error(\"Text node doesn't have a parent\")\n }\n\n // Find position of text node among its parent's children\n const siblings = Array.from(parentNode.childNodes)\n const nodeIndex = siblings.indexOf(node as ChildNode)\n\n if (nodeIndex === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n // Add the text node reference to the parent element's CFI\n // Text nodes are referenced by their index + 1 (CFI is 1-based)\n cfi = `/${nodeIndex + 1}`\n\n // If the parent has an ID, include it in the path\n if (isElement(parentNode) && parentNode.id) {\n const parentId = parentNode.id\n // Find the parent's index in its parent's children\n const parentSiblings = Array.from(parentNode.parentNode?.childNodes || [])\n const elementsBefore = parentSiblings\n .slice(0, parentSiblings.indexOf(parentNode as ChildNode) + 1)\n .filter((n) => n.nodeType === Node.ELEMENT_NODE)\n\n const parentIndex = elementsBefore.length * 2\n\n cfi = `/${parentIndex}[${cfiEscape(parentId)}]${cfi}`\n currentNode = parentNode.parentNode\n } else {\n // Continue with the parent as our current node\n currentNode = parentNode\n }\n }\n\n // Set up text assertion if needed\n let textAssertion: string | null = null\n if (textNode && options.includeTextAssertions) {\n textAssertion = extractTextAssertion(textNode, offset, options)\n }\n\n // Build the CFI path from the current node up to the html element\n while (currentNode?.parentNode) {\n // Skip if we're a text node's parent that's already been handled specially\n if (\n !(\n textNode &&\n currentNode === textNode.parentNode &&\n cfi.includes(`[${isElement(currentNode) ? currentNode.id : \"\"}]`)\n )\n ) {\n const parentNode = currentNode.parentNode\n\n // Find index among parent's children\n const siblings = Array.from(parentNode.childNodes)\n const nodeIndex = siblings.indexOf(currentNode as ChildNode)\n\n if (nodeIndex === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n // Find position among element siblings for CFI (element nodes only)\n // For CFI, element references are even-numbered (per CFI spec)\n const elementsBefore = siblings\n .slice(0, nodeIndex + 1)\n .filter((n) => n.nodeType === Node.ELEMENT_NODE)\n\n // Find the position of the current node in element siblings\n let elementIndex: number\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n elementIndex = elementsBefore.length\n } else {\n // For non-element nodes, use the number of elements before it\n elementIndex = elementsBefore.length\n }\n\n // CFI is 1-based, then doubled for element nodes\n const step = elementIndex * 2\n\n // Add the node index to the CFI\n // If the node has an ID, add it to the CFI\n if (isElement(currentNode) && currentNode.id) {\n cfi = `/${step}[${cfiEscape(currentNode.id)}]${cfi}`\n } else {\n cfi = `/${step}${cfi}`\n }\n }\n\n // If we've reached the html element, stop traversing up\n if (currentNode.parentNode.nodeName.toLowerCase() === \"html\") {\n break\n }\n\n currentNode = currentNode.parentNode\n }\n\n // Add the character offset if provided\n if (offset !== undefined) {\n cfi += `:${offset}`\n }\n\n // Add temporal offset if provided (from position parameter)\n const temporal = position?.temporal\n if (temporal !== undefined) {\n cfi += `~${temporal}`\n }\n\n // Add spatial offset if provided (from position parameter or options)\n const spatial = position?.spatial || options.spatialOffset\n if (spatial !== undefined) {\n const [x, y] = spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n\n // Add the spatial offset regardless of temporal offset\n cfi += `@${safeX}:${safeY}`\n }\n\n // Add text assertions if available\n if (textAssertion) {\n cfi += `[${cfiEscape(textAssertion)}]`\n }\n\n // Add side bias if specified\n if (options.includeSideBias) {\n const sideBias = options.includeSideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n // If we don't have a text assertion, add the side bias directly\n cfi += `[;s=${sideBias}]`\n } else {\n // Otherwise, we need to modify the last text assertion\n // Remove the closing bracket and add the side bias\n cfi = `${cfi.substring(0, cfi.length - 1)};s=${sideBias}]`\n }\n }\n\n // Add extension parameters if specified\n cfi = addExtensions(cfi, options.extensions)\n\n return cfi\n}\n\n/**\n * Generate a relative path from one node to another\n */\nfunction generateRelativePath(\n fromNode: Node,\n toNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n if (fromNode === toNode) {\n let result = offset !== undefined ? `:${offset}` : \"\"\n\n // Add temporal offset if provided\n if (position?.temporal !== undefined) {\n result += `~${position.temporal}`\n }\n\n // Add spatial offset if provided\n if (position?.spatial) {\n const [x, y] = position.spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n\n // If we already have a temporal offset, don't add the @ symbol\n if (position.temporal !== undefined) {\n result += `@${safeX}:${safeY}`\n } else {\n result += `@${safeX}:${safeY}`\n }\n }\n\n return result\n }\n\n const path: string[] = []\n let currentNode: Node | null = toNode\n\n // Build path from toNode up to fromNode (exclusive)\n while (currentNode && currentNode !== fromNode) {\n const parentNode = currentNode.parentNode as Node | null\n if (!parentNode) break\n\n const siblings = parentNode.childNodes\n let index = -1\n\n for (let i = 0; i < siblings.length; i++) {\n if (siblings[i] === currentNode) {\n index = i\n break\n }\n }\n\n if (index === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n // Add the node index to the path\n const step = index + 1\n\n // If the node has an ID, add it\n if (isElement(currentNode) && currentNode.id) {\n path.unshift(`/${step}[${cfiEscape(currentNode.id)}]`)\n } else {\n path.unshift(`/${step}`)\n }\n\n currentNode = parentNode\n }\n\n let relativePath = path.join(\"\")\n\n // Add offset if specified\n if (offset !== undefined) {\n relativePath += `:${offset}`\n }\n\n // Add temporal offset if provided\n if (position?.temporal !== undefined) {\n relativePath += `~${position.temporal}`\n }\n\n // Add spatial offset if provided\n if (position?.spatial) {\n const [x, y] = position.spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n\n // Add the spatial offset regardless of temporal offset\n relativePath += `@${safeX}:${safeY}`\n }\n\n // Add text assertion if enabled\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n if (textAssertion) {\n relativePath += `[${cfiEscape(textAssertion)}]`\n }\n }\n\n // Add side bias if specified\n if (options.includeSideBias) {\n const sideBias = options.includeSideBias === \"before\" ? \"b\" : \"a\"\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n // If we have a text assertion, modify it to include side bias\n relativePath = relativePath.replace(/\\]$/, `;s=${sideBias}]`)\n } else {\n // Otherwise add a separate side bias\n relativePath += `[;s=${sideBias}]`\n }\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\n/**\n * Generate a range CFI between two points in the document\n */\nfunction generateRange(\n startNode: Node,\n startOffset: number,\n endNode: Node,\n endOffset: number,\n options: GenerateOptions = {},\n startPosition?: CfiPosition,\n endPosition?: CfiPosition,\n): string {\n // Find common ancestor\n const ancestor = findCommonAncestor(startNode, endNode)\n if (!ancestor) {\n throw new Error(\"No common ancestor found\")\n }\n\n // Generate CFI from ancestor to document\n const ancestorCfi = generatePoint(ancestor, undefined, options)\n\n // Generate path from ancestor to start node\n const startPath = generateRelativePath(\n ancestor,\n startNode,\n startOffset,\n options,\n startPosition,\n )\n\n // Generate path from ancestor to end node\n const endPath = generateRelativePath(\n ancestor,\n endNode,\n endOffset,\n options,\n endPosition,\n )\n\n // For range CFIs, add extensions to each part separately\n if (options.extensions && Object.keys(options.extensions).length > 0) {\n // Format: epubcfi(/ancestor,/start[;extensions],/end[;extensions])\n const extensionString = Object.entries(options.extensions)\n .map(([key, value]) => `${key}=${encodeURIComponent(cfiEscape(value))}`)\n .join(\";\")\n\n // Check if start/end paths already have extensions or brackets\n const startWithExt = startPath.includes(\"[\")\n ? startPath.replace(/\\]$/, `;${extensionString}]`)\n : `${startPath}[;${extensionString}]`\n\n const endWithExt = endPath.includes(\"[\")\n ? endPath.replace(/\\]$/, `;${extensionString}]`)\n : `${endPath}[;${extensionString}]`\n\n return `${ancestorCfi},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\n/**\n * Unified generate function that can handle both single positions and ranges\n *\n * @example\n * // Generate CFI for a single node\n * const cfi = generate(node);\n *\n * @example\n * // Generate CFI for a text node with offset\n * const cfi = generate({ node: textNode, offset: 5 });\n *\n * @example\n * // Generate CFI for a video with temporal offset\n * const cfi = generate({ node: videoElement, temporal: 45.5 });\n *\n * @example\n * // Generate CFI for an image with spatial coordinates\n * const cfi = generate({ node: imageElement, spatial: [50, 75] });\n *\n * @example\n * // Generate a range CFI\n * const cfi = generate({\n * start: { node: startNode, offset: 0 },\n * end: { node: endNode, offset: 10 }\n * });\n *\n * @example\n * // Generate a robust CFI with text assertions\n * const cfi = generate(node, {\n * includeTextAssertions: true,\n * textAssertionLength: 15\n * });\n */\nexport function generate(\n position: Node | CfiPosition | { start: CfiPosition; end: CfiPosition },\n options: GenerateOptions = {},\n): string {\n // Case 1: Simple Node\n if (isNode(position)) {\n return `epubcfi(${generatePoint(position, undefined, options)})`\n }\n\n // Case 2: CfiPosition (node + optional offset/temporal/spatial)\n if (\"node\" in position && !(\"start\" in position)) {\n return `epubcfi(${generatePoint(\n position.node,\n position.offset,\n options,\n position,\n )})`\n }\n\n // Case 3: Range (start + end positions)\n if (\"start\" in position && \"end\" in position) {\n const { start, end } = position\n return `epubcfi(${generateRange(\n start.node,\n start.offset ?? 0,\n end.node,\n end.offset ?? 0,\n options,\n start,\n end,\n )})`\n }\n\n throw new Error(\n \"Invalid argument: expected Node, CfiPosition, or {start, end} object\",\n )\n}\n","import { type CfiPart, type ParsedCfi, parse } from \"./parse\"\n\n/**\n * Collapses a parsed CFI to a single path (private helper for compare)\n * @param parsed The parsed CFI to collapse\n * @param toEnd Whether to collapse to the end of a range\n * @returns A collapsed CFI\n */\nfunction collapse(parsed: ParsedCfi, toEnd = false): CfiPart[][] {\n if (typeof parsed === \"string\") {\n return collapse(parse(parsed), toEnd)\n }\n\n if (\"parent\" in parsed) {\n // It's a range\n if (toEnd) {\n return parsed.parent.concat(parsed.end)\n }\n return parsed.parent.concat(parsed.start)\n }\n\n // It's a single CFI\n return parsed\n}\n\n/**\n * Get the weight of a step type for sorting\n * According to rule 9: character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n */\nfunction getStepTypeWeight(part: CfiPart): number {\n if (part.offset !== undefined) return 1 // character offset (:)\n if (part.index !== undefined) return 2 // child (/)\n if (part.temporal !== undefined || part.spatial !== undefined) return 3 // temporal-spatial (~ or @)\n return 4 // reference (!)\n}\n\n/**\n * Compare two CFIs according to the EPUB CFI specification sorting rules (section 3.2)\n * @param a The first CFI\n * @param b The second CFI\n * @returns -1 if a < b, 0 if a = b, 1 if a > b\n */\nexport function compare(a: ParsedCfi | string, b: ParsedCfi | string): number {\n const aParsed = typeof a === \"string\" ? parse(a) : a\n const bParsed = typeof b === \"string\" ? parse(b) : b\n\n if (\"parent\" in aParsed || \"parent\" in bParsed) {\n // At least one is a range\n return (\n compare(collapse(aParsed), collapse(bParsed)) ||\n compare(collapse(aParsed, true), collapse(bParsed, true))\n )\n }\n\n // Both are single CFIs\n for (let i = 0; i < Math.max(aParsed.length, bParsed.length); i++) {\n const p = aParsed[i] || []\n const q = bParsed[i] || []\n const maxIndex = Math.max(p.length, q.length) - 1\n\n for (let i = 0; i <= maxIndex; i++) {\n const x = p[i]\n const y = q[i]\n\n if (!x) return -1\n if (!y) return 1\n\n // Compare step types (rule 9)\n // character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n const xStepType = getStepTypeWeight(x)\n const yStepType = getStepTypeWeight(y)\n\n if (xStepType !== yStepType) {\n return xStepType - yStepType\n }\n\n // Compare element indices (rule 3 & 4)\n if (x.index > y.index) return 1\n if (x.index < y.index) return -1\n\n // Compare temporal positions (rule 7 & 8)\n // Temporal is more important than spatial\n const xTemporal = x.temporal !== undefined\n const yTemporal = y.temporal !== undefined\n\n if (xTemporal && !yTemporal) return 1\n if (!xTemporal && yTemporal) return -1\n\n if (xTemporal && yTemporal) {\n if ((x.temporal ?? 0) > (y.temporal ?? 0)) return 1\n if ((x.temporal ?? 0) < (y.temporal ?? 0)) return -1\n }\n\n // Compare spatial positions (rule 5 & 6)\n const xSpatial = x.spatial !== undefined\n const ySpatial = y.spatial !== undefined\n\n if (xSpatial && !ySpatial) return 1\n if (!xSpatial && ySpatial) return -1\n\n if (xSpatial && ySpatial) {\n // Y position is more important than X (rule 5)\n const xY = x.spatial?.[1] ?? 0\n const yY = y.spatial?.[1] ?? 0\n\n if (xY > yY) return 1\n if (xY < yY) return -1\n\n // Compare X positions if Y positions are equal\n const xX = x.spatial?.[0] ?? 0\n const yX = y.spatial?.[0] ?? 0\n\n if (xX > yX) return 1\n if (xX < yX) return -1\n }\n\n // Last part comparison including character offsets\n if (i === maxIndex) {\n if ((x.offset ?? 0) > (y.offset ?? 0)) return 1\n if ((x.offset ?? 0) < (y.offset ?? 0)) return -1\n }\n }\n }\n\n return 0\n}\n","import type { CfiPart, ParsedCfi } from \"./parse\"\nimport { cfiEscape } from \"./utils\"\n\n/**\n * Serialize a single CFI part into a string\n * @param part The CFI part to serialize\n * @returns The serialized string representation\n */\nfunction serializePart(part: CfiPart): string {\n let result = `/${part.index}`\n\n // Handle ID assertion first if present\n if (part.id) {\n result += `[${cfiEscape(part.id)}`\n // Add extensions inside ID brackets if present\n if (part.extensions) {\n for (const [key, value] of Object.entries(part.extensions)) {\n result += `;${key}=${cfiEscape(value)}`\n }\n }\n result += `]`\n }\n\n // Handle character offset\n if (part.offset !== undefined) {\n result += `:${part.offset}`\n }\n\n // Handle temporal offset\n if (part.temporal !== undefined) {\n result += `~${part.temporal}`\n }\n\n // Handle spatial offset\n if (part.spatial && part.spatial.length > 0) {\n result += `@${part.spatial.join(\":\")}`\n }\n\n // Handle text assertions and side bias in brackets\n const inBrackets: string[] = []\n\n // Handle text assertions\n console.log(part.text)\n if (part.text && part.text.length > 0) {\n inBrackets.push(part.text.map(cfiEscape).join(\",\"))\n }\n\n // Handle side bias and extensions in brackets\n if (part.side || (part.extensions && !part.id)) {\n if (part.side) {\n inBrackets.push(`;s=${part.side}`)\n }\n if (part.extensions && !part.id) {\n for (const [key, value] of Object.entries(part.extensions)) {\n inBrackets.push(`;${key}=${cfiEscape(value)}`)\n }\n }\n }\n\n // Add bracketed attributes if any\n if (inBrackets.length > 0) {\n result += `[${inBrackets.join(\"\")}]`\n }\n\n return result\n}\n\n/**\n * Serialize a single CFI path into a string\n * @param path The CFI path to serialize\n * @returns The serialized string representation\n */\nfunction serializePath(path: CfiPart[]): string {\n return path.map((part) => serializePart(part)).join(\"\")\n}\n\n/**\n * Serialize a parsed CFI into a string\n * @param parsed The parsed CFI to serialize\n * @returns The serialized CFI string\n */\nexport function serialize(parsed: ParsedCfi): string {\n if (Array.isArray(parsed)) {\n // Handle simple CFI or CFI with indirections\n return `epubcfi(${parsed.map(serializePath).join(\"!\")})`\n }\n\n // Handle CFI range\n const parent = parsed.parent.map(serializePath).join(\"!\")\n const start = parsed.start.map(serializePath).join(\"!\")\n const end = parsed.end.map(serializePath).join(\"!\")\n return `epubcfi(${parent},${start},${end})`\n}\n"],"names":["getAncestors","node","ancestors","current","findCommonAncestor","nodeA","nodeB","ancestorsA","ancestorsSet","CFI_SPECIAL_CHARS","cfiEscape","str","isCFI","isElement","isNode","isTextNode","unwrapCfi","cfi","match","tokenize","tokens","state","isEscaped","value","push","token","cat","unwrappedCfi","chars","i","char","findTokenIndices","type","splitAt","arr","indices","result","start","index","parsePart","_a","parts","pathStepTokens","currentPathStep","val","stepIndex","currentPart","stepsTokens","paramName","looksLikeId","part","parseIndirection","indirectionIndices","parse","commaIndices","parentTokens","startTokens","endTokens","isParsedCfiRange","parsed","resolve","document","options","resolveParsed","parsedCfi","resolveRange","error","asRange","firstPath","resolvePath","range","parentPath","startPath","endPath","parentNode","parentResult","startResult","endResult","startNode","endNode","domRange","extractSideBias","text","sideBiasMatch","isTextNodeStep","resolveExtensions","lastPart","lastPartPath","path","throwOnError","nodeById","remainingPathIndex","findNodeById","childIndex","childNode","sideBias","extensions","offset","currentNode","startIndex","nodeIndex","childElements","nextNode","extractTextAssertion","textNode","textContent","maxLength","halfLength","end","addExtensions","extensionParts","key","generatePoint","position","parentId","parentSiblings","n","textAssertion","siblings","elementsBefore","elementIndex","step","temporal","spatial","x","y","safeX","safeY","generateRelativePath","fromNode","toNode","relativePath","generateRange","startOffset","endOffset","startPosition","endPosition","ancestor","ancestorCfi","extensionString","startWithExt","endWithExt","generate","collapse","toEnd","getStepTypeWeight","compare","a","b","_b","_c","_d","aParsed","bParsed","p","q","maxIndex","xStepType","yStepType","xTemporal","yTemporal","xSpatial","ySpatial","xY","yY","xX","yX","serializePart","inBrackets","serializePath","serialize","parent"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(p,T){typeof exports=="object"&&typeof module<"u"?T(exports):typeof define=="function"&&define.amd?define(["exports"],T):(p=typeof globalThis<"u"?globalThis:p||self,T(p["prose-reader-cfi"]={}))})(this,function(p){"use strict";function T(e){const t=[e];let n=e;for(;n.parentNode;)t.push(n.parentNode),n=n.parentNode;return t}function K(e,t){if(e===t)return e;const n=T(e),r=new Set(n);let i=t;for(;i;){if(r.has(i))return i;i=i.parentNode}return null}const Q=/[\[\]\^,();]/g;function N(e){return e.replace(Q,"^$&")}const V=/^epubcfi\((.*)\)$/,R=e=>e.nodeType===Node.ELEMENT_NODE,M=e=>typeof e=="object"&&e!==null&&"nodeType"in e&&(e.nodeType===Node.ELEMENT_NODE||e.nodeType===Node.TEXT_NODE),F=e=>e.nodeType===Node.TEXT_NODE;function Z(e){const t=e.match(V);return t&&t[1]||e}function P(e){const t=[];let n=null,r=!1,i="";const o=c=>{t.push(c),n=null,i=""},l=c=>{i+=c,r=!1},a=Z(e).trim(),h=Array.from(a).concat("");for(let c=0;c<h.length;c++){const f=h[c];if(!f){n==="/"||n===":"?o([n,parseInt(i,10)]):n==="~"?o(["~",parseFloat(i)]):n==="@"?o(["@",parseFloat(i)]):n==="["?o(["[",i]):(n===";"||n!=null&&n.startsWith(";"))&&o([n,i]);break}if(f==="^"&&!r){r=!0;continue}if(n==="!")o(["!",0]);else if(n===",")o([",",0]);else if(n==="/"||n===":"){if(/^\d$/.test(f)){l(f);continue}o([n,parseInt(i,10)])}else if(n==="~"){if(/^\d$/.test(f)||f==="."){l(f);continue}o(["~",parseFloat(i)])}else if(n==="@"){if(f===":"){o(["@",parseFloat(i)]),n="@";continue}if(/^\d$/.test(f)||f==="."){l(f);continue}o(["@",parseFloat(i)])}else if(n==="[")if(f===";"&&!r)o(["[",i]),n=";";else if(f==="]"&&!r)o(["[",i]);else{l(f);continue}else if(n===";")if(f==="="&&!r)n=`;${i}`,i="";else if(f===";"&&!r)o([n,i]),n=";";else if(f==="]"&&!r)o([n,i]);else{l(f);continue}else if(n!=null&&n.startsWith(";"))if(f===";"&&!r)o([n,i]),n=";";else if(f==="]"&&!r)o([n,i]);else{l(f);continue}else n===null&&f===";"&&(n=";");(f==="/"||f===":"||f==="~"||f==="@"||f==="["||f==="!"||f===",")&&(n=f)}return t}function _(e,t){return e?e.map((n,r)=>n[0]===t?r:null).filter(n=>n!==null):[]}function D(e,t){const n=[];let r=0;for(const i of t)n.push(e.slice(r,i)),r=i;return n.push(e.slice(r)),n}function ee(e){var i;const t=[],n={};let r=-1;for(let o=0;o<e.length;o++){const l=e[o];if(!l)continue;const[a,h]=l;a==="/"?(r++,t[r]={index:h},n[r]=[]):r>=0&&((i=n[r])==null||i.push(l))}for(let o=0;o<t.length;o++){const l=t[o];if(!l)continue;const a=n[o]||[];for(let h=0;h<a.length;h++){const c=a[h];if(!c)continue;const[f,d]=c;if(f===":")l.offset=d;else if(f==="~")l.temporal=d;else if(f==="@")l.spatial=(l.spatial||[]).concat(d);else if(f===";s")l.side=d;else if(f.startsWith(";")&&f!==";s"){const g=f.substring(1);l.extensions||(l.extensions={}),l.extensions[g]=d}else if(f==="["){const g=typeof d=="string"&&!d.includes(" ")&&d.length<50;if(h===0&&g&&!l.id)l.id=d;else if(d!==""){const s=d.split(",").map(u=>u.trim());l.text=s}}}}return t}function A(e){const t=_(e,"!");return D(e,t).map(ee)}function v(e){if(!e)throw new Error("CFI string cannot be empty");const t=P(e);if(!t||t.length===0)throw new Error("Failed to tokenize CFI string");const n=_(t,",");if(n.length===0)return A(t);const[r,i,o]=D(t,n);return{parent:A(r||[]),start:A(i||[]),end:A(o||[])}}function k(e){return e!==null&&typeof e=="object"&&"parent"in e&&"start"in e&&"end"in e}function ne(e,t,n={}){try{if(typeof e!="string")return L(e,t,n);const r=v(e);return n.asRange&&k(r)?X(r,t):L(r,t,n)}catch(r){if(n.throwOnError)throw r;return{node:null,isRange:!1}}}function L(e,t,n={asRange:!1}){const{asRange:r=!1}=n;if(k(e)){if(r)return X(e,t);const i=e.start[0]||[];return w(i,t,n)}if(e[0])return w(e[0],t,n);throw new Error("Invalid CFI structure")}function X(e,t){const n=e.parent[0]||[],r=e.start[0]||[],i=e.end[0]||[];let o=t.documentElement;if(n.length>0){const d=w(n,t);M(d.node)&&(o=d.node)}if(!o)throw new Error("Failed to resolve parent node in CFI range");const l=w(r,t),a=w(i,t);if(!M(l.node)||!M(a.node))throw new Error("Failed to resolve start or end node in CFI range");const h=l.node,c=a.node,f=t.createRange();return f.setStart(h,(Array.isArray(l.offset)?l.offset[0]:l.offset)??0),f.setEnd(c,(Array.isArray(a.offset)?a.offset[0]:a.offset)??0),{node:f,isRange:!0,offset:l.offset,temporal:l.temporal,spatial:l.spatial,side:l.side,extensions:l.extensions}}function b(e){if(e!=null&&e.side)return e.side;if(e!=null&&e.text&&e.text.length>0){const t=e.text[0];if(t){const n=t.match(/^([ab])$/);if(n)return n[1]}}}function C(e){return e.index%2!==0}function te(e){const n=(k(e)?e.end:e).at(-1),r=n==null?void 0:n.at(-1);return r==null?void 0:r.extensions}function w(e,t,n={}){const{throwOnError:r=!1,asRange:i=!1}=n;if(!t){if(r)throw new Error("Document is not available");return{node:null,isRange:!1}}const{node:o,remainingPathIndex:l}=ie(t,e);if(o&&l>=e.length){const s=e.at(-1);if(s&&C(s)){const $=s.index-1;if($>=0&&$<o.childNodes.length){const m=o.childNodes[$],E=b(s),y=s==null?void 0:s.extensions;return{node:m,isRange:!1,offset:s==null?void 0:s.offset,temporal:s==null?void 0:s.temporal,spatial:s==null?void 0:s.spatial,side:E,extensions:y}}}const u=b(s),x=s==null?void 0:s.extensions;if(i){const $=t.createRange();if($.selectNodeContents(o),(s==null?void 0:s.offset)!==void 0){const m=Array.isArray(s.offset)?s.offset[0]:s.offset;F(o)&&$.setStart(o,m||0)}return $.setEnd(o,(s==null?void 0:s.offset)||0),{node:$,isRange:!0,offset:s==null?void 0:s.offset,temporal:s==null?void 0:s.temporal,spatial:s==null?void 0:s.spatial,side:u,extensions:x}}return{node:o,isRange:!1,offset:s==null?void 0:s.offset,temporal:s==null?void 0:s.temporal,spatial:s==null?void 0:s.spatial,side:u,extensions:x}}let a=o||t.documentElement;const h=o?l:0;if(i&&e.length>0){const s=e[e.length-1];if(s&&!C(s)){if(s.index===0&&a){const x=t.createRange();return x.setStart(a,0),x.setEnd(a,0),{node:x,isRange:!0,offset:s.offset,temporal:s.temporal,spatial:s.spatial,side:b(s),extensions:s.extensions}}let u=a;for(let x=h;x<e.length-1;x++){const $=e[x];if(!u||!$)break;if(C($)){const m=$.index-1;if(m>=0&&m<u.childNodes.length)u=u.childNodes[m];else{u=null;break}}else{const m=Array.from(u.childNodes).filter(y=>y.nodeType===Node.ELEMENT_NODE),E=Math.floor($.index/2)-1;if(E>=0&&E<m.length){const y=m[E];if(y)u=y;else{u=null;break}}else{u=null;break}}}if(u){const x=Array.from(u.childNodes).filter(m=>m.nodeType===Node.ELEMENT_NODE);if(Math.floor(s.index/2)-1===x.length){const m=t.createRange();return m.selectNodeContents(u),m.collapse(!1),{node:m,isRange:!0,offset:s.offset,temporal:s.temporal,spatial:s.spatial,side:b(s),extensions:s.extensions}}}}}for(let s=h;s<e.length;s++){const u=e[s];if(!a||!u)break;if(C(u)){const x=u.index-1;if(x>=0&&x<a.childNodes.length)a=a.childNodes[x];else{if(r)throw new Error(`Invalid text node index: ${u.index}`);a=null;break}}else{const x=Array.from(a.childNodes).filter(m=>m.nodeType===Node.ELEMENT_NODE),$=Math.floor(u.index/2)-1;if($>=0&&$<x.length){const m=x[$];m&&(a=m)}else{if(r)throw new Error(`Invalid element step index: ${u.index}`);a=null;break}}}if(!a){if(r)throw new Error("Failed to resolve CFI path");return{node:null,isRange:!1}}const c=e.at(-1),f=b(c),d=c==null?void 0:c.extensions,g={node:a,isRange:!1,offset:c==null?void 0:c.offset,temporal:c==null?void 0:c.temporal,spatial:c==null?void 0:c.spatial,side:f,extensions:d};if(i){const s=t.createRange();if(s.selectNodeContents(a),(c==null?void 0:c.offset)!==void 0){const u=Array.isArray(c.offset)?c.offset[0]:c.offset;F(a)&&s.setStart(a,u||0)}return{...g,node:s,isRange:!0}}return g}function ie(e,t){for(let n=t.length-1;n>=0;n--){const r=t[n];if(r!=null&&r.id){const i=e.getElementById(r.id);if(i)return{node:i,remainingPathIndex:n+1}}}return{node:null,remainingPathIndex:0}}function W(e,t,n={}){if(!e.textContent||e.textContent.trim()==="")return null;const r=e.textContent,i=n.textAssertionLength||10;if(t!==void 0&&t<=r.length){const o=Math.floor(i/2),l=Math.max(0,t-o),a=Math.min(r.length,t+o);return r.substring(l,a)}return r.substring(0,Math.min(r.length,i))}function z(e,t){if(!t||Object.keys(t).length===0)return e;const n=Object.entries(t).map(([r,i])=>`${r}=${encodeURIComponent(N(i))}`);return e.endsWith("]")?`${e.substring(0,e.length-1)}[;${n.join(";")}]`:`${e}[;${n.join(";")}]`}function S(e,t,n={},r){var f;let i="",o=e,l=null;if(e.nodeType===Node.TEXT_NODE){l=e;const d=e.parentNode;if(!d)throw new Error("Text node doesn't have a parent");const s=Array.from(d.childNodes).indexOf(e);if(s===-1)throw new Error("Node not found in parent's children");if(i=`/${s+1}`,R(d)&&d.id){const u=d.id,x=Array.from(((f=d.parentNode)==null?void 0:f.childNodes)||[]);i=`/${x.slice(0,x.indexOf(d)+1).filter(E=>E.nodeType===Node.ELEMENT_NODE).length*2}[${N(u)}]${i}`,o=d.parentNode}else o=d}let a=null;for(l&&n.includeTextAssertions&&(a=W(l,t,n));o!=null&&o.parentNode;){if(!(l&&o===l.parentNode&&i.includes(`[${R(o)?o.id:""}]`))){const d=o.parentNode,g=Array.from(d.childNodes),s=g.indexOf(o);if(s===-1)throw new Error("Node not found in parent's children");const u=g.slice(0,s+1).filter(m=>m.nodeType===Node.ELEMENT_NODE);let x;o.nodeType,Node.ELEMENT_NODE,x=u.length;const $=x*2;R(o)&&o.id?i=`/${$}[${N(o.id)}]${i}`:i=`/${$}${i}`}if(o.parentNode.nodeName.toLowerCase()==="html")break;o=o.parentNode}t!==void 0&&(i+=`:${t}`);const h=r==null?void 0:r.temporal;h!==void 0&&(i+=`~${h}`);const c=(r==null?void 0:r.spatial)||n.spatialOffset;if(c!==void 0){const[d,g]=c,s=Math.max(0,Math.min(100,d)),u=Math.max(0,Math.min(100,g));i+=`@${s}:${u}`}if(a&&(i+=`[${N(a)}]`),n.includeSideBias){const d=n.includeSideBias==="before"?"b":"a";a?i=`${i.substring(0,i.length-1)};s=${d}]`:i+=`[;s=${d}]`}return i=z(i,n.extensions),i}function Y(e,t,n,r={},i){if(e===t){let h=n!==void 0?`:${n}`:"";if((i==null?void 0:i.temporal)!==void 0&&(h+=`~${i.temporal}`),i!=null&&i.spatial){const[c,f]=i.spatial,d=Math.max(0,Math.min(100,c)),g=Math.max(0,Math.min(100,f));i.temporal!==void 0?h+=`@${d}:${g}`:h+=`@${d}:${g}`}return h}const o=[];let l=t;for(;l&&l!==e;){const h=l.parentNode;if(!h)break;const c=h.childNodes;let f=-1;for(let g=0;g<c.length;g++)if(c[g]===l){f=g;break}if(f===-1)throw new Error("Node not found in parent's children");const d=f+1;R(l)&&l.id?o.unshift(`/${d}[${N(l.id)}]`):o.unshift(`/${d}`),l=h}let a=o.join("");if(n!==void 0&&(a+=`:${n}`),(i==null?void 0:i.temporal)!==void 0&&(a+=`~${i.temporal}`),i!=null&&i.spatial){const[h,c]=i.spatial,f=Math.max(0,Math.min(100,h)),d=Math.max(0,Math.min(100,c));a+=`@${f}:${d}`}if(r.includeTextAssertions&&t.nodeType===Node.TEXT_NODE){const h=W(t,n,r);h&&(a+=`[${N(h)}]`)}if(r.includeSideBias){const h=r.includeSideBias==="before"?"b":"a";r.includeTextAssertions&&t.nodeType===Node.TEXT_NODE?a=a.replace(/\]$/,`;s=${h}]`):a+=`[;s=${h}]`}return a=z(a,r.extensions),a}function se(e,t,n,r,i={},o,l){const a=K(e,n);if(!a)throw new Error("No common ancestor found");const h=S(a,void 0,i),c=Y(a,e,t,i,o),f=Y(a,n,r,i,l);if(i.extensions&&Object.keys(i.extensions).length>0){const d=Object.entries(i.extensions).map(([u,x])=>`${u}=${encodeURIComponent(N(x))}`).join(";"),g=c.includes("[")?c.replace(/\]$/,`;${d}]`):`${c}[;${d}]`,s=f.includes("[")?f.replace(/\]$/,`;${d}]`):`${f}[;${d}]`;return`${h},${g},${s}`}return`${h},${c},${f}`}function re(e,t={}){if(M(e))return`epubcfi(${S(e,void 0,t)})`;if("node"in e&&!("start"in e))return`epubcfi(${S(e.node,e.offset,t,e)})`;if("start"in e&&"end"in e){const{start:n,end:r}=e;return`epubcfi(${se(n.node,n.offset??0,r.node,r.offset??0,t,n,r)})`}throw new Error("Invalid argument: expected Node, CfiPosition, or {start, end} object")}function I(e,t=!1){return typeof e=="string"?I(v(e),t):"parent"in e?t?e.parent.concat(e.end):e.parent.concat(e.start):e}function U(e){return e.offset!==void 0?1:e.index!==void 0?2:e.temporal!==void 0||e.spatial!==void 0?3:4}function j(e,t){var i,o,l,a;const n=typeof e=="string"?v(e):e,r=typeof t=="string"?v(t):t;if("parent"in n||"parent"in r)return j(I(n),I(r))||j(I(n,!0),I(r,!0));for(let h=0;h<Math.max(n.length,r.length);h++){const c=n[h]||[],f=r[h]||[],d=Math.max(c.length,f.length)-1;for(let g=0;g<=d;g++){const s=c[g],u=f[g];if(!s)return-1;if(!u)return 1;const x=U(s),$=U(u);if(x!==$)return x-$;if(s.index>u.index)return 1;if(s.index<u.index)return-1;const m=s.temporal!==void 0,E=u.temporal!==void 0;if(m&&!E)return 1;if(!m&&E)return-1;if(m&&E){if((s.temporal??0)>(u.temporal??0))return 1;if((s.temporal??0)<(u.temporal??0))return-1}const y=s.spatial!==void 0,B=u.spatial!==void 0;if(y&&!B)return 1;if(!y&&B)return-1;if(y&&B){const q=((i=s.spatial)==null?void 0:i[1])??0,H=((o=u.spatial)==null?void 0:o[1])??0;if(q>H)return 1;if(q<H)return-1;const G=((l=s.spatial)==null?void 0:l[0])??0,J=((a=u.spatial)==null?void 0:a[0])??0;if(G>J)return 1;if(G<J)return-1}if(g===d){if((s.offset??0)>(u.offset??0))return 1;if((s.offset??0)<(u.offset??0))return-1}}}return 0}function oe(e){let t=`/${e.index}`;if(e.id){if(t+=`[${N(e.id)}`,e.extensions)for(const[r,i]of Object.entries(e.extensions))t+=`;${r}=${N(i)}`;t+="]"}e.offset!==void 0&&(t+=`:${e.offset}`),e.temporal!==void 0&&(t+=`~${e.temporal}`),e.spatial&&e.spatial.length>0&&(t+=`@${e.spatial.join(":")}`);const n=[];if(console.log(e.text),e.text&&e.text.length>0&&n.push(e.text.map(N).join(",")),(e.side||e.extensions&&!e.id)&&(e.side&&n.push(`;s=${e.side}`),e.extensions&&!e.id))for(const[r,i]of Object.entries(e.extensions))n.push(`;${r}=${N(i)}`);return n.length>0&&(t+=`[${n.join("")}]`),t}function O(e){return e.map(t=>oe(t)).join("")}function fe(e){if(Array.isArray(e))return`epubcfi(${e.map(O).join("!")})`;const t=e.parent.map(O).join("!"),n=e.start.map(O).join("!"),r=e.end.map(O).join("!");return`epubcfi(${t},${n},${r})`}p.compare=j,p.generate=re,p.isParsedCfiRange=k,p.parse=v,p.resolve=ne,p.resolveExtensions=te,p.serialize=fe,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})});
2
+ //# sourceMappingURL=index.umd.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.cjs","sources":["../src/utils.ts","../src/parse.ts","../src/resolve.ts","../src/generate.ts","../src/compare.ts","../src/serialize.ts"],"sourcesContent":["/**\n * Utility functions for EPUB CFI operations\n */\n\n/**\n * Get all ancestors of a node, including the node itself\n */\nexport function getAncestors(node: Node): Node[] {\n const ancestors: Node[] = [node]\n let current: Node | null = node\n\n while (current.parentNode) {\n ancestors.push(current.parentNode)\n current = current.parentNode\n }\n\n return ancestors\n}\n\n/**\n * Find the closest common ancestor of two nodes\n */\nexport function findCommonAncestor(nodeA: Node, nodeB: Node): Node | null {\n if (nodeA === nodeB) return nodeA\n\n const ancestorsA = getAncestors(nodeA)\n const ancestorsSet = new Set(ancestorsA)\n\n // Start with nodeB and traverse up until we find a common ancestor\n let current: Node | null = nodeB\n while (current) {\n if (ancestorsSet.has(current)) {\n return current\n }\n current = current.parentNode\n }\n\n return null\n}\n\n/**\n * Special characters in CFI that need to be escaped according to the spec\n * These are: [ ] ^ , ( ) ;\n */\nexport const CFI_SPECIAL_CHARS = /[\\[\\]\\^,();]/g\n\n/**\n * Escape special characters in a CFI string\n * @param str The string to escape\n * @returns The escaped string\n */\nexport function cfiEscape(str: string): string {\n return str.replace(CFI_SPECIAL_CHARS, `^$&`)\n}\n\n/**\n * Regular expression to check if a string is a valid CFI\n */\nexport const isCFI = /^epubcfi\\((.*)\\)$/\n\n/**\n * Wrap a CFI string in the epubcfi() function\n * @param cfi The CFI string to wrap\n * @returns The wrapped CFI string\n */\nexport function wrapCfi(cfi: string): string {\n return isCFI.test(cfi) ? cfi : `epubcfi(${cfi})`\n}\n\n/**\n * @important Make it non browser runtime specific\n */\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === Node.ELEMENT_NODE\n\n/**\n * @important Make it non browser runtime specific\n */\n// biome-ignore lint/suspicious/noExplicitAny: <explanation>\nexport const isNode = (node: any): node is Node =>\n typeof node === \"object\" &&\n node !== null &&\n \"nodeType\" in node &&\n (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE)\n\n/**\n * Check if a node is a text node\n */\nexport const isTextNode = (node: Node): boolean =>\n node.nodeType === Node.TEXT_NODE\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n * Based on the EPUB CFI 1.1 specification: https://idpf.org/epub/linking/cfi/epub-cfi.html\n */\n\nimport { isCFI } from \"./utils\"\n\n/**\n * Interface for a parsed CFI part\n *\n * According to the EPUB CFI 1.1 specification, each step in a CFI path can have\n * its own properties including ID assertions, character offsets, and extension parameters.\n * Extensions are parameters in the form of key-value pairs that can be attached to any\n * step in the CFI path to provide additional information or metadata about that specific step.\n */\nexport interface CfiPart {\n index: number\n id?: string\n offset?: number\n temporal?: number\n spatial?: number[]\n text?: string[]\n side?: string\n /**\n * Extension parameters for this CFI path step\n *\n * The EPUB CFI spec allows for extension parameters to be attached to any step in the path.\n * These are key-value pairs that provide additional information or metadata.\n * For example, in /6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[;vnd.example.param=value],\n * the extension parameter is \"vnd.example.param\" with value \"value\".\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Interface for a parsed CFI range\n */\nexport interface CfiRange {\n parent: CfiPart[][]\n start: CfiPart[][]\n end: CfiPart[][]\n}\n\n/**\n * Interface for a parsed CFI\n */\nexport type ParsedCfi = CfiPart[][] | CfiRange\n\n/**\n * Unwrap a CFI string from the epubcfi() function\n * @param cfi The CFI string to unwrap\n * @returns The unwrapped CFI string\n */\nexport function unwrapCfi(cfi: string): string {\n const match = cfi.match(isCFI)\n return match ? match[1] || cfi : cfi\n}\n\n/**\n * Token type for CFI parsing\n */\ntype CfiToken = [string, string | number]\n\n/**\n * Tokenize a CFI string into an array of tokens\n * @param cfi The CFI string to tokenize\n * @returns An array of tokens\n */\nfunction tokenize(cfi: string): CfiToken[] {\n const tokens: CfiToken[] = []\n let state: string | null = null\n let isEscaped = false\n let value = \"\"\n\n const push = (token: CfiToken) => {\n tokens.push(token)\n state = null\n value = \"\"\n }\n\n const cat = (c: string) => {\n value += c\n isEscaped = false\n }\n\n const unwrappedCfi = unwrapCfi(cfi).trim()\n const chars = Array.from(unwrappedCfi).concat(\"\")\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]\n\n if (!char) {\n // End of string, push any pending token\n if (state === \"/\" || state === \":\") {\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n push([\"[\", value])\n } else if (state === \";\" || state?.startsWith(\";\")) {\n push([state, value])\n }\n break\n }\n\n // Handle escape characters\n if (char === \"^\" && !isEscaped) {\n isEscaped = true\n continue\n }\n\n if (state === \"!\") {\n push([\"!\", 0])\n } else if (state === \",\") {\n push([\",\", 0])\n } else if (state === \"/\" || state === \":\") {\n if (/^\\d$/.test(char)) {\n cat(char)\n continue\n }\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n if (char === \":\") {\n push([\"@\", parseFloat(value)])\n state = \"@\"\n continue\n }\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n if (char === \";\" && !isEscaped) {\n push([\"[\", value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([\"[\", value])\n } else {\n cat(char)\n continue\n }\n } else if (state === \";\") {\n // Handle extension parameter key\n if (char === \"=\" && !isEscaped) {\n state = `;${value}`\n value = \"\"\n } else if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state?.startsWith(\";\")) {\n // Handle extension parameter value\n if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state === null && char === \";\") {\n // Handle standalone extension parameters (not inside brackets)\n state = \";\"\n }\n\n if (\n char === \"/\" ||\n char === \":\" ||\n char === \"~\" ||\n char === \"@\" ||\n char === \"[\" ||\n char === \"!\" ||\n char === \",\"\n ) {\n state = char\n }\n }\n\n return tokens\n}\n\n/**\n * Find indices of tokens with a specific type\n * @param tokens The tokens to search\n * @param type The type to find\n * @returns An array of indices\n */\nfunction findTokenIndices(\n tokens: CfiToken[] | undefined,\n type: string,\n): number[] {\n if (!tokens) {\n return []\n }\n\n return tokens\n .map((token, i) => (token[0] === type ? i : null))\n .filter((i): i is number => i !== null)\n}\n\n/**\n * Split an array at specific indices\n * @param arr The array to split\n * @param indices The indices to split at\n * @returns An array of arrays\n */\nfunction splitAt<T>(arr: T[], indices: number[]): T[][] {\n const result: T[][] = []\n let start = 0\n\n for (const index of indices) {\n result.push(arr.slice(start, index))\n start = index\n }\n\n result.push(arr.slice(start))\n return result\n}\n\n/**\n * Parse a single part of a CFI\n * @param tokens The tokens to parse\n * @returns An array of CFI parts\n */\nfunction parsePart(tokens: CfiToken[]): CfiPart[] {\n const parts: CfiPart[] = []\n\n // Group tokens by path step\n const pathStepTokens: { [key: number]: CfiToken[] } = {}\n let currentPathStep = -1\n\n // First pass: group tokens by path step\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \"/\") {\n currentPathStep++\n parts[currentPathStep] = { index: val as number }\n pathStepTokens[currentPathStep] = []\n } else if (currentPathStep >= 0) {\n pathStepTokens[currentPathStep]?.push(token)\n }\n }\n\n // Second pass: process tokens for each path step\n for (let stepIndex = 0; stepIndex < parts.length; stepIndex++) {\n const currentPart = parts[stepIndex]\n if (!currentPart) continue\n\n const stepsTokens = pathStepTokens[stepIndex] || []\n\n for (let i = 0; i < stepsTokens.length; i++) {\n const token = stepsTokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \":\") {\n currentPart.offset = val as number\n } else if (type === \"~\") {\n currentPart.temporal = val as number\n } else if (type === \"@\") {\n currentPart.spatial = (currentPart.spatial || []).concat(val as number)\n } else if (type === \";s\") {\n currentPart.side = val as string\n } else if (type.startsWith(\";\") && type !== \";s\") {\n // This is an extension parameter\n const paramName = type.substring(1)\n if (!currentPart.extensions) {\n currentPart.extensions = {}\n }\n currentPart.extensions[paramName] = val as string\n } else if (type === \"[\") {\n // Determine if this is an ID or text assertion\n const looksLikeId =\n typeof val === \"string\" && !val.includes(\" \") && val.length < 50\n\n if (i === 0 && looksLikeId && !currentPart.id) {\n currentPart.id = val as string\n } else {\n // Otherwise, it's a text assertion\n if (val !== \"\") {\n // Split on comma and trim each part\n const parts = (val as string).split(\",\").map((part) => part.trim())\n currentPart.text = parts\n }\n }\n }\n }\n }\n\n return parts\n}\n\n/**\n * Parse a CFI with indirections\n * @param tokens The tokens to parse\n * @returns An array of arrays of CFI parts\n */\nfunction parseIndirection(tokens: CfiToken[]): CfiPart[][] {\n const indirectionIndices = findTokenIndices(tokens, \"!\")\n return splitAt(tokens, indirectionIndices).map(parsePart)\n}\n\n/**\n * Parse a CFI string into a structured representation\n * @param cfi The CFI string to parse\n * @returns A parsed CFI\n */\nexport function parse(cfi: string): ParsedCfi {\n if (!cfi) {\n throw new Error(\"CFI string cannot be empty\")\n }\n\n const tokens = tokenize(cfi)\n if (!tokens || tokens.length === 0) {\n throw new Error(\"Failed to tokenize CFI string\")\n }\n\n const commaIndices = findTokenIndices(tokens, \",\")\n\n if (commaIndices.length === 0) {\n return parseIndirection(tokens)\n }\n\n const [parentTokens, startTokens, endTokens] = splitAt(tokens, commaIndices)\n\n return {\n parent: parseIndirection(parentTokens || []),\n start: parseIndirection(startTokens || []),\n end: parseIndirection(endTokens || []),\n }\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport { isNode, isTextNode } from \"./utils\"\n\n/**\n * Options for resolving a CFI\n */\ninterface ResolveOptions {\n /**\n * Whether to throw an error if the CFI cannot be resolved\n * @default false\n */\n throwOnError?: boolean\n\n /**\n * Whether to return a range instead of a single node\n * @default false\n */\n asRange?: boolean\n}\n\n/**\n * Result of resolving a CFI\n */\ninterface ResolveRangeResult extends ResolveResultBase {\n node: Range | null\n isRange: true\n}\n\ninterface ResolveResultBase {\n offset?: number[] | number\n\n /**\n * The temporal offset if applicable\n */\n temporal?: number\n\n /**\n * The spatial offset if applicable\n */\n spatial?: number[]\n\n /**\n * The side bias if applicable\n */\n side?: string\n\n /**\n * Any extension parameters in the CFI\n */\n extensions?: Record<string, string>\n}\n\ninterface ResolveNodeResult extends ResolveResultBase {\n node: Node | null\n isRange: false\n}\n\ntype ResolveResult = ResolveNodeResult | ResolveRangeResult\n\n/**\n * Check if a parsed CFI is a range\n */\nexport function isParsedCfiRange(parsed: ParsedCfi): parsed is CfiRange {\n return (\n parsed !== null &&\n typeof parsed === \"object\" &&\n \"parent\" in parsed &&\n \"start\" in parsed &&\n \"end\" in parsed\n )\n}\n\n/**\n * Resolves a CFI string to a DOM node or range\n */\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: Omit<ResolveOptions, \"asRange\"> & { asRange: true },\n): ResolveRangeResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: ResolveOptions,\n): ResolveResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n try {\n // If already parsed, use it directly\n if (typeof cfi !== \"string\") {\n return resolveParsed(cfi, document, options)\n }\n\n // Parse the CFI once and use the parsed object for all checks\n const parsedCfi = parse(cfi)\n\n // Handle range CFIs when asRange is true\n if (options.asRange && isParsedCfiRange(parsedCfi)) {\n return resolveRange(parsedCfi, document)\n }\n\n // For all other cases, use the parsed CFI\n return resolveParsed(parsedCfi, document, options)\n } catch (error) {\n if (options.throwOnError) {\n throw error\n }\n\n return { node: null, isRange: false }\n }\n}\n\n/**\n * Resolves a parsed CFI to a DOM node or range\n */\nfunction resolveParsed(\n parsed: ParsedCfi,\n document: Document,\n options: { asRange?: boolean } = { asRange: false },\n): ResolveResult {\n const { asRange = false } = options\n\n // Handle range CFIs\n if (isParsedCfiRange(parsed)) {\n if (asRange) {\n return resolveRange(parsed, document)\n }\n\n // If not as range, use the start point\n const firstPath = parsed.start[0] || []\n return resolvePath(firstPath, document, options)\n }\n\n // Handle path CFI (indirection)\n if (parsed[0]) {\n return resolvePath(parsed[0], document, options)\n }\n\n throw new Error(\"Invalid CFI structure\")\n}\n\n/**\n * Resolves a CFI range to a DOM range\n */\nfunction resolveRange(range: CfiRange, document: Document): ResolveResult {\n // Get the parent path and start/end paths\n const parentPath = range.parent[0] || []\n const startPath = range.start[0] || []\n const endPath = range.end[0] || []\n\n // Find the parent node\n let parentNode: Node | null = document.documentElement\n\n if (parentPath.length > 0) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // Find the start and end nodes\n const startResult = resolvePath(startPath, document)\n const endResult = resolvePath(endPath, document)\n\n // Check that we have valid Node objects\n if (!isNode(startResult.node) || !isNode(endResult.node)) {\n throw new Error(\"Failed to resolve start or end node in CFI range\")\n }\n\n const startNode = startResult.node\n const endNode = endResult.node\n\n const domRange = document.createRange()\n domRange.setStart(\n startNode,\n (Array.isArray(startResult.offset)\n ? startResult.offset[0]\n : startResult.offset) ?? 0,\n )\n domRange.setEnd(\n endNode,\n (Array.isArray(endResult.offset)\n ? endResult.offset[0]\n : endResult.offset) ?? 0,\n )\n\n return {\n node: domRange,\n isRange: true,\n offset: startResult.offset,\n temporal: startResult.temporal,\n spatial: startResult.spatial,\n side: startResult.side,\n extensions: startResult.extensions,\n }\n}\n\n/**\n * Extracts side bias from a CFI part\n */\nfunction extractSideBias(part: CfiPart | undefined): string | undefined {\n // Return the side if it exists\n if (part?.side) return part.side\n\n // Look for side bias in text assertions\n if (part?.text && part.text.length > 0) {\n const text = part.text[0]\n // The CFI spec says side bias can be a=after or b=before\n if (text) {\n const sideBiasMatch = text.match(/^([ab])$/)\n if (sideBiasMatch) {\n return sideBiasMatch[1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Determines if a step in a CFI path is for a text node\n * Text nodes have indices that are not doubled (odd numbers in CFI)\n */\nfunction isTextNodeStep(part: CfiPart): boolean {\n // Per the CFI spec, element indices are always even numbers\n // So if we have an odd number, it's likely a text node or other non-element node\n return part.index % 2 !== 0\n}\n\n/**\n * Returns the extensions from the last valid part of a parsed CFI\n */\nexport function resolveExtensions(parsedCfi: ParsedCfi) {\n const parts = isParsedCfiRange(parsedCfi) ? parsedCfi.end : parsedCfi\n const lastPart = parts.at(-1)\n const lastPartPath = lastPart?.at(-1)\n\n return lastPartPath?.extensions\n}\n\n/**\n * Resolves a CFI path to a DOM node\n */\nfunction resolvePath(\n path: CfiPart[],\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n const { throwOnError = false, asRange = false } = options\n\n if (!document) {\n if (throwOnError) {\n throw new Error(\"Document is not available\")\n }\n return { node: null, isRange: false }\n }\n\n // Look for an element with an ID first\n const { node: nodeById, remainingPathIndex } = findNodeById(document, path)\n\n // If there's no remaining path to process after the ID node, return the ID node\n if (nodeById && remainingPathIndex >= path.length) {\n const lastPart = path.at(-1)\n\n // Handle case where we have element with ID and we need to get to a text node child\n if (lastPart && isTextNodeStep(lastPart)) {\n // Text nodes use 1-based indexing without doubling\n const childIndex = lastPart.index - 1\n\n if (childIndex >= 0 && childIndex < nodeById.childNodes.length) {\n const childNode = nodeById.childNodes[childIndex] as Node\n\n const sideBias = extractSideBias(lastPart)\n const extensions = lastPart?.extensions\n\n return {\n node: childNode,\n isRange: false,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n }\n }\n\n // If no text node reference or it couldn't be found, use the node found by ID\n const sideBias = extractSideBias(lastPart)\n const extensions = lastPart?.extensions\n\n if (asRange) {\n // Create a range\n const range = document.createRange()\n range.selectNodeContents(nodeById)\n\n // Adjust for offset\n if (lastPart?.offset !== undefined) {\n const offset = Array.isArray(lastPart.offset)\n ? lastPart.offset[0]\n : lastPart.offset\n if (isTextNode(nodeById)) {\n range.setStart(nodeById, offset || 0)\n }\n }\n\n range.setEnd(nodeById, lastPart?.offset || 0)\n\n return {\n node: range,\n isRange: true,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n }\n\n return {\n node: nodeById,\n isRange: false,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n }\n\n // Start traversal from the ID node if found, otherwise start from the document root\n let currentNode: Node | null = nodeById || document.documentElement\n\n // Get the starting index for path traversal\n const startIndex = nodeById ? remainingPathIndex : 0\n\n // Special case: Virtual positions (before first element or after last element)\n // only applicable when asRange is true\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\n\n // Check if the last part might be a virtual position indicator\n if (lastPart && !isTextNodeStep(lastPart)) {\n // Handle position before first element (index 0)\n if (lastPart.index === 0 && currentNode) {\n const range = document.createRange()\n range.setStart(currentNode, 0)\n range.setEnd(currentNode, 0)\n\n return {\n node: range,\n isRange: true,\n offset: lastPart.offset,\n temporal: lastPart.temporal,\n spatial: lastPart.spatial,\n side: extractSideBias(lastPart),\n extensions: lastPart.extensions,\n }\n }\n\n // Parent node for navigation\n let parentNode: Node | null = currentNode\n\n // Navigate through all parts except the last one\n for (let i = startIndex; i < path.length - 1; i++) {\n const part = path[i]\n if (!parentNode || !part) break\n\n if (isTextNodeStep(part)) {\n const nodeIndex = part.index - 1\n if (nodeIndex >= 0 && nodeIndex < parentNode.childNodes.length) {\n parentNode = parentNode.childNodes[nodeIndex] as Node\n } else {\n parentNode = null\n break\n }\n } else {\n const childElements: Node[] = Array.from(\n parentNode.childNodes,\n ).filter((node) => node.nodeType === Node.ELEMENT_NODE)\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n parentNode = nextNode\n } else {\n parentNode = null\n break\n }\n } else {\n parentNode = null\n break\n }\n }\n }\n\n // If we still have a parent node, handle possible after-last-element position\n if (parentNode) {\n const childElements: Node[] = Array.from(parentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(lastPart.index / 2) - 1\n\n // If the index is equal to the number of child elements, it's a position after the last element\n if (index === childElements.length) {\n const range = document.createRange()\n range.selectNodeContents(parentNode)\n range.collapse(false) // Collapse to end\n\n return {\n node: range,\n isRange: true,\n offset: lastPart.offset,\n temporal: lastPart.temporal,\n spatial: lastPart.spatial,\n side: extractSideBias(lastPart),\n extensions: lastPart.extensions,\n }\n }\n }\n }\n }\n\n // For each part in the path\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!currentNode || !part) break\n\n // Handle text nodes differently than element nodes\n if (isTextNodeStep(part)) {\n // For text nodes, we use the raw index (minus 1 for 0-based indexing)\n const nodeIndex = part.index - 1\n\n if (nodeIndex >= 0 && nodeIndex < currentNode.childNodes.length) {\n // We know this is a valid index, so the child node must exist\n currentNode = currentNode.childNodes[nodeIndex] as Node\n } else {\n if (throwOnError) {\n throw new Error(`Invalid text node index: ${part.index}`)\n }\n currentNode = null\n break\n }\n } else {\n // For element nodes, we need to filter to just element nodes\n // Get child nodes and try to navigate to the right one\n const childElements: Node[] = Array.from(currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n\n // Calculate the actual index (CFI indices are 1-based and doubled for elements)\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n currentNode = nextNode\n }\n } else {\n if (throwOnError) {\n throw new Error(`Invalid element step index: ${part.index}`)\n }\n\n currentNode = null\n break\n }\n }\n }\n\n if (!currentNode) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n\n return { node: null, isRange: false }\n }\n\n // Prepare the result\n const lastPart = path.at(-1)\n const sideBias = extractSideBias(lastPart)\n const extensions = lastPart?.extensions\n\n const result: ResolveResult = {\n node: currentNode,\n isRange: false,\n offset: lastPart?.offset,\n temporal: lastPart?.temporal,\n spatial: lastPart?.spatial,\n side: sideBias,\n extensions,\n }\n\n if (asRange) {\n const range = document.createRange()\n range.selectNodeContents(currentNode)\n\n // Adjust for offset\n if (lastPart?.offset !== undefined) {\n const offset = Array.isArray(lastPart.offset)\n ? lastPart.offset[0]\n : lastPart.offset\n if (isTextNode(currentNode)) {\n range.setStart(currentNode, offset || 0)\n }\n }\n\n return {\n ...result,\n node: range,\n isRange: true,\n }\n }\n\n return result\n}\n\n/**\n * Find a node by ID from a CFI path.\n * Starting from the last part and working backwards.\n * Returns the node and the remaining path that needs to be processed.\n */\nfunction findNodeById(\n document: Document,\n parts: CfiPart[],\n): { node: Node | null; remainingPathIndex: number } {\n for (let i = parts.length - 1; i >= 0; i--) {\n const part = parts[i]\n if (part?.id) {\n const node = document.getElementById(part.id)\n if (node) return { node, remainingPathIndex: i + 1 }\n }\n }\n\n return { node: null, remainingPathIndex: 0 }\n}\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n */\n\nimport { cfiEscape, findCommonAncestor, isElement, isNode } from \"./utils\"\n\n/**\n * Options for generating CFIs\n */\nexport interface GenerateOptions {\n /**\n * Whether to include text assertions for more robust CFIs\n */\n includeTextAssertions?: boolean\n\n /**\n * The maximum length of text to use for text assertions\n * Default is 10 characters\n */\n textAssertionLength?: number\n\n /**\n * Whether to include a side bias assertion\n */\n includeSideBias?: \"before\" | \"after\"\n\n /**\n * Whether to include spatial coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatialOffset?: [number, number]\n\n /**\n * Extension parameters to include in the CFI\n * Keys should be parameter names, values should be the parameter values\n * Vendor-specific parameters should be prefixed with 'vnd.' followed by the vendor name\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Position in a document, consisting of a node and optional offset\n */\nexport interface CfiPosition {\n /**\n * The DOM node\n */\n node: Node\n\n /**\n * Character offset within the node (for text nodes)\n */\n offset?: number\n\n /**\n * Temporal position in seconds (for audio/video content)\n */\n temporal?: number\n\n /**\n * Spatial position as [x,y] coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatial?: [number, number]\n}\n\n/**\n * Extract a suitable text assertion from a text node\n */\nfunction extractTextAssertion(\n textNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n): string | null {\n if (!textNode.textContent || textNode.textContent.trim() === \"\") {\n return null\n }\n\n const textContent = textNode.textContent\n const maxLength = options.textAssertionLength || 10\n\n // If we have an offset, use the text around that position\n if (offset !== undefined && offset <= textContent.length) {\n // We'll take a portion before and after the offset\n const halfLength = Math.floor(maxLength / 2)\n const start = Math.max(0, offset - halfLength)\n const end = Math.min(textContent.length, offset + halfLength)\n return textContent.substring(start, end)\n }\n\n // Otherwise, just take the first part of the text\n return textContent.substring(0, Math.min(textContent.length, maxLength))\n}\n\n/**\n * Add extension parameters to a CFI path\n */\nfunction addExtensions(\n cfi: string,\n extensions?: Record<string, string>,\n): string {\n if (!extensions || Object.keys(extensions).length === 0) {\n return cfi\n }\n\n // Build the extension string\n const extensionParts = Object.entries(extensions).map(\n ([key, value]) => `${key}=${encodeURIComponent(cfiEscape(value))}`,\n )\n\n // If we're dealing with a bracket at end, insert our extensions before the closing bracket\n if (cfi.endsWith(\"]\")) {\n return `${cfi.substring(0, cfi.length - 1)}[;${extensionParts.join(\";\")}]`\n }\n\n // No bracket at the end - just add with new brackets\n return `${cfi}[;${extensionParts.join(\";\")}]`\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n let cfi = \"\"\n let currentNode: Node | null = node\n let textNode: Node | null = null\n\n // Handle text nodes specially\n if (node.nodeType === Node.TEXT_NODE) {\n // If this is a text node, we need to remember it for text assertions\n // but for path construction, we'll work with the parent\n textNode = node\n\n // Store the offset value for later\n const parentNode = node.parentNode\n if (!parentNode) {\n throw new Error(\"Text node doesn't have a parent\")\n }\n\n // Find position of text node among its parent's children\n const siblings = Array.from(parentNode.childNodes)\n const nodeIndex = siblings.indexOf(node as ChildNode)\n\n if (nodeIndex === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n // Add the text node reference to the parent element's CFI\n // Text nodes are referenced by their index + 1 (CFI is 1-based)\n cfi = `/${nodeIndex + 1}`\n\n // If the parent has an ID, include it in the path\n if (isElement(parentNode) && parentNode.id) {\n const parentId = parentNode.id\n // Find the parent's index in its parent's children\n const parentSiblings = Array.from(parentNode.parentNode?.childNodes || [])\n const elementsBefore = parentSiblings\n .slice(0, parentSiblings.indexOf(parentNode as ChildNode) + 1)\n .filter((n) => n.nodeType === Node.ELEMENT_NODE)\n\n const parentIndex = elementsBefore.length * 2\n\n cfi = `/${parentIndex}[${cfiEscape(parentId)}]${cfi}`\n currentNode = parentNode.parentNode\n } else {\n // Continue with the parent as our current node\n currentNode = parentNode\n }\n }\n\n // Set up text assertion if needed\n let textAssertion: string | null = null\n if (textNode && options.includeTextAssertions) {\n textAssertion = extractTextAssertion(textNode, offset, options)\n }\n\n // Build the CFI path from the current node up to the html element\n while (currentNode?.parentNode) {\n // Skip if we're a text node's parent that's already been handled specially\n if (\n !(\n textNode &&\n currentNode === textNode.parentNode &&\n cfi.includes(`[${isElement(currentNode) ? currentNode.id : \"\"}]`)\n )\n ) {\n const parentNode = currentNode.parentNode\n\n // Find index among parent's children\n const siblings = Array.from(parentNode.childNodes)\n const nodeIndex = siblings.indexOf(currentNode as ChildNode)\n\n if (nodeIndex === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n // Find position among element siblings for CFI (element nodes only)\n // For CFI, element references are even-numbered (per CFI spec)\n const elementsBefore = siblings\n .slice(0, nodeIndex + 1)\n .filter((n) => n.nodeType === Node.ELEMENT_NODE)\n\n // Find the position of the current node in element siblings\n let elementIndex: number\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n elementIndex = elementsBefore.length\n } else {\n // For non-element nodes, use the number of elements before it\n elementIndex = elementsBefore.length\n }\n\n // CFI is 1-based, then doubled for element nodes\n const step = elementIndex * 2\n\n // Add the node index to the CFI\n // If the node has an ID, add it to the CFI\n if (isElement(currentNode) && currentNode.id) {\n cfi = `/${step}[${cfiEscape(currentNode.id)}]${cfi}`\n } else {\n cfi = `/${step}${cfi}`\n }\n }\n\n // If we've reached the html element, stop traversing up\n if (currentNode.parentNode.nodeName.toLowerCase() === \"html\") {\n break\n }\n\n currentNode = currentNode.parentNode\n }\n\n // Add the character offset if provided\n if (offset !== undefined) {\n cfi += `:${offset}`\n }\n\n // Add temporal offset if provided (from position parameter)\n const temporal = position?.temporal\n if (temporal !== undefined) {\n cfi += `~${temporal}`\n }\n\n // Add spatial offset if provided (from position parameter or options)\n const spatial = position?.spatial || options.spatialOffset\n if (spatial !== undefined) {\n const [x, y] = spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n\n // Add the spatial offset regardless of temporal offset\n cfi += `@${safeX}:${safeY}`\n }\n\n // Add text assertions if available\n if (textAssertion) {\n cfi += `[${cfiEscape(textAssertion)}]`\n }\n\n // Add side bias if specified\n if (options.includeSideBias) {\n const sideBias = options.includeSideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n // If we don't have a text assertion, add the side bias directly\n cfi += `[;s=${sideBias}]`\n } else {\n // Otherwise, we need to modify the last text assertion\n // Remove the closing bracket and add the side bias\n cfi = `${cfi.substring(0, cfi.length - 1)};s=${sideBias}]`\n }\n }\n\n // Add extension parameters if specified\n cfi = addExtensions(cfi, options.extensions)\n\n return cfi\n}\n\n/**\n * Generate a relative path from one node to another\n */\nfunction generateRelativePath(\n fromNode: Node,\n toNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n if (fromNode === toNode) {\n let result = offset !== undefined ? `:${offset}` : \"\"\n\n // Add temporal offset if provided\n if (position?.temporal !== undefined) {\n result += `~${position.temporal}`\n }\n\n // Add spatial offset if provided\n if (position?.spatial) {\n const [x, y] = position.spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n\n // If we already have a temporal offset, don't add the @ symbol\n if (position.temporal !== undefined) {\n result += `@${safeX}:${safeY}`\n } else {\n result += `@${safeX}:${safeY}`\n }\n }\n\n return result\n }\n\n const path: string[] = []\n let currentNode: Node | null = toNode\n\n // Build path from toNode up to fromNode (exclusive)\n while (currentNode && currentNode !== fromNode) {\n const parentNode = currentNode.parentNode as Node | null\n if (!parentNode) break\n\n const siblings = parentNode.childNodes\n let index = -1\n\n for (let i = 0; i < siblings.length; i++) {\n if (siblings[i] === currentNode) {\n index = i\n break\n }\n }\n\n if (index === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n // Add the node index to the path\n const step = index + 1\n\n // If the node has an ID, add it\n if (isElement(currentNode) && currentNode.id) {\n path.unshift(`/${step}[${cfiEscape(currentNode.id)}]`)\n } else {\n path.unshift(`/${step}`)\n }\n\n currentNode = parentNode\n }\n\n let relativePath = path.join(\"\")\n\n // Add offset if specified\n if (offset !== undefined) {\n relativePath += `:${offset}`\n }\n\n // Add temporal offset if provided\n if (position?.temporal !== undefined) {\n relativePath += `~${position.temporal}`\n }\n\n // Add spatial offset if provided\n if (position?.spatial) {\n const [x, y] = position.spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n\n // Add the spatial offset regardless of temporal offset\n relativePath += `@${safeX}:${safeY}`\n }\n\n // Add text assertion if enabled\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n if (textAssertion) {\n relativePath += `[${cfiEscape(textAssertion)}]`\n }\n }\n\n // Add side bias if specified\n if (options.includeSideBias) {\n const sideBias = options.includeSideBias === \"before\" ? \"b\" : \"a\"\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n // If we have a text assertion, modify it to include side bias\n relativePath = relativePath.replace(/\\]$/, `;s=${sideBias}]`)\n } else {\n // Otherwise add a separate side bias\n relativePath += `[;s=${sideBias}]`\n }\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\n/**\n * Generate a range CFI between two points in the document\n */\nfunction generateRange(\n startNode: Node,\n startOffset: number,\n endNode: Node,\n endOffset: number,\n options: GenerateOptions = {},\n startPosition?: CfiPosition,\n endPosition?: CfiPosition,\n): string {\n // Find common ancestor\n const ancestor = findCommonAncestor(startNode, endNode)\n if (!ancestor) {\n throw new Error(\"No common ancestor found\")\n }\n\n // Generate CFI from ancestor to document\n const ancestorCfi = generatePoint(ancestor, undefined, options)\n\n // Generate path from ancestor to start node\n const startPath = generateRelativePath(\n ancestor,\n startNode,\n startOffset,\n options,\n startPosition,\n )\n\n // Generate path from ancestor to end node\n const endPath = generateRelativePath(\n ancestor,\n endNode,\n endOffset,\n options,\n endPosition,\n )\n\n // For range CFIs, add extensions to each part separately\n if (options.extensions && Object.keys(options.extensions).length > 0) {\n // Format: epubcfi(/ancestor,/start[;extensions],/end[;extensions])\n const extensionString = Object.entries(options.extensions)\n .map(([key, value]) => `${key}=${encodeURIComponent(cfiEscape(value))}`)\n .join(\";\")\n\n // Check if start/end paths already have extensions or brackets\n const startWithExt = startPath.includes(\"[\")\n ? startPath.replace(/\\]$/, `;${extensionString}]`)\n : `${startPath}[;${extensionString}]`\n\n const endWithExt = endPath.includes(\"[\")\n ? endPath.replace(/\\]$/, `;${extensionString}]`)\n : `${endPath}[;${extensionString}]`\n\n return `${ancestorCfi},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\n/**\n * Unified generate function that can handle both single positions and ranges\n *\n * @example\n * // Generate CFI for a single node\n * const cfi = generate(node);\n *\n * @example\n * // Generate CFI for a text node with offset\n * const cfi = generate({ node: textNode, offset: 5 });\n *\n * @example\n * // Generate CFI for a video with temporal offset\n * const cfi = generate({ node: videoElement, temporal: 45.5 });\n *\n * @example\n * // Generate CFI for an image with spatial coordinates\n * const cfi = generate({ node: imageElement, spatial: [50, 75] });\n *\n * @example\n * // Generate a range CFI\n * const cfi = generate({\n * start: { node: startNode, offset: 0 },\n * end: { node: endNode, offset: 10 }\n * });\n *\n * @example\n * // Generate a robust CFI with text assertions\n * const cfi = generate(node, {\n * includeTextAssertions: true,\n * textAssertionLength: 15\n * });\n */\nexport function generate(\n position: Node | CfiPosition | { start: CfiPosition; end: CfiPosition },\n options: GenerateOptions = {},\n): string {\n // Case 1: Simple Node\n if (isNode(position)) {\n return `epubcfi(${generatePoint(position, undefined, options)})`\n }\n\n // Case 2: CfiPosition (node + optional offset/temporal/spatial)\n if (\"node\" in position && !(\"start\" in position)) {\n return `epubcfi(${generatePoint(\n position.node,\n position.offset,\n options,\n position,\n )})`\n }\n\n // Case 3: Range (start + end positions)\n if (\"start\" in position && \"end\" in position) {\n const { start, end } = position\n return `epubcfi(${generateRange(\n start.node,\n start.offset ?? 0,\n end.node,\n end.offset ?? 0,\n options,\n start,\n end,\n )})`\n }\n\n throw new Error(\n \"Invalid argument: expected Node, CfiPosition, or {start, end} object\",\n )\n}\n","import { type CfiPart, type ParsedCfi, parse } from \"./parse\"\n\n/**\n * Collapses a parsed CFI to a single path (private helper for compare)\n * @param parsed The parsed CFI to collapse\n * @param toEnd Whether to collapse to the end of a range\n * @returns A collapsed CFI\n */\nfunction collapse(parsed: ParsedCfi, toEnd = false): CfiPart[][] {\n if (typeof parsed === \"string\") {\n return collapse(parse(parsed), toEnd)\n }\n\n if (\"parent\" in parsed) {\n // It's a range\n if (toEnd) {\n return parsed.parent.concat(parsed.end)\n }\n return parsed.parent.concat(parsed.start)\n }\n\n // It's a single CFI\n return parsed\n}\n\n/**\n * Get the weight of a step type for sorting\n * According to rule 9: character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n */\nfunction getStepTypeWeight(part: CfiPart): number {\n if (part.offset !== undefined) return 1 // character offset (:)\n if (part.index !== undefined) return 2 // child (/)\n if (part.temporal !== undefined || part.spatial !== undefined) return 3 // temporal-spatial (~ or @)\n return 4 // reference (!)\n}\n\n/**\n * Compare two CFIs according to the EPUB CFI specification sorting rules (section 3.2)\n * @param a The first CFI\n * @param b The second CFI\n * @returns -1 if a < b, 0 if a = b, 1 if a > b\n */\nexport function compare(a: ParsedCfi | string, b: ParsedCfi | string): number {\n const aParsed = typeof a === \"string\" ? parse(a) : a\n const bParsed = typeof b === \"string\" ? parse(b) : b\n\n if (\"parent\" in aParsed || \"parent\" in bParsed) {\n // At least one is a range\n return (\n compare(collapse(aParsed), collapse(bParsed)) ||\n compare(collapse(aParsed, true), collapse(bParsed, true))\n )\n }\n\n // Both are single CFIs\n for (let i = 0; i < Math.max(aParsed.length, bParsed.length); i++) {\n const p = aParsed[i] || []\n const q = bParsed[i] || []\n const maxIndex = Math.max(p.length, q.length) - 1\n\n for (let i = 0; i <= maxIndex; i++) {\n const x = p[i]\n const y = q[i]\n\n if (!x) return -1\n if (!y) return 1\n\n // Compare step types (rule 9)\n // character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n const xStepType = getStepTypeWeight(x)\n const yStepType = getStepTypeWeight(y)\n\n if (xStepType !== yStepType) {\n return xStepType - yStepType\n }\n\n // Compare element indices (rule 3 & 4)\n if (x.index > y.index) return 1\n if (x.index < y.index) return -1\n\n // Compare temporal positions (rule 7 & 8)\n // Temporal is more important than spatial\n const xTemporal = x.temporal !== undefined\n const yTemporal = y.temporal !== undefined\n\n if (xTemporal && !yTemporal) return 1\n if (!xTemporal && yTemporal) return -1\n\n if (xTemporal && yTemporal) {\n if ((x.temporal ?? 0) > (y.temporal ?? 0)) return 1\n if ((x.temporal ?? 0) < (y.temporal ?? 0)) return -1\n }\n\n // Compare spatial positions (rule 5 & 6)\n const xSpatial = x.spatial !== undefined\n const ySpatial = y.spatial !== undefined\n\n if (xSpatial && !ySpatial) return 1\n if (!xSpatial && ySpatial) return -1\n\n if (xSpatial && ySpatial) {\n // Y position is more important than X (rule 5)\n const xY = x.spatial?.[1] ?? 0\n const yY = y.spatial?.[1] ?? 0\n\n if (xY > yY) return 1\n if (xY < yY) return -1\n\n // Compare X positions if Y positions are equal\n const xX = x.spatial?.[0] ?? 0\n const yX = y.spatial?.[0] ?? 0\n\n if (xX > yX) return 1\n if (xX < yX) return -1\n }\n\n // Last part comparison including character offsets\n if (i === maxIndex) {\n if ((x.offset ?? 0) > (y.offset ?? 0)) return 1\n if ((x.offset ?? 0) < (y.offset ?? 0)) return -1\n }\n }\n }\n\n return 0\n}\n","import type { CfiPart, ParsedCfi } from \"./parse\"\nimport { cfiEscape } from \"./utils\"\n\n/**\n * Serialize a single CFI part into a string\n * @param part The CFI part to serialize\n * @returns The serialized string representation\n */\nfunction serializePart(part: CfiPart): string {\n let result = `/${part.index}`\n\n // Handle ID assertion first if present\n if (part.id) {\n result += `[${cfiEscape(part.id)}`\n // Add extensions inside ID brackets if present\n if (part.extensions) {\n for (const [key, value] of Object.entries(part.extensions)) {\n result += `;${key}=${cfiEscape(value)}`\n }\n }\n result += `]`\n }\n\n // Handle character offset\n if (part.offset !== undefined) {\n result += `:${part.offset}`\n }\n\n // Handle temporal offset\n if (part.temporal !== undefined) {\n result += `~${part.temporal}`\n }\n\n // Handle spatial offset\n if (part.spatial && part.spatial.length > 0) {\n result += `@${part.spatial.join(\":\")}`\n }\n\n // Handle text assertions and side bias in brackets\n const inBrackets: string[] = []\n\n // Handle text assertions\n console.log(part.text)\n if (part.text && part.text.length > 0) {\n inBrackets.push(part.text.map(cfiEscape).join(\",\"))\n }\n\n // Handle side bias and extensions in brackets\n if (part.side || (part.extensions && !part.id)) {\n if (part.side) {\n inBrackets.push(`;s=${part.side}`)\n }\n if (part.extensions && !part.id) {\n for (const [key, value] of Object.entries(part.extensions)) {\n inBrackets.push(`;${key}=${cfiEscape(value)}`)\n }\n }\n }\n\n // Add bracketed attributes if any\n if (inBrackets.length > 0) {\n result += `[${inBrackets.join(\"\")}]`\n }\n\n return result\n}\n\n/**\n * Serialize a single CFI path into a string\n * @param path The CFI path to serialize\n * @returns The serialized string representation\n */\nfunction serializePath(path: CfiPart[]): string {\n return path.map((part) => serializePart(part)).join(\"\")\n}\n\n/**\n * Serialize a parsed CFI into a string\n * @param parsed The parsed CFI to serialize\n * @returns The serialized CFI string\n */\nexport function serialize(parsed: ParsedCfi): string {\n if (Array.isArray(parsed)) {\n // Handle simple CFI or CFI with indirections\n return `epubcfi(${parsed.map(serializePath).join(\"!\")})`\n }\n\n // Handle CFI range\n const parent = parsed.parent.map(serializePath).join(\"!\")\n const start = parsed.start.map(serializePath).join(\"!\")\n const end = parsed.end.map(serializePath).join(\"!\")\n return `epubcfi(${parent},${start},${end})`\n}\n"],"names":["getAncestors","node","ancestors","current","findCommonAncestor","nodeA","nodeB","ancestorsA","ancestorsSet","CFI_SPECIAL_CHARS","cfiEscape","str","isCFI","isElement","isNode","isTextNode","unwrapCfi","cfi","match","tokenize","tokens","state","isEscaped","value","push","token","cat","unwrappedCfi","chars","i","char","findTokenIndices","type","splitAt","arr","indices","result","start","index","parsePart","parts","pathStepTokens","currentPathStep","val","_a","stepIndex","currentPart","stepsTokens","paramName","looksLikeId","part","parseIndirection","indirectionIndices","parse","commaIndices","parentTokens","startTokens","endTokens","isParsedCfiRange","parsed","resolve","document","options","resolveParsed","parsedCfi","resolveRange","error","asRange","firstPath","resolvePath","range","parentPath","startPath","endPath","parentNode","parentResult","startResult","endResult","startNode","endNode","domRange","extractSideBias","text","sideBiasMatch","isTextNodeStep","resolveExtensions","lastPart","lastPartPath","path","throwOnError","nodeById","remainingPathIndex","findNodeById","childIndex","childNode","sideBias","extensions","offset","currentNode","startIndex","nodeIndex","childElements","nextNode","extractTextAssertion","textNode","textContent","maxLength","halfLength","end","addExtensions","extensionParts","key","generatePoint","position","parentId","parentSiblings","n","textAssertion","siblings","elementsBefore","elementIndex","step","temporal","spatial","x","y","safeX","safeY","generateRelativePath","fromNode","toNode","relativePath","generateRange","startOffset","endOffset","startPosition","endPosition","ancestor","ancestorCfi","extensionString","startWithExt","endWithExt","generate","collapse","toEnd","getStepTypeWeight","compare","a","b","aParsed","bParsed","p","q","maxIndex","xStepType","yStepType","xTemporal","yTemporal","xSpatial","ySpatial","xY","yY","_b","xX","_c","yX","_d","serializePart","inBrackets","serializePath","serialize","parent"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export interface CfiPart {
2
+ index: number;
3
+ id?: string;
4
+ offset?: number;
5
+ temporal?: number;
6
+ spatial?: number[];
7
+ text?: string[];
8
+ side?: string;
9
+ extensions?: Record<string, string>;
10
+ }
11
+ export interface CfiRange {
12
+ parent: CfiPart[][];
13
+ start: CfiPart[][];
14
+ end: CfiPart[][];
15
+ }
16
+ export type ParsedCfi = CfiPart[][] | CfiRange;
17
+ export declare function unwrapCfi(cfi: string): string;
18
+ export declare function parse(cfi: string): ParsedCfi;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { CfiRange, ParsedCfi } from './parse';
2
+ interface ResolveOptions {
3
+ throwOnError?: boolean;
4
+ asRange?: boolean;
5
+ }
6
+ interface ResolveRangeResult extends ResolveResultBase {
7
+ node: Range | null;
8
+ isRange: true;
9
+ }
10
+ interface ResolveResultBase {
11
+ offset?: number[] | number;
12
+ temporal?: number;
13
+ spatial?: number[];
14
+ side?: string;
15
+ extensions?: Record<string, string>;
16
+ }
17
+ interface ResolveNodeResult extends ResolveResultBase {
18
+ node: Node | null;
19
+ isRange: false;
20
+ }
21
+ type ResolveResult = ResolveNodeResult | ResolveRangeResult;
22
+ export declare function isParsedCfiRange(parsed: ParsedCfi): parsed is CfiRange;
23
+ export declare function resolve(cfi: string | ParsedCfi, document: Document, options?: Omit<ResolveOptions, "asRange"> & {
24
+ asRange: true;
25
+ }): ResolveRangeResult;
26
+ export declare function resolve(cfi: string | ParsedCfi, document: Document, options?: ResolveOptions): ResolveResult;
27
+ export declare function resolveExtensions(parsedCfi: ParsedCfi): Record<string, string> | undefined;
28
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { ParsedCfi } from './parse';
2
+ export declare function serialize(parsed: ParsedCfi): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export declare function getAncestors(node: Node): Node[];
2
+ export declare function findCommonAncestor(nodeA: Node, nodeB: Node): Node | null;
3
+ export declare const CFI_SPECIAL_CHARS: RegExp;
4
+ export declare function cfiEscape(str: string): string;
5
+ export declare const isCFI: RegExp;
6
+ export declare function wrapCfi(cfi: string): string;
7
+ export declare const isElement: (node: Node) => node is Element;
8
+ export declare const isNode: (node: any) => node is Node;
9
+ export declare const isTextNode: (node: Node) => boolean;
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@prose-reader/cfi",
3
+ "version": "1.209.0",
4
+ "type": "module",
5
+ "files": ["/dist"],
6
+ "main": "./dist/index.umd.cjs",
7
+ "module": "./dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.umd.cjs"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "dev": "vite",
16
+ "start": "vite build --watch --mode development",
17
+ "build": "tsc && vite build",
18
+ "preview": "vite preview",
19
+ "test": "vitest run"
20
+ },
21
+ "devDependencies": {
22
+ "typescript": "*",
23
+ "vite": "^6.3.1"
24
+ },
25
+ "dependencies": {
26
+ "epub-cfi-resolver": "^1.0.2"
27
+ }
28
+ }