@prose-reader/cfi 1.250.0 → 1.252.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +20 -21
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +1 -1
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -22,12 +22,12 @@ function p(e) {
|
|
|
22
22
|
}
|
|
23
23
|
const J = /^epubcfi\((.*)\)$/, v = (e) => e.nodeType === Node.ELEMENT_NODE, P = (e) => typeof e == "object" && e !== null && "nodeType" in e && (e.nodeType === Node.ELEMENT_NODE || e.nodeType === Node.TEXT_NODE), K = (e) => e.nodeType === Node.TEXT_NODE;
|
|
24
24
|
function Q(e) {
|
|
25
|
-
if (
|
|
25
|
+
if (k(e))
|
|
26
26
|
return !1;
|
|
27
27
|
const n = e[e.length - 1];
|
|
28
28
|
return e.length > 1 && (n === void 0 || n.length === 0);
|
|
29
29
|
}
|
|
30
|
-
function
|
|
30
|
+
function k(e) {
|
|
31
31
|
return e !== null && typeof e == "object" && "parent" in e && "start" in e && "end" in e;
|
|
32
32
|
}
|
|
33
33
|
function Z(e) {
|
|
@@ -167,7 +167,7 @@ function A(e) {
|
|
|
167
167
|
const n = z(e, "!");
|
|
168
168
|
return Y(e, n).map(te);
|
|
169
169
|
}
|
|
170
|
-
function
|
|
170
|
+
function w(e) {
|
|
171
171
|
if (!e)
|
|
172
172
|
throw new Error("CFI string cannot be empty");
|
|
173
173
|
const n = ee(e);
|
|
@@ -199,7 +199,7 @@ function b(e) {
|
|
|
199
199
|
}
|
|
200
200
|
function ce(e, n, t = {}) {
|
|
201
201
|
try {
|
|
202
|
-
const i = typeof e != "string" ? e :
|
|
202
|
+
const i = typeof e != "string" ? e : w(e);
|
|
203
203
|
return ne(i, n, t);
|
|
204
204
|
} catch (i) {
|
|
205
205
|
if (t.throwOnError)
|
|
@@ -208,13 +208,13 @@ function ce(e, n, t = {}) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
function ne(e, n, t = { asRange: !1 }) {
|
|
211
|
-
if (
|
|
211
|
+
if (k(e))
|
|
212
212
|
return re(e, n);
|
|
213
213
|
if (Q(e))
|
|
214
214
|
return N(null);
|
|
215
215
|
const i = e.at(-1);
|
|
216
216
|
if (i)
|
|
217
|
-
return
|
|
217
|
+
return b(i, n, t);
|
|
218
218
|
throw new Error("Invalid CFI structure");
|
|
219
219
|
}
|
|
220
220
|
function re(e, n) {
|
|
@@ -224,20 +224,20 @@ function re(e, n) {
|
|
|
224
224
|
if (t.length > 1) {
|
|
225
225
|
const d = t[0];
|
|
226
226
|
if (d) {
|
|
227
|
-
const h =
|
|
227
|
+
const h = b(d, n);
|
|
228
228
|
P(h.node) && (s = h.node);
|
|
229
229
|
}
|
|
230
230
|
if (t.length > 1 && s) {
|
|
231
231
|
const h = t[1];
|
|
232
232
|
if (h) {
|
|
233
|
-
const g =
|
|
233
|
+
const g = b(h, n);
|
|
234
234
|
P(g.node) && (s = g.node);
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
} else {
|
|
238
238
|
const d = t[0];
|
|
239
239
|
if (d) {
|
|
240
|
-
const h =
|
|
240
|
+
const h = b(d, n);
|
|
241
241
|
P(h.node) && (s = h.node);
|
|
242
242
|
}
|
|
243
243
|
}
|
|
@@ -277,7 +277,7 @@ function re(e, n) {
|
|
|
277
277
|
}
|
|
278
278
|
const u = n.createRange();
|
|
279
279
|
return u.setStart(a, f), u.setEnd(c, o), {
|
|
280
|
-
...
|
|
280
|
+
...F(i[i.length - 1]),
|
|
281
281
|
node: u,
|
|
282
282
|
isRange: !0
|
|
283
283
|
};
|
|
@@ -297,9 +297,9 @@ function C(e) {
|
|
|
297
297
|
return e.index % 2 !== 0;
|
|
298
298
|
}
|
|
299
299
|
function de(e) {
|
|
300
|
-
return (
|
|
300
|
+
return (k(e) ? e.end : e).at(-1)?.at(-1)?.extensions;
|
|
301
301
|
}
|
|
302
|
-
function
|
|
302
|
+
function F(e) {
|
|
303
303
|
const n = ie(e), t = e?.extensions;
|
|
304
304
|
return {
|
|
305
305
|
offset: e?.offset,
|
|
@@ -313,14 +313,14 @@ function N(e, n) {
|
|
|
313
313
|
return {
|
|
314
314
|
node: e,
|
|
315
315
|
isRange: !1,
|
|
316
|
-
...
|
|
316
|
+
...F(n)
|
|
317
317
|
};
|
|
318
318
|
}
|
|
319
319
|
function I(e, n) {
|
|
320
320
|
return {
|
|
321
321
|
node: e,
|
|
322
322
|
isRange: !0,
|
|
323
|
-
...
|
|
323
|
+
...F(n)
|
|
324
324
|
};
|
|
325
325
|
}
|
|
326
326
|
function D(e, n, t) {
|
|
@@ -363,7 +363,7 @@ function O(e, n, t, i) {
|
|
|
363
363
|
}
|
|
364
364
|
return r;
|
|
365
365
|
}
|
|
366
|
-
function
|
|
366
|
+
function b(e, n, t = {}) {
|
|
367
367
|
const { throwOnError: i = !1, asRange: r = !1 } = t;
|
|
368
368
|
if (!n) {
|
|
369
369
|
if (i)
|
|
@@ -565,9 +565,8 @@ function ae(e, n, t, i, r = {}, s, l) {
|
|
|
565
565
|
return `${c},${f},${o}`;
|
|
566
566
|
}
|
|
567
567
|
const B = (e, n, t = {}, i) => {
|
|
568
|
-
const r = "";
|
|
569
568
|
let l = `/6/${(e + 1) * 2}`;
|
|
570
|
-
return n && (l += `[${p(n)}]`), l = i ? m(l, t.extensions) : l, `${l}
|
|
569
|
+
return n && (l += `[${p(n)}]`), l = i ? m(l, t.extensions) : l, `${l}!`;
|
|
571
570
|
};
|
|
572
571
|
function ue(e, n = {}) {
|
|
573
572
|
if (P(e))
|
|
@@ -602,13 +601,13 @@ function ue(e, n = {}) {
|
|
|
602
601
|
)), `epubcfi(${t})`;
|
|
603
602
|
}
|
|
604
603
|
function $(e, n = !1) {
|
|
605
|
-
return typeof e == "string" ? $(
|
|
604
|
+
return typeof e == "string" ? $(w(e), n) : "parent" in e ? n ? e.parent.concat(e.end) : e.parent.concat(e.start) : e;
|
|
606
605
|
}
|
|
607
606
|
function W(e) {
|
|
608
607
|
return e.offset !== void 0 ? 1 : e.index !== void 0 ? 2 : e.temporal !== void 0 || e.spatial !== void 0 ? 3 : 4;
|
|
609
608
|
}
|
|
610
609
|
function X(e, n) {
|
|
611
|
-
const t = typeof e == "string" ?
|
|
610
|
+
const t = typeof e == "string" ? w(e) : e, i = typeof n == "string" ? w(n) : n;
|
|
612
611
|
if ("parent" in t || "parent" in i)
|
|
613
612
|
return X($(t), $(i)) || X($(t, !0), $(i, !0));
|
|
614
613
|
for (let r = 0; r < Math.max(t.length, i.length); r++) {
|
|
@@ -676,8 +675,8 @@ export {
|
|
|
676
675
|
X as compare,
|
|
677
676
|
ue as generate,
|
|
678
677
|
Q as isIndirectionOnly,
|
|
679
|
-
|
|
680
|
-
|
|
678
|
+
k as isParsedCfiRange,
|
|
679
|
+
w as parse,
|
|
681
680
|
ce as resolve,
|
|
682
681
|
de as resolveExtensions,
|
|
683
682
|
he as serialize
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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":["import type { CfiRange, ParsedCfi } from \"./parse\"\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: TODO\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/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\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 * 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 } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\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\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 // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n isTextNode,\n} 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 * 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 const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\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 if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, 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 paths and start/end paths\n const parentPaths = range.parent\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 (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = parentNode.nodeType === Node.TEXT_NODE\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // If startPath/endPath are empty or only have offset, use parentNode directly\n const isStartOffsetOnly =\n startPath.length === 0 ||\n (startPath.length === 1 && typeof startPath[0]?.offset === \"number\")\n const isEndOffsetOnly =\n endPath.length === 0 ||\n (endPath.length === 1 && typeof endPath[0]?.offset === \"number\")\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n }\n }\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(startNode, startOffset)\n domRange.setEnd(endNode, endOffset)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\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\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isTextNode(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): Node | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isTextNodeStep(part)) {\n const nodeIndex = part.index - 1\n if (nodeIndex >= 0 && nodeIndex < _currentNode.childNodes.length) {\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 const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\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 _currentNode = null\n break\n }\n }\n }\n\n return _currentNode\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 createNodeResultObject(null)\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 if (lastPart && isTextNodeStep(lastPart)) {\n const childIndex = lastPart.index - 1\n if (childIndex >= 0 && childIndex < nodeById.childNodes.length) {\n const childNode = nodeById.childNodes[childIndex] as Node\n return createNodeResultObject(childNode, lastPart)\n }\n }\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\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 const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\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 return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )\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 return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n currentNode = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!currentNode) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n if (asRange) {\n const range = createRangeForNode(document, currentNode, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(currentNode, lastPart)\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 | null\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 * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\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 * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\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 return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\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 and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\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 result = addOffsets(result, position?.temporal, position?.spatial)\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 = Array.from(parentNode.childNodes)\n const index = siblings.indexOf(currentNode as ChildNode)\n\n if (index === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n let step: number\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n // Find index among element siblings\n const elementSiblings = siblings.filter(\n (n) => n.nodeType === Node.ELEMENT_NODE,\n )\n const elementIndex = elementSiblings.indexOf(currentNode as ChildNode)\n step = (elementIndex + 1) * 2\n } else {\n // For text nodes, use index among all child nodes (1-based, odd)\n step = index + 1\n }\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 and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\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 const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\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\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 // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\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 CFI with spine indirection\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\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 let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine indirection if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine indirection if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += 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 return `epubcfi(${cfi})`\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 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","isIndirectionOnly","parsed","isParsedCfiRange","lastPart","unwrapCfi","cfi","match","tokenize","tokens","state","isEscaped","value","push","token","cat","c","unwrappedCfi","chars","i","char","findTokenIndices","type","splitAt","arr","indices","result","start","index","parsePart","parts","pathStepTokens","currentPathStep","val","stepIndex","currentPart","stepsTokens","paramName","looksLikeId","part","parseIndirection","indirectionIndices","parse","commaIndices","parentTokens","startTokens","endTokens","attachOffsetToParent","parent","offsetTokens","filteredTokens","lastParentPath","offsetToken","pathClone","end","resolve","document","options","parsedCfi","resolveParsed","error","resolveRange","createNodeResultObject","nonIndirectionPart","resolvePath","range","parentPaths","startPath","endPath","parentNode","indirectionPath","indirectionResult","actualParentPath","actualParentResult","parentPath","parentResult","isParentTextNode","startNode","endNode","startOffset","endOffset","lastStartPart","lastEndPart","isStartOffsetOnly","isEndOffsetOnly","traversed","traverseNodePath","domRange","createBaseResultObject","extractSideBias","text","sideBiasMatch","isTextNodeStep","resolveExtensions","sideBias","extensions","createRangeResultObject","createRangeForNode","offset","offsetValue","currentNode","path","startIndex","throwOnError","_currentNode","nodeIndex","childElements","nextNode","asRange","nodeById","remainingPathIndex","findNodeById","childIndex","childNode","extractTextAssertion","textNode","textContent","maxLength","halfLength","formatSpatialOffset","spatial","x","y","safeX","safeY","addOffsets","temporal","addTextAssertionAndSideBias","textAssertion","sideBiasChar","generatePoint","position","parentId","parentSiblings","n","siblings","elementsBefore","elementIndex","step","addExtensions","generateRelativePath","fromNode","toNode","relativePath","serializeExtensions","key","escapedValue","extensionString","lastBracketIndex","content","generateRange","startPosition","endPosition","ancestor","ancestorCfi","ancestorWithExt","startWithExt","endWithExt","generateSpineCfi","spineIndex","spineId","isFinal","bracket","generate","collapse","toEnd","getStepTypeWeight","compare","a","b","aParsed","bParsed","p","q","maxIndex","xStepType","yStepType","xTemporal","yTemporal","xSpatial","ySpatial","xY","yY","xX","yX","serializePart","inBrackets","serializePath","serialize"],"mappings":"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"}
|
|
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":["import type { CfiRange, ParsedCfi } from \"./parse\"\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: TODO\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/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\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 * 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 } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\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\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 // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n isTextNode,\n} 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 * 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 const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\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 if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, 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 paths and start/end paths\n const parentPaths = range.parent\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 (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = parentNode.nodeType === Node.TEXT_NODE\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // If startPath/endPath are empty or only have offset, use parentNode directly\n const isStartOffsetOnly =\n startPath.length === 0 ||\n (startPath.length === 1 && typeof startPath[0]?.offset === \"number\")\n const isEndOffsetOnly =\n endPath.length === 0 ||\n (endPath.length === 1 && typeof endPath[0]?.offset === \"number\")\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n }\n }\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(startNode, startOffset)\n domRange.setEnd(endNode, endOffset)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\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\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isTextNode(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): Node | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isTextNodeStep(part)) {\n const nodeIndex = part.index - 1\n if (nodeIndex >= 0 && nodeIndex < _currentNode.childNodes.length) {\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 const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\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 _currentNode = null\n break\n }\n }\n }\n\n return _currentNode\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 createNodeResultObject(null)\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 if (lastPart && isTextNodeStep(lastPart)) {\n const childIndex = lastPart.index - 1\n if (childIndex >= 0 && childIndex < nodeById.childNodes.length) {\n const childNode = nodeById.childNodes[childIndex] as Node\n return createNodeResultObject(childNode, lastPart)\n }\n }\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\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 const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\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 return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )\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 return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n currentNode = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!currentNode) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n if (asRange) {\n const range = createRangeForNode(document, currentNode, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(currentNode, lastPart)\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 | null\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 * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\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 * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\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 return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\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 and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\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 result = addOffsets(result, position?.temporal, position?.spatial)\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 = Array.from(parentNode.childNodes)\n const index = siblings.indexOf(currentNode as ChildNode)\n\n if (index === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n let step: number\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n // Find index among element siblings\n const elementSiblings = siblings.filter(\n (n) => n.nodeType === Node.ELEMENT_NODE,\n )\n const elementIndex = elementSiblings.indexOf(currentNode as ChildNode)\n step = (elementIndex + 1) * 2\n } else {\n // For text nodes, use index among all child nodes (1-based, odd)\n step = index + 1\n }\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 and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\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 const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\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\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 // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\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 CFI with spine indirection\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\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 let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine indirection if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine indirection if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += 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 return `epubcfi(${cfi})`\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 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","isIndirectionOnly","parsed","isParsedCfiRange","lastPart","unwrapCfi","cfi","match","tokenize","tokens","state","isEscaped","value","push","token","cat","c","unwrappedCfi","chars","i","char","findTokenIndices","type","splitAt","arr","indices","result","start","index","parsePart","parts","pathStepTokens","currentPathStep","val","stepIndex","currentPart","stepsTokens","paramName","looksLikeId","part","parseIndirection","indirectionIndices","parse","commaIndices","parentTokens","startTokens","endTokens","attachOffsetToParent","parent","offsetTokens","filteredTokens","lastParentPath","offsetToken","pathClone","end","resolve","document","options","parsedCfi","resolveParsed","error","resolveRange","createNodeResultObject","nonIndirectionPart","resolvePath","range","parentPaths","startPath","endPath","parentNode","indirectionPath","indirectionResult","actualParentPath","actualParentResult","parentPath","parentResult","isParentTextNode","startNode","endNode","startOffset","endOffset","lastStartPart","lastEndPart","isStartOffsetOnly","isEndOffsetOnly","traversed","traverseNodePath","domRange","createBaseResultObject","extractSideBias","text","sideBiasMatch","isTextNodeStep","resolveExtensions","sideBias","extensions","createRangeResultObject","createRangeForNode","offset","offsetValue","currentNode","path","startIndex","throwOnError","_currentNode","nodeIndex","childElements","nextNode","asRange","nodeById","remainingPathIndex","findNodeById","childIndex","childNode","extractTextAssertion","textNode","textContent","maxLength","halfLength","formatSpatialOffset","spatial","x","y","safeX","safeY","addOffsets","temporal","addTextAssertionAndSideBias","textAssertion","sideBiasChar","generatePoint","position","parentId","parentSiblings","n","siblings","elementsBefore","elementIndex","step","addExtensions","generateRelativePath","fromNode","toNode","relativePath","serializeExtensions","key","escapedValue","extensionString","lastBracketIndex","content","generateRange","startPosition","endPosition","ancestor","ancestorCfi","ancestorWithExt","startWithExt","endWithExt","generateSpineCfi","spineIndex","spineId","isFinal","generate","collapse","toEnd","getStepTypeWeight","compare","a","b","aParsed","bParsed","p","q","maxIndex","xStepType","yStepType","xTemporal","yTemporal","xSpatial","ySpatial","xY","yY","xX","yX","serializePart","inBrackets","serializePath","serialize"],"mappings":"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"}
|
package/dist/index.umd.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(p,$){typeof exports=="object"&&typeof module<"u"?$(exports):typeof define=="function"&&define.amd?define(["exports"],$):(p=typeof globalThis<"u"?globalThis:p||self,$(p["prose-reader-cfi"]={}))})(this,function(p){"use strict";function $(e){const n=[e];let t=e;for(;t.parentNode;)n.push(t.parentNode),t=t.parentNode;return n}function J(e,n){if(e===n)return e;const t=$(e),i=new Set(t);let r=n;for(;r;){if(i.has(r))return r;r=r.parentNode}return null}const K=/[[\]^,();]/g;function E(e){return e.replace(K,"^$&")}const Q=/^epubcfi\((.*)\)$/,v=e=>e.nodeType===Node.ELEMENT_NODE,P=e=>typeof e=="object"&&e!==null&&"nodeType"in e&&(e.nodeType===Node.ELEMENT_NODE||e.nodeType===Node.TEXT_NODE),Z=e=>e.nodeType===Node.TEXT_NODE;function D(e){if(b(e))return!1;const n=e[e.length-1];return e.length>1&&(n===void 0||n.length===0)}function b(e){return e!==null&&typeof e=="object"&&"parent"in e&&"start"in e&&"end"in e}function ee(e){const n=e.match(Q);return n&&n[1]||e}function te(e){const n=[];let t=null,i=!1,r="";const s=a=>{n.push(a),t=null,r=""},l=a=>{r+=a,i=!1},f=ee(e).trim(),c=Array.from(f).concat("");for(let a=0;a<c.length;a++){const o=c[a];if(!o){t==="/"||t===":"?s([t,parseInt(r,10)]):t==="~"?s(["~",parseFloat(r)]):t==="@"?s(["@",parseFloat(r)]):t==="["?s(["[",r]):t===";"||t?.startsWith(";")?s([t,r]):t==="!"&&s(["!",0]);break}if(o==="^"&&!i){i=!0;continue}if(t==="!")s(["!",0]);else if(t===",")s([",",0]);else if(t==="/"||t===":"){if(/^\d$/.test(o)){l(o);continue}s([t,parseInt(r,10)])}else if(t==="~"){if(/^\d$/.test(o)||o==="."){l(o);continue}s(["~",parseFloat(r)])}else if(t==="@"){if(o===":"){s(["@",parseFloat(r)]),t="@";continue}if(/^\d$/.test(o)||o==="."){l(o);continue}s(["@",parseFloat(r)])}else if(t==="[")if(o===";"&&!i)s(["[",r]),t=";";else if(o==="]"&&!i)s(["[",r]);else{l(o);continue}else if(t===";")if(o==="="&&!i)t=`;${r}`,r="";else if(o===";"&&!i)s([t,r]),t=";";else if(o==="]"&&!i)s([t,r]);else{l(o);continue}else if(t?.startsWith(";"))if(o===";"&&!i)s([t,r]),t=";";else if(o==="]"&&!i)s([t,r]);else{l(o);continue}else t===null&&o===";"&&(t=";");(o==="/"||o===":"||o==="~"||o==="@"||o==="["||o==="!"||o===",")&&(t=o)}return n}function L(e,n){return e?e.map((t,i)=>t[0]===n?i:null).filter(t=>t!==null):[]}function B(e,n){const t=[];let i=0;for(const r of n)t.push(e.slice(i,r)),i=r;return t.push(e.slice(i)),t}function ne(e){const n=[],t={};let i=-1;for(let r=0;r<e.length;r++){const s=e[r];if(!s)continue;const[l,f]=s;l==="/"?(i++,n[i]={index:f},t[i]=[]):i>=0&&t[i]?.push(s)}for(let r=0;r<n.length;r++){const s=n[r];if(!s)continue;const l=t[r]||[];for(let f=0;f<l.length;f++){const c=l[f];if(!c)continue;const[a,o]=c;if(a===":")s.offset=o;else if(a==="~")s.temporal=o;else if(a==="@")s.spatial=(s.spatial||[]).concat(o);else if(a===";s")s.side=o;else if(a.startsWith(";")&&a!==";s"){const u=a.substring(1);s.extensions||(s.extensions={}),s.extensions[u]=o}else if(a==="["){const u=typeof o=="string"&&!o.includes(" ")&&o.length<50;if(f===0&&u&&!s.id)s.id=o;else if(o!==""){const d=o.split(",").map(h=>h.trim());s.text=d}}}}return n}function R(e){const n=L(e,"!");return B(e,n).map(ne)}function T(e){if(!e)throw new Error("CFI string cannot be empty");const n=te(e);if(!n||n.length===0)throw new Error("Failed to tokenize CFI string");const t=L(n,",");if(t.length===0)return R(n);const[i,r,s]=B(n,t);function l(o,u){if(!u||u.length===0)return[[]];const d=u.filter(h=>h[0]!==",");if(d.length===1&&d[0]&&d[0][0]===":"){const h=o.length>0?o[o.length-1]:void 0,g=d[0];if(h&&h.length>0&&g){const x=h.map(C=>({...C})),y=x[x.length-1];return y&&(y.offset=g[1]),[x]}return[[]]}return R(u)}const f=R(i||[]),c=l(f,r||[]),a=l(f,s||[]);return{parent:f,start:c,end:a}}function re(e,n,t={}){try{const i=typeof e!="string"?e:T(e);return ie(i,n,t)}catch(i){if(t.throwOnError)throw i;return{node:null,isRange:!1}}}function ie(e,n,t={asRange:!1}){if(b(e))return se(e,n);if(D(e))return N(null);const i=e.at(-1);if(i)return S(i,n,t);throw new Error("Invalid CFI structure")}function se(e,n){const t=e.parent,i=e.start[0]||[],r=e.end[0]||[];let s=n.documentElement;if(t.length>0)if(t.length>1){const d=t[0];if(d){const h=S(d,n);P(h.node)&&(s=h.node)}if(t.length>1&&s){const h=t[1];if(h){const g=S(h,n);P(g.node)&&(s=g.node)}}}else{const d=t[0];if(d){const h=S(d,n);P(h.node)&&(s=h.node)}}if(!s)throw new Error("Failed to resolve parent node in CFI range");const l=s.nodeType===Node.TEXT_NODE;let f,c,a=0,o=0;if(l){f=s,c=s;const d=i[i.length-1],h=r[r.length-1];a=(Array.isArray(d?.offset)?d.offset[0]:d?.offset)??0,o=(Array.isArray(h?.offset)?h.offset[0]:h?.offset)??0}else{const d=i.length===0||i.length===1&&typeof i[0]?.offset=="number",h=r.length===0||r.length===1&&typeof r[0]?.offset=="number";if(d){if(!s)throw new Error("Failed to resolve parent node in CFI range (start)");f=s,a=i[0]?.offset??0}else{const g=O(s,i,0,!0);if(!g)throw new Error("Failed to resolve start node in CFI range");f=g;const x=i[i.length-1];a=(Array.isArray(x?.offset)?x.offset[0]:x?.offset)??0}if(h){if(!s)throw new Error("Failed to resolve parent node in CFI range (end)");c=s,o=r[0]?.offset??0}else{const g=O(s,r,0,!0);if(!g)throw new Error("Failed to resolve end node in CFI range");c=g;const x=r[r.length-1];o=(Array.isArray(x?.offset)?x.offset[0]:x?.offset)??0}}const u=n.createRange();return u.setStart(f,a),u.setEnd(c,o),{...k(i[i.length-1]),node:u,isRange:!0}}function oe(e){if(e?.side)return e.side;if(e?.text&&e.text.length>0){const n=e.text[0];if(n){const t=n.match(/^([ab])$/);if(t)return t[1]}}}function F(e){return e.index%2!==0}function le(e){return(b(e)?e.end:e).at(-1)?.at(-1)?.extensions}function k(e){const n=oe(e),t=e?.extensions;return{offset:e?.offset,temporal:e?.temporal,spatial:e?.spatial,side:n,extensions:t}}function N(e,n){return{node:e,isRange:!1,...k(n)}}function w(e,n){return{node:e,isRange:!0,...k(n)}}function W(e,n,t){const i=e.createRange();if(i.selectNodeContents(n),t!==void 0){const r=Array.isArray(t)?t[0]:t;Z(n)&&i.setStart(n,r||0)}return i}function O(e,n,t,i){let r=e;for(let s=t;s<n.length;s++){const l=n[s];if(!r||!l)break;if(F(l)){const f=l.index-1;if(f>=0&&f<r.childNodes.length)r=r.childNodes[f];else{if(i)throw new Error(`Invalid text node index: ${l.index}`);r=null;break}}else{const f=Array.from(r.childNodes).filter(a=>a.nodeType===Node.ELEMENT_NODE),c=Math.floor(l.index/2)-1;if(c>=0&&c<f.length){const a=f[c];a&&(r=a)}else{if(i)throw new Error(`Invalid element step index: ${l.index}`);r=null;break}}}return r}function S(e,n,t={}){const{throwOnError:i=!1,asRange:r=!1}=t;if(!n){if(i)throw new Error("Document is not available");return N(null)}const{node:s,remainingPathIndex:l}=fe(n,e);if(s&&l>=e.length){const o=e.at(-1);if(o&&F(o)){const u=o.index-1;if(u>=0&&u<s.childNodes.length){const d=s.childNodes[u];return N(d,o)}}if(r){const u=W(n,s,o?.offset);return w(u,o)}return N(s,o)}let f=s||n.documentElement;const c=s?l:0;if(r&&e.length>0){const o=e[e.length-1];if(o&&!F(o)){if(o.index===0&&f){const d=n.createRange();return d.setStart(f,0),d.setEnd(f,0),w(d,o)}const u=O(f,e.slice(0,-1),c,i);if(u){const d=Array.from(u.childNodes).filter(g=>g.nodeType===Node.ELEMENT_NODE);if(Math.floor(o.index/2)-1===d.length){const g=n.createRange();return g.selectNodeContents(u),g.collapse(!1),w(g,o)}}}}if(f=O(f,e,c,i),!f){if(i)throw new Error("Failed to resolve CFI path");return N(null)}const a=e.at(-1);if(r){const o=W(n,f,a?.offset);return w(o,a)}return N(f,a)}function fe(e,n){for(let t=n.length-1;t>=0;t--){const i=n[t];if(i?.id){const r=e.getElementById(i.id);if(r)return{node:r,remainingPathIndex:t+1}}}return{node:null,remainingPathIndex:0}}function X(e,n,t={}){if(!e.textContent||e.textContent.trim()==="")return null;const i=e.textContent,r=t.textAssertionLength||10;if(n!==void 0&&n<=i.length){const s=Math.floor(r/2),l=Math.max(0,n-s),f=Math.min(i.length,n+s);return i.substring(l,f)}return i.substring(0,Math.min(i.length,r))}function ae(e){const[n,t]=e,i=Math.max(0,Math.min(100,n)),r=Math.max(0,Math.min(100,t));return`@${i}:${r}`}function M(e,n,t){let i=e;return n!==void 0&&(i+=`~${n}`),t!==void 0&&(i+=ae(t)),i}function z(e,n,t){let i=e;if(n&&(i+=`[${E(n)}]`),t){const r=t==="before"?"b":"a";n?i=`${i.substring(0,i.length-1)};s=${r}]`:i+=`[;s=${r}]`}return i}function j(e,n,t={},i){let r="",s=e,l=null;if(e?.nodeType===Node.TEXT_NODE){l=e;const c=e.parentNode;if(!c)throw new Error("Text node doesn't have a parent");const o=Array.from(c.childNodes).indexOf(e);if(o===-1)throw new Error("Node not found in parent's children");if(r=`/${o+1}`,v(c)&&c.id){const u=c.id,d=Array.from(c.parentNode?.childNodes||[]);r=`/${d.slice(0,d.indexOf(c)+1).filter(x=>x.nodeType===Node.ELEMENT_NODE).length*2}[${E(u)}]${r}`,s=c.parentNode}else s=c}let f=null;for(l&&t.includeTextAssertions&&(f=X(l,n,t));s?.parentNode;){if(!(l&&s===l.parentNode&&r.includes(`[${v(s)?s.id:""}]`))){const c=s.parentNode,a=Array.from(c.childNodes),o=a.indexOf(s);if(o===-1)throw new Error("Node not found in parent's children");const u=a.slice(0,o+1).filter(g=>g.nodeType===Node.ELEMENT_NODE);let d;s.nodeType,Node.ELEMENT_NODE,d=u.length;const h=d*2;v(s)&&s.id?r=`/${h}[${E(s.id)}]${r}`:r=`/${h}${r}`}if(s.parentNode.nodeName.toLowerCase()==="html")break;s=s.parentNode}return n!==void 0&&(r+=`:${n}`),r=M(r,i?.temporal,i?.spatial||t.spatialOffset),r=z(r,f,t.includeSideBias),r=m(r,t.extensions),r}function Y(e,n,t,i={},r){if(e===n){let c=t!==void 0?`:${t}`:"";return c=M(c,r?.temporal,r?.spatial),c}const s=[];let l=n;for(;l&&l!==e;){const c=l.parentNode;if(!c)break;const a=Array.from(c.childNodes),o=a.indexOf(l);if(o===-1)throw new Error("Node not found in parent's children");let u;l.nodeType===Node.ELEMENT_NODE?u=(a.filter(g=>g.nodeType===Node.ELEMENT_NODE).indexOf(l)+1)*2:u=o+1,v(l)&&l.id?s.unshift(`/${u}[${E(l.id)}]`):s.unshift(`/${u}`),l=c}let f=s.join("");if(t!==void 0&&(f+=`:${t}`),f=M(f,r?.temporal,r?.spatial),i.includeTextAssertions&&n.nodeType===Node.TEXT_NODE){const c=X(n,t,i);f=z(f,c,i.includeSideBias)}return f=m(f,i.extensions),f}const ce=e=>Object.entries(e).map(([n,t])=>{const i=E(t);return`${n}=${encodeURIComponent(i)}`}).join(";");function m(e,n){if(!n||Object.keys(n).length===0)return e;const t=ce(n);if(e.endsWith("]")){const i=e.lastIndexOf("["),r=e.substring(i+1,e.length-1);return r.includes(t)?e:`${e.substring(0,e.length-1)}${r.includes(";"),";"}${t}]`}return e.endsWith("!")?e:`${e}[;${t}]`}function de(e,n,t,i,r={},s,l){const f=J(e,t);if(!f)throw new Error("No common ancestor found");const c=j(f,void 0,r),a=Y(f,e,n,r,s),o=Y(f,t,i,r,l);if(r.extensions&&Object.keys(r.extensions).length>0){const u=m(c,r.extensions),d=m(a,r.extensions),h=m(o,r.extensions);return`${u},${d},${h}`}return`${c},${a},${o}`}const V=(e,n,t={},i)=>{const r="";let l=`/6/${(e+1)*2}`;return n&&(l+=`[${E(n)}]`),l=i?m(l,t.extensions):l,`${l}${r}!`};function ue(e,n={}){if(P(e))return`epubcfi(${j(e,void 0,n)})`;let t="";if(!("start"in e))return e.spineIndex!==void 0&&(t=V(e.spineIndex,e.spineId,n,!e.node),!e.node)?`epubcfi(${t})`:(t+=j(e.node??null,e.offset,n,e),`epubcfi(${t})`);const{start:i,end:r}=e;return i.spineIndex!==void 0&&(t=V(i.spineIndex,i.spineId,n,!i.node)),i.node&&r.node&&(t+=de(i.node,i.offset??0,r.node,r.offset??0,n,i,r)),`epubcfi(${t})`}function I(e,n=!1){return typeof e=="string"?I(T(e),n):"parent"in e?n?e.parent.concat(e.end):e.parent.concat(e.start):e}function q(e){return e.offset!==void 0?1:e.index!==void 0?2:e.temporal!==void 0||e.spatial!==void 0?3:4}function _(e,n){const t=typeof e=="string"?T(e):e,i=typeof n=="string"?T(n):n;if("parent"in t||"parent"in i)return _(I(t),I(i))||_(I(t,!0),I(i,!0));for(let r=0;r<Math.max(t.length,i.length);r++){const s=t[r]||[],l=i[r]||[],f=Math.max(s.length,l.length)-1;for(let c=0;c<=f;c++){const a=s[c],o=l[c];if(!a)return-1;if(!o)return 1;const u=q(a),d=q(o);if(u!==d)return u-d;if(a.index>o.index)return 1;if(a.index<o.index)return-1;const h=a.temporal!==void 0,g=o.temporal!==void 0;if(h&&!g)return 1;if(!h&&g)return-1;if(h&&g){if((a.temporal??0)>(o.temporal??0))return 1;if((a.temporal??0)<(o.temporal??0))return-1}const x=a.spatial!==void 0,y=o.spatial!==void 0;if(x&&!y)return 1;if(!x&&y)return-1;if(x&&y){const C=a.spatial?.[1]??0,H=o.spatial?.[1]??0;if(C>H)return 1;if(C<H)return-1;const U=a.spatial?.[0]??0,G=o.spatial?.[0]??0;if(U>G)return 1;if(U<G)return-1}if(c===f){if((a.offset??0)>(o.offset??0))return 1;if((a.offset??0)<(o.offset??0))return-1}}}return 0}function he(e){let n=`/${e.index}`;if(e.id){if(n+=`[${E(e.id)}`,e.extensions)for(const[i,r]of Object.entries(e.extensions))n+=`;${i}=${E(r)}`;n+="]"}e.offset!==void 0&&(n+=`:${e.offset}`),e.temporal!==void 0&&(n+=`~${e.temporal}`),e.spatial&&e.spatial.length>0&&(n+=`@${e.spatial.join(":")}`);const t=[];if(e.text&&e.text.length>0&&t.push(e.text.map(E).join(",")),(e.side||e.extensions&&!e.id)&&(e.side&&t.push(`;s=${e.side}`),e.extensions&&!e.id))for(const[i,r]of Object.entries(e.extensions))t.push(`;${i}=${E(r)}`);return t.length>0&&(n+=`[${t.join("")}]`),n}function A(e){return e.map(n=>he(n)).join("")}function ge(e){if(Array.isArray(e))return`epubcfi(${e.map(A).join("!")})`;const n=e.parent.map(A).join("!"),t=e.start.map(A).join("!"),i=e.end.map(A).join("!");return`epubcfi(${n},${t},${i})`}p.compare=_,p.generate=ue,p.isIndirectionOnly=D,p.isParsedCfiRange=b,p.parse=T,p.resolve=re,p.resolveExtensions=le,p.serialize=ge,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})});
|
|
1
|
+
(function(p,$){typeof exports=="object"&&typeof module<"u"?$(exports):typeof define=="function"&&define.amd?define(["exports"],$):(p=typeof globalThis<"u"?globalThis:p||self,$(p["prose-reader-cfi"]={}))})(this,(function(p){"use strict";function $(e){const n=[e];let t=e;for(;t.parentNode;)n.push(t.parentNode),t=t.parentNode;return n}function J(e,n){if(e===n)return e;const t=$(e),i=new Set(t);let r=n;for(;r;){if(i.has(r))return r;r=r.parentNode}return null}const K=/[[\]^,();]/g;function E(e){return e.replace(K,"^$&")}const Q=/^epubcfi\((.*)\)$/,v=e=>e.nodeType===Node.ELEMENT_NODE,P=e=>typeof e=="object"&&e!==null&&"nodeType"in e&&(e.nodeType===Node.ELEMENT_NODE||e.nodeType===Node.TEXT_NODE),Z=e=>e.nodeType===Node.TEXT_NODE;function D(e){if(b(e))return!1;const n=e[e.length-1];return e.length>1&&(n===void 0||n.length===0)}function b(e){return e!==null&&typeof e=="object"&&"parent"in e&&"start"in e&&"end"in e}function ee(e){const n=e.match(Q);return n&&n[1]||e}function te(e){const n=[];let t=null,i=!1,r="";const s=a=>{n.push(a),t=null,r=""},l=a=>{r+=a,i=!1},f=ee(e).trim(),c=Array.from(f).concat("");for(let a=0;a<c.length;a++){const o=c[a];if(!o){t==="/"||t===":"?s([t,parseInt(r,10)]):t==="~"?s(["~",parseFloat(r)]):t==="@"?s(["@",parseFloat(r)]):t==="["?s(["[",r]):t===";"||t?.startsWith(";")?s([t,r]):t==="!"&&s(["!",0]);break}if(o==="^"&&!i){i=!0;continue}if(t==="!")s(["!",0]);else if(t===",")s([",",0]);else if(t==="/"||t===":"){if(/^\d$/.test(o)){l(o);continue}s([t,parseInt(r,10)])}else if(t==="~"){if(/^\d$/.test(o)||o==="."){l(o);continue}s(["~",parseFloat(r)])}else if(t==="@"){if(o===":"){s(["@",parseFloat(r)]),t="@";continue}if(/^\d$/.test(o)||o==="."){l(o);continue}s(["@",parseFloat(r)])}else if(t==="[")if(o===";"&&!i)s(["[",r]),t=";";else if(o==="]"&&!i)s(["[",r]);else{l(o);continue}else if(t===";")if(o==="="&&!i)t=`;${r}`,r="";else if(o===";"&&!i)s([t,r]),t=";";else if(o==="]"&&!i)s([t,r]);else{l(o);continue}else if(t?.startsWith(";"))if(o===";"&&!i)s([t,r]),t=";";else if(o==="]"&&!i)s([t,r]);else{l(o);continue}else t===null&&o===";"&&(t=";");(o==="/"||o===":"||o==="~"||o==="@"||o==="["||o==="!"||o===",")&&(t=o)}return n}function L(e,n){return e?e.map((t,i)=>t[0]===n?i:null).filter(t=>t!==null):[]}function B(e,n){const t=[];let i=0;for(const r of n)t.push(e.slice(i,r)),i=r;return t.push(e.slice(i)),t}function ne(e){const n=[],t={};let i=-1;for(let r=0;r<e.length;r++){const s=e[r];if(!s)continue;const[l,f]=s;l==="/"?(i++,n[i]={index:f},t[i]=[]):i>=0&&t[i]?.push(s)}for(let r=0;r<n.length;r++){const s=n[r];if(!s)continue;const l=t[r]||[];for(let f=0;f<l.length;f++){const c=l[f];if(!c)continue;const[a,o]=c;if(a===":")s.offset=o;else if(a==="~")s.temporal=o;else if(a==="@")s.spatial=(s.spatial||[]).concat(o);else if(a===";s")s.side=o;else if(a.startsWith(";")&&a!==";s"){const u=a.substring(1);s.extensions||(s.extensions={}),s.extensions[u]=o}else if(a==="["){const u=typeof o=="string"&&!o.includes(" ")&&o.length<50;if(f===0&&u&&!s.id)s.id=o;else if(o!==""){const d=o.split(",").map(h=>h.trim());s.text=d}}}}return n}function R(e){const n=L(e,"!");return B(e,n).map(ne)}function T(e){if(!e)throw new Error("CFI string cannot be empty");const n=te(e);if(!n||n.length===0)throw new Error("Failed to tokenize CFI string");const t=L(n,",");if(t.length===0)return R(n);const[i,r,s]=B(n,t);function l(o,u){if(!u||u.length===0)return[[]];const d=u.filter(h=>h[0]!==",");if(d.length===1&&d[0]&&d[0][0]===":"){const h=o.length>0?o[o.length-1]:void 0,g=d[0];if(h&&h.length>0&&g){const x=h.map(C=>({...C})),y=x[x.length-1];return y&&(y.offset=g[1]),[x]}return[[]]}return R(u)}const f=R(i||[]),c=l(f,r||[]),a=l(f,s||[]);return{parent:f,start:c,end:a}}function re(e,n,t={}){try{const i=typeof e!="string"?e:T(e);return ie(i,n,t)}catch(i){if(t.throwOnError)throw i;return{node:null,isRange:!1}}}function ie(e,n,t={asRange:!1}){if(b(e))return se(e,n);if(D(e))return N(null);const i=e.at(-1);if(i)return S(i,n,t);throw new Error("Invalid CFI structure")}function se(e,n){const t=e.parent,i=e.start[0]||[],r=e.end[0]||[];let s=n.documentElement;if(t.length>0)if(t.length>1){const d=t[0];if(d){const h=S(d,n);P(h.node)&&(s=h.node)}if(t.length>1&&s){const h=t[1];if(h){const g=S(h,n);P(g.node)&&(s=g.node)}}}else{const d=t[0];if(d){const h=S(d,n);P(h.node)&&(s=h.node)}}if(!s)throw new Error("Failed to resolve parent node in CFI range");const l=s.nodeType===Node.TEXT_NODE;let f,c,a=0,o=0;if(l){f=s,c=s;const d=i[i.length-1],h=r[r.length-1];a=(Array.isArray(d?.offset)?d.offset[0]:d?.offset)??0,o=(Array.isArray(h?.offset)?h.offset[0]:h?.offset)??0}else{const d=i.length===0||i.length===1&&typeof i[0]?.offset=="number",h=r.length===0||r.length===1&&typeof r[0]?.offset=="number";if(d){if(!s)throw new Error("Failed to resolve parent node in CFI range (start)");f=s,a=i[0]?.offset??0}else{const g=O(s,i,0,!0);if(!g)throw new Error("Failed to resolve start node in CFI range");f=g;const x=i[i.length-1];a=(Array.isArray(x?.offset)?x.offset[0]:x?.offset)??0}if(h){if(!s)throw new Error("Failed to resolve parent node in CFI range (end)");c=s,o=r[0]?.offset??0}else{const g=O(s,r,0,!0);if(!g)throw new Error("Failed to resolve end node in CFI range");c=g;const x=r[r.length-1];o=(Array.isArray(x?.offset)?x.offset[0]:x?.offset)??0}}const u=n.createRange();return u.setStart(f,a),u.setEnd(c,o),{...F(i[i.length-1]),node:u,isRange:!0}}function oe(e){if(e?.side)return e.side;if(e?.text&&e.text.length>0){const n=e.text[0];if(n){const t=n.match(/^([ab])$/);if(t)return t[1]}}}function k(e){return e.index%2!==0}function le(e){return(b(e)?e.end:e).at(-1)?.at(-1)?.extensions}function F(e){const n=oe(e),t=e?.extensions;return{offset:e?.offset,temporal:e?.temporal,spatial:e?.spatial,side:n,extensions:t}}function N(e,n){return{node:e,isRange:!1,...F(n)}}function w(e,n){return{node:e,isRange:!0,...F(n)}}function W(e,n,t){const i=e.createRange();if(i.selectNodeContents(n),t!==void 0){const r=Array.isArray(t)?t[0]:t;Z(n)&&i.setStart(n,r||0)}return i}function O(e,n,t,i){let r=e;for(let s=t;s<n.length;s++){const l=n[s];if(!r||!l)break;if(k(l)){const f=l.index-1;if(f>=0&&f<r.childNodes.length)r=r.childNodes[f];else{if(i)throw new Error(`Invalid text node index: ${l.index}`);r=null;break}}else{const f=Array.from(r.childNodes).filter(a=>a.nodeType===Node.ELEMENT_NODE),c=Math.floor(l.index/2)-1;if(c>=0&&c<f.length){const a=f[c];a&&(r=a)}else{if(i)throw new Error(`Invalid element step index: ${l.index}`);r=null;break}}}return r}function S(e,n,t={}){const{throwOnError:i=!1,asRange:r=!1}=t;if(!n){if(i)throw new Error("Document is not available");return N(null)}const{node:s,remainingPathIndex:l}=fe(n,e);if(s&&l>=e.length){const o=e.at(-1);if(o&&k(o)){const u=o.index-1;if(u>=0&&u<s.childNodes.length){const d=s.childNodes[u];return N(d,o)}}if(r){const u=W(n,s,o?.offset);return w(u,o)}return N(s,o)}let f=s||n.documentElement;const c=s?l:0;if(r&&e.length>0){const o=e[e.length-1];if(o&&!k(o)){if(o.index===0&&f){const d=n.createRange();return d.setStart(f,0),d.setEnd(f,0),w(d,o)}const u=O(f,e.slice(0,-1),c,i);if(u){const d=Array.from(u.childNodes).filter(g=>g.nodeType===Node.ELEMENT_NODE);if(Math.floor(o.index/2)-1===d.length){const g=n.createRange();return g.selectNodeContents(u),g.collapse(!1),w(g,o)}}}}if(f=O(f,e,c,i),!f){if(i)throw new Error("Failed to resolve CFI path");return N(null)}const a=e.at(-1);if(r){const o=W(n,f,a?.offset);return w(o,a)}return N(f,a)}function fe(e,n){for(let t=n.length-1;t>=0;t--){const i=n[t];if(i?.id){const r=e.getElementById(i.id);if(r)return{node:r,remainingPathIndex:t+1}}}return{node:null,remainingPathIndex:0}}function X(e,n,t={}){if(!e.textContent||e.textContent.trim()==="")return null;const i=e.textContent,r=t.textAssertionLength||10;if(n!==void 0&&n<=i.length){const s=Math.floor(r/2),l=Math.max(0,n-s),f=Math.min(i.length,n+s);return i.substring(l,f)}return i.substring(0,Math.min(i.length,r))}function ae(e){const[n,t]=e,i=Math.max(0,Math.min(100,n)),r=Math.max(0,Math.min(100,t));return`@${i}:${r}`}function M(e,n,t){let i=e;return n!==void 0&&(i+=`~${n}`),t!==void 0&&(i+=ae(t)),i}function z(e,n,t){let i=e;if(n&&(i+=`[${E(n)}]`),t){const r=t==="before"?"b":"a";n?i=`${i.substring(0,i.length-1)};s=${r}]`:i+=`[;s=${r}]`}return i}function j(e,n,t={},i){let r="",s=e,l=null;if(e?.nodeType===Node.TEXT_NODE){l=e;const c=e.parentNode;if(!c)throw new Error("Text node doesn't have a parent");const o=Array.from(c.childNodes).indexOf(e);if(o===-1)throw new Error("Node not found in parent's children");if(r=`/${o+1}`,v(c)&&c.id){const u=c.id,d=Array.from(c.parentNode?.childNodes||[]);r=`/${d.slice(0,d.indexOf(c)+1).filter(x=>x.nodeType===Node.ELEMENT_NODE).length*2}[${E(u)}]${r}`,s=c.parentNode}else s=c}let f=null;for(l&&t.includeTextAssertions&&(f=X(l,n,t));s?.parentNode;){if(!(l&&s===l.parentNode&&r.includes(`[${v(s)?s.id:""}]`))){const c=s.parentNode,a=Array.from(c.childNodes),o=a.indexOf(s);if(o===-1)throw new Error("Node not found in parent's children");const u=a.slice(0,o+1).filter(g=>g.nodeType===Node.ELEMENT_NODE);let d;s.nodeType,Node.ELEMENT_NODE,d=u.length;const h=d*2;v(s)&&s.id?r=`/${h}[${E(s.id)}]${r}`:r=`/${h}${r}`}if(s.parentNode.nodeName.toLowerCase()==="html")break;s=s.parentNode}return n!==void 0&&(r+=`:${n}`),r=M(r,i?.temporal,i?.spatial||t.spatialOffset),r=z(r,f,t.includeSideBias),r=m(r,t.extensions),r}function Y(e,n,t,i={},r){if(e===n){let c=t!==void 0?`:${t}`:"";return c=M(c,r?.temporal,r?.spatial),c}const s=[];let l=n;for(;l&&l!==e;){const c=l.parentNode;if(!c)break;const a=Array.from(c.childNodes),o=a.indexOf(l);if(o===-1)throw new Error("Node not found in parent's children");let u;l.nodeType===Node.ELEMENT_NODE?u=(a.filter(g=>g.nodeType===Node.ELEMENT_NODE).indexOf(l)+1)*2:u=o+1,v(l)&&l.id?s.unshift(`/${u}[${E(l.id)}]`):s.unshift(`/${u}`),l=c}let f=s.join("");if(t!==void 0&&(f+=`:${t}`),f=M(f,r?.temporal,r?.spatial),i.includeTextAssertions&&n.nodeType===Node.TEXT_NODE){const c=X(n,t,i);f=z(f,c,i.includeSideBias)}return f=m(f,i.extensions),f}const ce=e=>Object.entries(e).map(([n,t])=>{const i=E(t);return`${n}=${encodeURIComponent(i)}`}).join(";");function m(e,n){if(!n||Object.keys(n).length===0)return e;const t=ce(n);if(e.endsWith("]")){const i=e.lastIndexOf("["),r=e.substring(i+1,e.length-1);return r.includes(t)?e:`${e.substring(0,e.length-1)}${r.includes(";"),";"}${t}]`}return e.endsWith("!")?e:`${e}[;${t}]`}function de(e,n,t,i,r={},s,l){const f=J(e,t);if(!f)throw new Error("No common ancestor found");const c=j(f,void 0,r),a=Y(f,e,n,r,s),o=Y(f,t,i,r,l);if(r.extensions&&Object.keys(r.extensions).length>0){const u=m(c,r.extensions),d=m(a,r.extensions),h=m(o,r.extensions);return`${u},${d},${h}`}return`${c},${a},${o}`}const V=(e,n,t={},i)=>{let l=`/6/${(e+1)*2}`;return n&&(l+=`[${E(n)}]`),l=i?m(l,t.extensions):l,`${l}!`};function ue(e,n={}){if(P(e))return`epubcfi(${j(e,void 0,n)})`;let t="";if(!("start"in e))return e.spineIndex!==void 0&&(t=V(e.spineIndex,e.spineId,n,!e.node),!e.node)?`epubcfi(${t})`:(t+=j(e.node??null,e.offset,n,e),`epubcfi(${t})`);const{start:i,end:r}=e;return i.spineIndex!==void 0&&(t=V(i.spineIndex,i.spineId,n,!i.node)),i.node&&r.node&&(t+=de(i.node,i.offset??0,r.node,r.offset??0,n,i,r)),`epubcfi(${t})`}function I(e,n=!1){return typeof e=="string"?I(T(e),n):"parent"in e?n?e.parent.concat(e.end):e.parent.concat(e.start):e}function q(e){return e.offset!==void 0?1:e.index!==void 0?2:e.temporal!==void 0||e.spatial!==void 0?3:4}function _(e,n){const t=typeof e=="string"?T(e):e,i=typeof n=="string"?T(n):n;if("parent"in t||"parent"in i)return _(I(t),I(i))||_(I(t,!0),I(i,!0));for(let r=0;r<Math.max(t.length,i.length);r++){const s=t[r]||[],l=i[r]||[],f=Math.max(s.length,l.length)-1;for(let c=0;c<=f;c++){const a=s[c],o=l[c];if(!a)return-1;if(!o)return 1;const u=q(a),d=q(o);if(u!==d)return u-d;if(a.index>o.index)return 1;if(a.index<o.index)return-1;const h=a.temporal!==void 0,g=o.temporal!==void 0;if(h&&!g)return 1;if(!h&&g)return-1;if(h&&g){if((a.temporal??0)>(o.temporal??0))return 1;if((a.temporal??0)<(o.temporal??0))return-1}const x=a.spatial!==void 0,y=o.spatial!==void 0;if(x&&!y)return 1;if(!x&&y)return-1;if(x&&y){const C=a.spatial?.[1]??0,H=o.spatial?.[1]??0;if(C>H)return 1;if(C<H)return-1;const U=a.spatial?.[0]??0,G=o.spatial?.[0]??0;if(U>G)return 1;if(U<G)return-1}if(c===f){if((a.offset??0)>(o.offset??0))return 1;if((a.offset??0)<(o.offset??0))return-1}}}return 0}function he(e){let n=`/${e.index}`;if(e.id){if(n+=`[${E(e.id)}`,e.extensions)for(const[i,r]of Object.entries(e.extensions))n+=`;${i}=${E(r)}`;n+="]"}e.offset!==void 0&&(n+=`:${e.offset}`),e.temporal!==void 0&&(n+=`~${e.temporal}`),e.spatial&&e.spatial.length>0&&(n+=`@${e.spatial.join(":")}`);const t=[];if(e.text&&e.text.length>0&&t.push(e.text.map(E).join(",")),(e.side||e.extensions&&!e.id)&&(e.side&&t.push(`;s=${e.side}`),e.extensions&&!e.id))for(const[i,r]of Object.entries(e.extensions))t.push(`;${i}=${E(r)}`);return t.length>0&&(n+=`[${t.join("")}]`),n}function A(e){return e.map(n=>he(n)).join("")}function ge(e){if(Array.isArray(e))return`epubcfi(${e.map(A).join("!")})`;const n=e.parent.map(A).join("!"),t=e.start.map(A).join("!"),i=e.end.map(A).join("!");return`epubcfi(${n},${t},${i})`}p.compare=_,p.generate=ue,p.isIndirectionOnly=D,p.isParsedCfiRange=b,p.parse=T,p.resolve=re,p.resolveExtensions=le,p.serialize=ge,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=index.umd.cjs.map
|
package/dist/index.umd.cjs.map
CHANGED
|
@@ -1 +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":["import type { CfiRange, ParsedCfi } from \"./parse\"\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: TODO\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/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\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 * 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 } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\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\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 // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n isTextNode,\n} 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 * 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 const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\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 if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, 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 paths and start/end paths\n const parentPaths = range.parent\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 (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = parentNode.nodeType === Node.TEXT_NODE\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // If startPath/endPath are empty or only have offset, use parentNode directly\n const isStartOffsetOnly =\n startPath.length === 0 ||\n (startPath.length === 1 && typeof startPath[0]?.offset === \"number\")\n const isEndOffsetOnly =\n endPath.length === 0 ||\n (endPath.length === 1 && typeof endPath[0]?.offset === \"number\")\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n }\n }\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(startNode, startOffset)\n domRange.setEnd(endNode, endOffset)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\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\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isTextNode(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): Node | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isTextNodeStep(part)) {\n const nodeIndex = part.index - 1\n if (nodeIndex >= 0 && nodeIndex < _currentNode.childNodes.length) {\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 const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\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 _currentNode = null\n break\n }\n }\n }\n\n return _currentNode\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 createNodeResultObject(null)\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 if (lastPart && isTextNodeStep(lastPart)) {\n const childIndex = lastPart.index - 1\n if (childIndex >= 0 && childIndex < nodeById.childNodes.length) {\n const childNode = nodeById.childNodes[childIndex] as Node\n return createNodeResultObject(childNode, lastPart)\n }\n }\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\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 const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\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 return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )\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 return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n currentNode = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!currentNode) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n if (asRange) {\n const range = createRangeForNode(document, currentNode, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(currentNode, lastPart)\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 | null\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 * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\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 * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\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 return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\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 and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\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 result = addOffsets(result, position?.temporal, position?.spatial)\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 = Array.from(parentNode.childNodes)\n const index = siblings.indexOf(currentNode as ChildNode)\n\n if (index === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n let step: number\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n // Find index among element siblings\n const elementSiblings = siblings.filter(\n (n) => n.nodeType === Node.ELEMENT_NODE,\n )\n const elementIndex = elementSiblings.indexOf(currentNode as ChildNode)\n step = (elementIndex + 1) * 2\n } else {\n // For text nodes, use index among all child nodes (1-based, odd)\n step = index + 1\n }\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 and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\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 const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\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\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 // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\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 CFI with spine indirection\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\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 let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine indirection if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine indirection if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += 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 return `epubcfi(${cfi})`\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 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","isIndirectionOnly","parsed","isParsedCfiRange","lastPart","unwrapCfi","cfi","match","tokenize","tokens","state","isEscaped","value","push","token","cat","c","unwrappedCfi","chars","i","char","findTokenIndices","type","splitAt","arr","indices","result","start","index","parsePart","parts","pathStepTokens","currentPathStep","val","stepIndex","currentPart","stepsTokens","paramName","looksLikeId","part","parseIndirection","indirectionIndices","parse","commaIndices","parentTokens","startTokens","endTokens","attachOffsetToParent","parent","offsetTokens","filteredTokens","lastParentPath","offsetToken","pathClone","end","resolve","document","options","parsedCfi","resolveParsed","error","resolveRange","createNodeResultObject","nonIndirectionPart","resolvePath","range","parentPaths","startPath","endPath","parentNode","indirectionPath","indirectionResult","actualParentPath","actualParentResult","parentPath","parentResult","isParentTextNode","startNode","endNode","startOffset","endOffset","lastStartPart","lastEndPart","isStartOffsetOnly","isEndOffsetOnly","traversed","traverseNodePath","domRange","createBaseResultObject","extractSideBias","text","sideBiasMatch","isTextNodeStep","resolveExtensions","sideBias","extensions","createRangeResultObject","createRangeForNode","offset","offsetValue","currentNode","path","startIndex","throwOnError","_currentNode","nodeIndex","childElements","nextNode","asRange","nodeById","remainingPathIndex","findNodeById","childIndex","childNode","extractTextAssertion","textNode","textContent","maxLength","halfLength","formatSpatialOffset","spatial","x","y","safeX","safeY","addOffsets","temporal","addTextAssertionAndSideBias","textAssertion","sideBiasChar","generatePoint","position","parentId","parentSiblings","n","siblings","elementsBefore","elementIndex","step","addExtensions","generateRelativePath","fromNode","toNode","relativePath","serializeExtensions","key","escapedValue","extensionString","lastBracketIndex","content","generateRange","startPosition","endPosition","ancestor","ancestorCfi","ancestorWithExt","startWithExt","endWithExt","generateSpineCfi","spineIndex","spineId","isFinal","bracket","generate","collapse","toEnd","getStepTypeWeight","compare","a","b","aParsed","bParsed","p","q","maxIndex","xStepType","yStepType","xTemporal","yTemporal","xSpatial","ySpatial","xY","yY","xX","yX","serializePart","inBrackets","serializePath","serialize"],"mappings":"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"}
|
|
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":["import type { CfiRange, ParsedCfi } from \"./parse\"\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: TODO\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/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\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 * 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 } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\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\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 // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n isTextNode,\n} 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 * 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 const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\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 if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, 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 paths and start/end paths\n const parentPaths = range.parent\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 (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = parentNode.nodeType === Node.TEXT_NODE\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // If startPath/endPath are empty or only have offset, use parentNode directly\n const isStartOffsetOnly =\n startPath.length === 0 ||\n (startPath.length === 1 && typeof startPath[0]?.offset === \"number\")\n const isEndOffsetOnly =\n endPath.length === 0 ||\n (endPath.length === 1 && typeof endPath[0]?.offset === \"number\")\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n }\n }\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(startNode, startOffset)\n domRange.setEnd(endNode, endOffset)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\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\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isTextNode(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): Node | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isTextNodeStep(part)) {\n const nodeIndex = part.index - 1\n if (nodeIndex >= 0 && nodeIndex < _currentNode.childNodes.length) {\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 const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\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 _currentNode = null\n break\n }\n }\n }\n\n return _currentNode\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 createNodeResultObject(null)\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 if (lastPart && isTextNodeStep(lastPart)) {\n const childIndex = lastPart.index - 1\n if (childIndex >= 0 && childIndex < nodeById.childNodes.length) {\n const childNode = nodeById.childNodes[childIndex] as Node\n return createNodeResultObject(childNode, lastPart)\n }\n }\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\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 const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\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 return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )\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 return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n currentNode = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!currentNode) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n if (asRange) {\n const range = createRangeForNode(document, currentNode, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(currentNode, lastPart)\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 | null\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 * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\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 * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\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 return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\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 and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\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 result = addOffsets(result, position?.temporal, position?.spatial)\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 = Array.from(parentNode.childNodes)\n const index = siblings.indexOf(currentNode as ChildNode)\n\n if (index === -1) {\n throw new Error(\"Node not found in parent's children\")\n }\n\n let step: number\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n // Find index among element siblings\n const elementSiblings = siblings.filter(\n (n) => n.nodeType === Node.ELEMENT_NODE,\n )\n const elementIndex = elementSiblings.indexOf(currentNode as ChildNode)\n step = (elementIndex + 1) * 2\n } else {\n // For text nodes, use index among all child nodes (1-based, odd)\n step = index + 1\n }\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 and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && toNode.nodeType === Node.TEXT_NODE) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\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 const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\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\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 // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\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 CFI with spine indirection\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\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 let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine indirection if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine indirection if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += 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 return `epubcfi(${cfi})`\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 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","isIndirectionOnly","parsed","isParsedCfiRange","lastPart","unwrapCfi","cfi","match","tokenize","tokens","state","isEscaped","value","push","token","cat","c","unwrappedCfi","chars","i","char","findTokenIndices","type","splitAt","arr","indices","result","start","index","parsePart","parts","pathStepTokens","currentPathStep","val","stepIndex","currentPart","stepsTokens","paramName","looksLikeId","part","parseIndirection","indirectionIndices","parse","commaIndices","parentTokens","startTokens","endTokens","attachOffsetToParent","parent","offsetTokens","filteredTokens","lastParentPath","offsetToken","pathClone","end","resolve","document","options","parsedCfi","resolveParsed","error","resolveRange","createNodeResultObject","nonIndirectionPart","resolvePath","range","parentPaths","startPath","endPath","parentNode","indirectionPath","indirectionResult","actualParentPath","actualParentResult","parentPath","parentResult","isParentTextNode","startNode","endNode","startOffset","endOffset","lastStartPart","lastEndPart","isStartOffsetOnly","isEndOffsetOnly","traversed","traverseNodePath","domRange","createBaseResultObject","extractSideBias","text","sideBiasMatch","isTextNodeStep","resolveExtensions","sideBias","extensions","createRangeResultObject","createRangeForNode","offset","offsetValue","currentNode","path","startIndex","throwOnError","_currentNode","nodeIndex","childElements","nextNode","asRange","nodeById","remainingPathIndex","findNodeById","childIndex","childNode","extractTextAssertion","textNode","textContent","maxLength","halfLength","formatSpatialOffset","spatial","x","y","safeX","safeY","addOffsets","temporal","addTextAssertionAndSideBias","textAssertion","sideBiasChar","generatePoint","position","parentId","parentSiblings","n","siblings","elementsBefore","elementIndex","step","addExtensions","generateRelativePath","fromNode","toNode","relativePath","serializeExtensions","key","escapedValue","extensionString","lastBracketIndex","content","generateRange","startPosition","endPosition","ancestor","ancestorCfi","ancestorWithExt","startWithExt","endWithExt","generateSpineCfi","spineIndex","spineId","isFinal","generate","collapse","toEnd","getStepTypeWeight","compare","a","b","aParsed","bParsed","p","q","maxIndex","xStepType","yStepType","xTemporal","yTemporal","xSpatial","ySpatial","xY","yY","xX","yX","serializePart","inBrackets","serializePath","serialize"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prose-reader/cfi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.252.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"/dist"
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"jsdom": "^26.1.0",
|
|
25
|
-
"typescript": "
|
|
25
|
+
"typescript": "*",
|
|
26
26
|
"vite": "^7"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "e122abd2a9e5c28b760c3d6f819b6abb12a27bea"
|
|
29
29
|
}
|