frond-js 0.4.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +247 -0
  3. package/README.md +211 -0
  4. package/dist/abort-BY8vBk0v.d.cts +99 -0
  5. package/dist/abort-BY8vBk0v.d.ts +99 -0
  6. package/dist/adapter-3G46J3CA.cjs +503 -0
  7. package/dist/adapter-3ONQWJVQ.js +501 -0
  8. package/dist/adapter-55QHWRSE.js +124 -0
  9. package/dist/adapter-DF34GBWJ.cjs +19 -0
  10. package/dist/adapter-EXTNILTC.cjs +126 -0
  11. package/dist/adapter-GOFC7TMC.js +284 -0
  12. package/dist/adapter-LRJTQ47I.cjs +286 -0
  13. package/dist/adapter-TWWZML4A.js +17 -0
  14. package/dist/adapter-ZM5FQTJT.js +1415 -0
  15. package/dist/adapter-ZRNSDQUV.cjs +1417 -0
  16. package/dist/chunk-2SUG7YFZ.cjs +108 -0
  17. package/dist/chunk-B2L2YVXD.js +89 -0
  18. package/dist/chunk-D4KWSEZD.js +393 -0
  19. package/dist/chunk-EZTIZO6R.cjs +430 -0
  20. package/dist/chunk-G7DLWGBW.cjs +103 -0
  21. package/dist/chunk-GTGLDLJD.cjs +479 -0
  22. package/dist/chunk-IIV6VIUJ.cjs +83 -0
  23. package/dist/chunk-JDTHZQUK.js +102 -0
  24. package/dist/chunk-JJVXT3AC.js +30 -0
  25. package/dist/chunk-LIXRYQL2.js +473 -0
  26. package/dist/chunk-NABYHI6X.cjs +400 -0
  27. package/dist/chunk-SJVOYNTF.js +425 -0
  28. package/dist/chunk-SLI2YL25.cjs +252 -0
  29. package/dist/chunk-U4264IQH.js +78 -0
  30. package/dist/chunk-UY2YRCFC.js +250 -0
  31. package/dist/chunk-WCZTTQ7Z.cjs +32 -0
  32. package/dist/core/index.cjs +162 -0
  33. package/dist/core/index.d.cts +321 -0
  34. package/dist/core/index.d.ts +321 -0
  35. package/dist/core/index.js +49 -0
  36. package/dist/default-DRLIJX73.js +1183 -0
  37. package/dist/default-UK52WOO5.cjs +1192 -0
  38. package/dist/formats/epub/index.cjs +29 -0
  39. package/dist/formats/epub/index.d.cts +286 -0
  40. package/dist/formats/epub/index.d.ts +286 -0
  41. package/dist/formats/epub/index.js +11 -0
  42. package/dist/index.cjs +655 -0
  43. package/dist/index.d.cts +335 -0
  44. package/dist/index.d.ts +335 -0
  45. package/dist/index.js +600 -0
  46. package/dist/render/index.cjs +2 -0
  47. package/dist/render/index.d.cts +116 -0
  48. package/dist/render/index.d.ts +116 -0
  49. package/dist/render/index.js +1 -0
  50. package/dist/types-B76GOMxj.d.ts +129 -0
  51. package/dist/types-B7mslPBY.d.cts +166 -0
  52. package/dist/types-B7mslPBY.d.ts +166 -0
  53. package/dist/types-BH88rUYt.d.cts +129 -0
  54. package/dist/types-C-5eHSRH.d.ts +379 -0
  55. package/dist/types-CPUqTEPW.d.cts +379 -0
  56. package/dist/types-DQYmArgv.d.cts +17 -0
  57. package/dist/types-DQYmArgv.d.ts +17 -0
  58. package/package.json +115 -0
@@ -0,0 +1,473 @@
1
+ import { ParseError, FormatError } from './chunk-B2L2YVXD.js';
2
+
3
+ // src/core/cfi/serialize.ts
4
+ var ESCAPABLE = "^[](),;=";
5
+ function escapeValue(value) {
6
+ let out = "";
7
+ for (const char of value) {
8
+ out += ESCAPABLE.includes(char) ? `^${char}` : char;
9
+ }
10
+ return out;
11
+ }
12
+ function serializeSide(side) {
13
+ if (side === void 0) return "";
14
+ return `;s=${side === "before" ? "b" : "a"}`;
15
+ }
16
+ function wrapAssertion(body) {
17
+ return body === "" ? "" : `[${body}]`;
18
+ }
19
+ function serializeStep(step) {
20
+ const body = `${step.id === void 0 ? "" : escapeValue(step.id)}${serializeSide(step.side)}`;
21
+ return `/${step.index}${wrapAssertion(body)}`;
22
+ }
23
+ function serializeOffset(offset, point) {
24
+ if (offset === void 0) {
25
+ if (point.text === void 0 && point.textAfter === void 0 && point.side === void 0) {
26
+ return "";
27
+ }
28
+ throw new FormatError("\u504F\u79FB\u65AD\u8A00\u7F3A\u5C11\u5B57\u7B26\u504F\u79FB");
29
+ }
30
+ const values = point.text === void 0 ? point.textAfter === void 0 ? "" : `,${escapeValue(point.textAfter)}` : `${escapeValue(point.text)}${point.textAfter === void 0 ? "" : `,${escapeValue(point.textAfter)}`}`;
31
+ return `:${offset}${wrapAssertion(`${values}${serializeSide(point.side)}`)}`;
32
+ }
33
+ function serializeSteps(steps) {
34
+ return steps.map(serializeStep).join("");
35
+ }
36
+ function serializePaths(paths) {
37
+ return paths.map((path) => serializeSteps(path.steps)).join("!");
38
+ }
39
+ function serializePointBody(point) {
40
+ return `${serializePaths(point.paths)}${serializeOffset(point.offset, point)}`;
41
+ }
42
+ function serializePoint(point) {
43
+ return `epubcfi(${serializePointBody(point)})`;
44
+ }
45
+ function relativeSubpath(parent, point, label) {
46
+ const parentLength = parent.length;
47
+ const mismatch = new FormatError(`${label} \u4E0E\u7236\u8DEF\u5F84\u7ED3\u6784\u4E0D\u4E00\u81F4\uFF0C\u65E0\u6CD5\u56DE\u5199\u4E3A\u89C4\u8303\u5F62\u6001`);
48
+ if (parentLength === 0 || point.paths.length < parentLength) throw mismatch;
49
+ const sameSteps = (left, right) => {
50
+ if (left.length !== right.length) return false;
51
+ return left.every((step, index) => step.index === right[index]?.index);
52
+ };
53
+ for (let i = 0; i < parentLength - 1; i += 1) {
54
+ const expected = parent[i];
55
+ const actual = point.paths[i];
56
+ if (expected === void 0 || actual === void 0 || !sameSteps(expected.steps, actual.steps)) {
57
+ throw mismatch;
58
+ }
59
+ }
60
+ const parentLast = parent[parentLength - 1];
61
+ const merged = point.paths[parentLength - 1];
62
+ if (parentLast === void 0 || merged === void 0) throw mismatch;
63
+ const prefix = parentLast.steps;
64
+ if (merged.steps.length < prefix.length) throw mismatch;
65
+ for (let i = 0; i < prefix.length; i += 1) {
66
+ if (prefix[i]?.index !== merged.steps[i]?.index) throw mismatch;
67
+ }
68
+ let body = serializeSteps(merged.steps.slice(prefix.length));
69
+ for (const path of point.paths.slice(parentLength)) {
70
+ body += `!${serializeSteps(path.steps)}`;
71
+ }
72
+ return `${body}${serializeOffset(point.offset, point)}`;
73
+ }
74
+ function serializeRange(range) {
75
+ if (range.parent === null) {
76
+ return `epubcfi(${serializePointBody(range.start)},${serializePointBody(range.end)})`;
77
+ }
78
+ const parentBody = serializePaths(range.parent);
79
+ const startBody = relativeSubpath(range.parent, range.start, "\u8303\u56F4\u8D77\u70B9");
80
+ const endBody = relativeSubpath(range.parent, range.end, "\u8303\u56F4\u7EC8\u70B9");
81
+ return `epubcfi(${parentBody},${startBody},${endBody})`;
82
+ }
83
+ function serializeCfi(cfi) {
84
+ return cfi.kind === "point" ? serializePoint(cfi) : serializeRange(cfi);
85
+ }
86
+
87
+ // src/core/cfi/collapse.ts
88
+ function collapseCfi(cfi, toEnd = false) {
89
+ if (cfi.kind === "point") return serializePoint(cfi);
90
+ return serializePoint(toEnd ? cfi.end : cfi.start);
91
+ }
92
+
93
+ // src/core/cfi/parse.ts
94
+ var ESCAPABLE2 = "^[](),;=";
95
+ function isDigit(char) {
96
+ return char !== void 0 && char >= "0" && char <= "9";
97
+ }
98
+ function splitTopLevel(raw, separator, context) {
99
+ const parts = [];
100
+ let current = "";
101
+ let depth = 0;
102
+ for (let i = 0; i < raw.length; i += 1) {
103
+ const char = raw[i];
104
+ if (char === "^") {
105
+ const next = raw[i + 1];
106
+ if (next === void 0) {
107
+ throw new ParseError(`\u8F6C\u4E49\u7B26 '^' \u540E\u7F3A\u5C11\u5B57\u7B26\uFF08${context}\uFF09`);
108
+ }
109
+ current += char + next;
110
+ i += 1;
111
+ continue;
112
+ }
113
+ if (char === "[") {
114
+ depth += 1;
115
+ current += char;
116
+ continue;
117
+ }
118
+ if (char === "]") {
119
+ depth -= 1;
120
+ if (depth < 0) {
121
+ throw new ParseError(`\u51FA\u73B0\u672A\u914D\u5BF9\u7684 ']'\uFF08${context}\uFF09`);
122
+ }
123
+ current += char;
124
+ continue;
125
+ }
126
+ if (char === separator && depth === 0) {
127
+ parts.push(current);
128
+ current = "";
129
+ continue;
130
+ }
131
+ current += char;
132
+ }
133
+ if (depth !== 0) {
134
+ throw new ParseError(`\u65B9\u62EC\u53F7\u672A\u95ED\u5408\uFF08${context}\uFF09`);
135
+ }
136
+ parts.push(current);
137
+ return parts;
138
+ }
139
+ function unescapeValue(raw, context) {
140
+ let out = "";
141
+ for (let i = 0; i < raw.length; i += 1) {
142
+ const char = raw[i];
143
+ if (char !== "^") {
144
+ out += char;
145
+ continue;
146
+ }
147
+ const next = raw[i + 1];
148
+ if (next === void 0) {
149
+ throw new ParseError(`\u65AD\u8A00\u4E2D\u5B58\u5728\u672A\u5B8C\u6210\u7684\u8F6C\u4E49\u7B26 '^'\uFF08${context}\uFF09`);
150
+ }
151
+ if (!ESCAPABLE2.includes(next)) {
152
+ throw new FormatError(`\u65AD\u8A00\u4E2D\u7684\u8F6C\u4E49\u5B57\u7B26\u975E\u6CD5\uFF1A'^${next}'\uFF08${context}\uFF09`);
153
+ }
154
+ out += next;
155
+ i += 1;
156
+ }
157
+ return out;
158
+ }
159
+ function containsUnescapedOpenBracket(raw) {
160
+ for (let i = 0; i < raw.length; i += 1) {
161
+ const char = raw[i];
162
+ if (char === "^") {
163
+ i += 1;
164
+ continue;
165
+ }
166
+ if (char === "[") return true;
167
+ }
168
+ return false;
169
+ }
170
+ function parseAssertion(raw, context) {
171
+ if (containsUnescapedOpenBracket(raw)) {
172
+ throw new FormatError(`\u65AD\u8A00\u4E2D\u7684 '[' \u5FC5\u987B\u8F6C\u4E49\u4E3A '^['\uFF08${context}\uFF09`);
173
+ }
174
+ const [valuePart = "", ...parameters] = splitTopLevel(raw, ";", context);
175
+ let leading;
176
+ let trailing;
177
+ if (valuePart !== "") {
178
+ const values = splitTopLevel(valuePart, ",", context);
179
+ if (values.length > 2) {
180
+ throw new FormatError(`\u65AD\u8A00\u4E2D\u9017\u53F7\u5206\u9694\u7684\u503C\u8D85\u8FC7 2 \u4E2A\uFF08${context}\uFF09`);
181
+ }
182
+ const [first = "", second] = values;
183
+ if (first !== "") {
184
+ leading = unescapeValue(first, context);
185
+ }
186
+ if (second !== void 0 && second !== "") {
187
+ trailing = unescapeValue(second, context);
188
+ }
189
+ }
190
+ let side;
191
+ for (const parameter of parameters) {
192
+ const equal = parameter.indexOf("=");
193
+ if (equal <= 0) {
194
+ throw new FormatError(`\u65AD\u8A00\u53C2\u6570\u7F3A\u5C11 'k=v' \u5F62\u5F0F\uFF1A${JSON.stringify(parameter)}\uFF08${context}\uFF09`);
195
+ }
196
+ const name = parameter.slice(0, equal);
197
+ const value = parameter.slice(equal + 1);
198
+ if (name !== "s") {
199
+ throw new FormatError(
200
+ `\u65AD\u8A00\u53C2\u6570\u540D\u4E0D\u652F\u6301\uFF1A${JSON.stringify(name)}\uFF08\u4EC5\u652F\u6301 's'\uFF09\uFF08${context}\uFF09`
201
+ );
202
+ }
203
+ if (value === "b") {
204
+ side = "before";
205
+ } else if (value === "a") {
206
+ side = "after";
207
+ } else {
208
+ throw new FormatError(
209
+ `side bias \u53D6\u503C\u975E\u6CD5\uFF1A${JSON.stringify(value)}\uFF08\u4EC5\u652F\u6301 'b' / 'a'\uFF09\uFF08${context}\uFF09`
210
+ );
211
+ }
212
+ }
213
+ return {
214
+ ...leading !== void 0 ? { leading } : {},
215
+ ...trailing !== void 0 ? { trailing } : {},
216
+ ...side !== void 0 ? { side } : {}
217
+ };
218
+ }
219
+ function tryReadAssertion(raw, start, context) {
220
+ if (raw[start] !== "[") return null;
221
+ let depth = 0;
222
+ for (let i = start; i < raw.length; i += 1) {
223
+ const char = raw[i];
224
+ if (char === "^") {
225
+ i += 1;
226
+ continue;
227
+ }
228
+ if (char === "[") {
229
+ depth += 1;
230
+ } else if (char === "]") {
231
+ depth -= 1;
232
+ if (depth === 0) {
233
+ return { content: raw.slice(start + 1, i), next: i + 1 };
234
+ }
235
+ }
236
+ }
237
+ throw new ParseError(`\u65B9\u62EC\u53F7\u672A\u95ED\u5408\uFF08${context}\uFF09`);
238
+ }
239
+ function parseInteger(raw, context) {
240
+ if (raw.length > 1 && raw.startsWith("0")) {
241
+ throw new ParseError(`\u6570\u5B57\u4E0D\u5141\u8BB8\u524D\u5BFC\u96F6\uFF1A${JSON.stringify(raw)}\uFF08${context}\uFF09`);
242
+ }
243
+ const value = Number(raw);
244
+ if (!Number.isSafeInteger(value)) {
245
+ throw new ParseError(`\u6570\u5B57\u8D85\u51FA\u5B89\u5168\u6574\u6570\u8303\u56F4\uFF1A${JSON.stringify(raw)}\uFF08${context}\uFF09`);
246
+ }
247
+ return value;
248
+ }
249
+ function parseSegment(raw, context) {
250
+ const steps = [];
251
+ let offset;
252
+ let assertion;
253
+ let index = 0;
254
+ while (index < raw.length) {
255
+ const char = raw[index];
256
+ if (char === "/") {
257
+ if (offset !== void 0) {
258
+ throw new ParseError(`\u5B57\u7B26\u504F\u79FB\u4E4B\u540E\u4E0D\u5141\u8BB8\u518D\u51FA\u73B0\u6B65\u9AA4\uFF08${context}\uFF09`);
259
+ }
260
+ index += 1;
261
+ const digitsStart = index;
262
+ while (isDigit(raw[index])) index += 1;
263
+ if (index === digitsStart) {
264
+ throw new ParseError(`\u6B65\u9AA4\u7F3A\u5C11\u7D22\u5F15\u6570\u5B57\uFF08${context}\uFF09`);
265
+ }
266
+ const stepIndex = parseInteger(raw.slice(digitsStart, index), context);
267
+ const scanned = tryReadAssertion(raw, index, context);
268
+ let step = { index: stepIndex };
269
+ if (scanned !== null) {
270
+ index = scanned.next;
271
+ const parsed = parseAssertion(scanned.content, context);
272
+ if (parsed.trailing !== void 0) {
273
+ throw new FormatError(`\u6B65\u9AA4\u4E0A\u7684\u65AD\u8A00\u4E0D\u63A5\u53D7\u9017\u53F7\u5206\u9694\u7684\u7B2C\u4E8C\u4E2A\u503C\uFF08${context}\uFF09`);
274
+ }
275
+ step = {
276
+ index: stepIndex,
277
+ ...parsed.leading !== void 0 ? { id: parsed.leading } : {},
278
+ ...parsed.side !== void 0 ? { side: parsed.side } : {}
279
+ };
280
+ }
281
+ steps.push(step);
282
+ continue;
283
+ }
284
+ if (char === ":") {
285
+ if (offset !== void 0) {
286
+ throw new ParseError(`\u8DEF\u5F84\u4E2D\u51FA\u73B0\u91CD\u590D\u7684\u5B57\u7B26\u504F\u79FB\uFF08${context}\uFF09`);
287
+ }
288
+ index += 1;
289
+ const digitsStart = index;
290
+ while (isDigit(raw[index])) index += 1;
291
+ if (index === digitsStart) {
292
+ throw new ParseError(`\u5B57\u7B26\u504F\u79FB\u7F3A\u5C11\u6570\u5B57\uFF08${context}\uFF09`);
293
+ }
294
+ offset = parseInteger(raw.slice(digitsStart, index), context);
295
+ const scanned = tryReadAssertion(raw, index, context);
296
+ if (scanned !== null) {
297
+ index = scanned.next;
298
+ assertion = parseAssertion(scanned.content, context);
299
+ }
300
+ continue;
301
+ }
302
+ throw new ParseError(`\u8DEF\u5F84\u4E2D\u51FA\u73B0\u65E0\u6CD5\u8BC6\u522B\u7684\u5B57\u7B26 ${JSON.stringify(char)}\uFF08${context}\uFF09`);
303
+ }
304
+ if (steps.length === 0) {
305
+ throw new ParseError(`\u8DEF\u5F84\u6BB5\u7F3A\u5C11\u6B65\u9AA4\uFF08${context}\uFF09`);
306
+ }
307
+ return {
308
+ paths: [{ steps }],
309
+ ...offset !== void 0 ? { offset } : {},
310
+ ...assertion !== void 0 ? { assertion } : {}
311
+ };
312
+ }
313
+ function parsePath(raw, context) {
314
+ if (raw === "") {
315
+ throw new ParseError(`\u8DEF\u5F84\u4E3A\u7A7A\uFF08${context}\uFF09`);
316
+ }
317
+ const segments = splitTopLevel(raw, "!", context);
318
+ const paths = [];
319
+ let offset;
320
+ let assertion;
321
+ segments.forEach((segment, segmentIndex) => {
322
+ const isLast = segmentIndex === segments.length - 1;
323
+ if (segment === "") {
324
+ throw new ParseError(`'!' \u4E4B\u540E\u7F3A\u5C11\u8DEF\u5F84\u6BB5\uFF08${context}\uFF09`);
325
+ }
326
+ if (!segment.startsWith("/")) {
327
+ throw new ParseError(`\u8DEF\u5F84\u6BB5\u5FC5\u987B\u4EE5 '/' \u5F00\u5934\uFF1A${JSON.stringify(segment)}\uFF08${context}\uFF09`);
328
+ }
329
+ const parsed = parseSegment(segment, context);
330
+ if (isLast) {
331
+ offset = parsed.offset;
332
+ assertion = parsed.assertion;
333
+ } else if (parsed.offset !== void 0) {
334
+ throw new FormatError(`\u975E\u672B\u6BB5\u4E0D\u5141\u8BB8\u51FA\u73B0\u5B57\u7B26\u504F\u79FB\uFF08${context}\uFF09`);
335
+ }
336
+ for (const path of parsed.paths) paths.push(path);
337
+ });
338
+ return {
339
+ paths,
340
+ ...offset !== void 0 ? { offset } : {},
341
+ ...assertion !== void 0 ? { assertion } : {}
342
+ };
343
+ }
344
+ function toPoint(parsed) {
345
+ const draft = {
346
+ kind: "point",
347
+ paths: parsed.paths,
348
+ ...parsed.offset !== void 0 ? { offset: parsed.offset } : {},
349
+ ...parsed.assertion?.leading !== void 0 ? { text: parsed.assertion.leading } : {},
350
+ ...parsed.assertion?.trailing !== void 0 ? { textAfter: parsed.assertion.trailing } : {},
351
+ ...parsed.assertion?.side !== void 0 ? { side: parsed.assertion.side } : {},
352
+ value: ""
353
+ };
354
+ return { ...draft, value: serializePoint(draft) };
355
+ }
356
+ function concatPath(parent, child, context) {
357
+ if (parent.offset !== void 0) {
358
+ throw new FormatError(`\u8303\u56F4\u7236\u8DEF\u5F84\u4E0D\u5141\u8BB8\u5E26\u5B57\u7B26\u504F\u79FB\uFF08${context}\uFF09`);
359
+ }
360
+ const lastIndex = parent.paths.length - 1;
361
+ const lastPath = parent.paths[lastIndex];
362
+ const childFirst = child.paths[0];
363
+ if (lastPath === void 0 || childFirst === void 0) {
364
+ throw new ParseError(`\u8303\u56F4\u8DEF\u5F84\u4E3A\u7A7A\uFF08${context}\uFF09`);
365
+ }
366
+ const merged = { steps: [...lastPath.steps, ...childFirst.steps] };
367
+ return {
368
+ paths: [...parent.paths.slice(0, lastIndex), merged, ...child.paths.slice(1)],
369
+ ...child.offset !== void 0 ? { offset: child.offset } : {},
370
+ ...child.assertion !== void 0 ? { assertion: child.assertion } : {}
371
+ };
372
+ }
373
+ function freezeCfi(cfi) {
374
+ const freezePaths = (paths) => {
375
+ for (const path of paths) {
376
+ for (const step of path.steps) Object.freeze(step);
377
+ Object.freeze(path.steps);
378
+ Object.freeze(path);
379
+ }
380
+ Object.freeze(paths);
381
+ };
382
+ if (cfi.kind === "point") {
383
+ freezePaths(cfi.paths);
384
+ } else {
385
+ freezeCfi(cfi.start);
386
+ freezeCfi(cfi.end);
387
+ if (cfi.parent !== null) freezePaths(cfi.parent);
388
+ }
389
+ return Object.freeze(cfi);
390
+ }
391
+ function stripWrapper(input, context) {
392
+ if (input.startsWith("epubcfi(")) {
393
+ if (!input.endsWith(")")) {
394
+ throw new ParseError(`\u7F3A\u5C11\u7ED3\u5C3E\u7684 ')'\uFF08${context}\uFF09`);
395
+ }
396
+ return input.slice("epubcfi(".length, -1);
397
+ }
398
+ if (input.endsWith(")") && input.includes("(")) {
399
+ throw new ParseError(`CFI \u524D\u7F00\u975E\u6CD5\uFF08\u5E94\u4E3A 'epubcfi('\uFF09\uFF1A${JSON.stringify(input)}\uFF08${context}\uFF09`);
400
+ }
401
+ return input;
402
+ }
403
+ function parseCfi(input) {
404
+ const context = `CFI ${JSON.stringify(input)}`;
405
+ const inner = stripWrapper(input, context);
406
+ if (inner === "") {
407
+ throw new ParseError(`CFI \u5185\u5BB9\u4E3A\u7A7A\uFF08${context}\uFF09`);
408
+ }
409
+ const parts = splitTopLevel(inner, ",", context);
410
+ if (parts.length === 1) {
411
+ return freezeCfi(toPoint(parsePath(parts[0], context)));
412
+ }
413
+ if (parts.length === 2) {
414
+ const [startRaw, endRaw] = parts;
415
+ const draft = {
416
+ kind: "range",
417
+ start: toPoint(parsePath(startRaw, context)),
418
+ end: toPoint(parsePath(endRaw, context)),
419
+ parent: null,
420
+ value: ""
421
+ };
422
+ return freezeCfi({ ...draft, value: serializeCfi(draft) });
423
+ }
424
+ if (parts.length === 3) {
425
+ const [parentRaw, startRaw, endRaw] = parts;
426
+ const parent = parsePath(parentRaw, context);
427
+ const startParsed = startRaw === "" ? parent : concatPath(parent, parsePath(startRaw, context), context);
428
+ const endParsed = endRaw === "" ? parent : concatPath(parent, parsePath(endRaw, context), context);
429
+ const draft = {
430
+ kind: "range",
431
+ start: toPoint(startParsed),
432
+ end: toPoint(endParsed),
433
+ parent: parent.paths,
434
+ value: ""
435
+ };
436
+ return freezeCfi({ ...draft, value: serializeCfi(draft) });
437
+ }
438
+ throw new ParseError(`\u8303\u56F4\u6700\u591A\u63A5\u53D7 3 \u4E2A\u9017\u53F7\u5206\u9694\u7684\u90E8\u5206\uFF0C\u5B9E\u9645 ${parts.length} \u4E2A\uFF08${context}\uFF09`);
439
+ }
440
+
441
+ // src/core/cfi/guards.ts
442
+ function isRecord(value) {
443
+ return typeof value === "object" && value !== null;
444
+ }
445
+ function isPathsLike(value) {
446
+ return Array.isArray(value) && value.every((path) => isRecord(path) && Array.isArray(path["steps"]));
447
+ }
448
+ function isPointLike(value) {
449
+ if (!isRecord(value)) return false;
450
+ return value["kind"] === "point" && isPathsLike(value["paths"]) && typeof value["value"] === "string";
451
+ }
452
+ function isRangeLike(value) {
453
+ if (!isRecord(value)) return false;
454
+ const parent = value["parent"];
455
+ return value["kind"] === "range" && isPointLike(value["start"]) && isPointLike(value["end"]) && (parent === null || isPathsLike(parent)) && typeof value["value"] === "string";
456
+ }
457
+ function isCfi(value) {
458
+ return isPointLike(value) || isRangeLike(value);
459
+ }
460
+ function isCfiString(value) {
461
+ if (typeof value !== "string") return false;
462
+ const trimmed = value.trim();
463
+ if (trimmed === "") return false;
464
+ if (!(trimmed.startsWith("epubcfi(") || trimmed.startsWith("/"))) return false;
465
+ try {
466
+ parseCfi(trimmed);
467
+ return true;
468
+ } catch {
469
+ return false;
470
+ }
471
+ }
472
+
473
+ export { collapseCfi, isCfi, isCfiString, parseCfi, serializeCfi };