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,321 @@
1
+ export { A as AbortError, F as FormatError, a as FrondError, b as FrondErrorCode, c as FrondErrorOptions, N as NetworkError, P as ParseError, S as SecurityError, d as StateError, e as createAbortError, i as isAbortError, f as isFrondError, l as linkSignals, o as onAbort, t as throwIfAborted } from '../abort-BY8vBk0v.cjs';
2
+ import { P as ParseZipOptions, Z as ZipArchive } from '../types-BH88rUYt.cjs';
3
+ export { a as ZipCompressionMethod, b as ZipEntry } from '../types-BH88rUYt.cjs';
4
+ export { O as OperationOptions } from '../types-DQYmArgv.cjs';
5
+ export { a as Book, b as BookFormat, c as BookMetadata, B as BookSource, C as ChapterItem, M as ManifestItem, d as MemoryBookSourceOptions, e as ReadingDirection, R as ResourceIO, f as ResourceItem, S as SpineItem, T as TocItem, g as createMemoryBookSource } from '../types-CPUqTEPW.cjs';
6
+ import { C as Cfi } from '../types-B7mslPBY.cjs';
7
+ export { a as CfiPath, b as CfiPoint, c as CfiRange, d as CfiStep } from '../types-B7mslPBY.cjs';
8
+
9
+ /**
10
+ * XML 解析的类型定义(ADR-0006)。
11
+ *
12
+ * 设计约束:
13
+ * - **不实现完整 DOM**,只覆盖 EPUB 所需的受控子集(`container.xml` / OPF / NAV / NCX / SMIL)
14
+ * - **零 DOM、零运行时依赖**(H4 + H12),因此不使用 `DOMParser` 等平台 API
15
+ * - **命名空间只做前缀分离**,不做完整的 URI 解析与校验 —— EPUB 场景下按 `localName` 查找已足够
16
+ *
17
+ * 与标准 DOM 的差异是**有意的**:本模块产出的是解析结果,不是可变的活文档。
18
+ * 所有字段均为 `readonly`,调用方无法通过它修改文档结构。
19
+ */
20
+ /** 文本节点。 */
21
+ interface XmlText {
22
+ readonly type: 'text';
23
+ /** 文本内容。非 CDATA 段已完成实体解码。 */
24
+ readonly value: string;
25
+ /**
26
+ * 是否来自 CDATA 段。
27
+ *
28
+ * CDATA 内容**不做**实体解码(`&` 保持字面量),因此需要与普通文本区分。
29
+ */
30
+ readonly cdata: boolean;
31
+ }
32
+ /** 元素节点。 */
33
+ interface XmlElement {
34
+ readonly type: 'element';
35
+ /** 原始标签名,含前缀。例如 `dc:title`。 */
36
+ readonly name: string;
37
+ /** 命名空间前缀。无前缀时为空字符串。 */
38
+ readonly prefix: string;
39
+ /** 去掉前缀的标签名。例如 `title`。查询请优先使用本字段。 */
40
+ readonly localName: string;
41
+ /**
42
+ * 属性表:原始属性名 → 属性值。
43
+ *
44
+ * - 键含前缀,例如 `xmlns:dc`、`full-path`
45
+ * - 值已完成实体解码
46
+ * - 保持文档中的出现顺序
47
+ */
48
+ readonly attributes: ReadonlyMap<string, string>;
49
+ /** 子节点,按文档顺序。 */
50
+ readonly children: readonly XmlNode[];
51
+ }
52
+ /** XML 节点。 */
53
+ type XmlNode = XmlElement | XmlText;
54
+ /** 解析选项。 */
55
+ interface ParseXmlOptions {
56
+ /**
57
+ * 源标识,用于错误定位(REQ-EPUB-007)。
58
+ *
59
+ * 通常传入文件路径或条目名,例如 `META-INF/container.xml`。
60
+ * 解析失败时该值会出现在 `ParseError.message` 中。
61
+ */
62
+ readonly source?: string;
63
+ /**
64
+ * **元素**嵌套层数上限,根元素算第 1 层。
65
+ *
66
+ * 存在它只有一个理由:XML 解析是递归下降的,而输入**来自陌生人给的文件** ⇒
67
+ * 不设上限时「10 KB 的 `<n><n><n>…`」会把宿主进程拖进 `RangeError: Maximum call
68
+ * stack size exceeded` —— 那是**引擎崩溃**,不是「这本书解析失败」,既不返回给调用方
69
+ * (绕过 `FrondError` 体系),也救不回同一线程上别的书。
70
+ *
71
+ * 超限抛 `ParseError`,**发生在读到越界的那个开始标签时**(不等闭合标签)。
72
+ * 兄弟节点与自闭合元素不增加深度,故大量同层节点不受影响。
73
+ *
74
+ * @defaultValue 512 —— 真实 XHTML 章节的常见深度在两位数以内,本仓最深的用例是 300 层,
75
+ * 512 留了约 5 倍余量。
76
+ */
77
+ readonly maxDepth?: number;
78
+ }
79
+
80
+ /**
81
+ * 极简 XML 解析器(ADR-0006)。
82
+ *
83
+ * 设计取舍:
84
+ * - **单遍扫描**,不做 token 预切分;用 `charCodeAt` 判断而非正则,避免回溯
85
+ * - **不实现完整 DOM**:无命名空间 URI 解析、无 DTD 校验、无外部实体
86
+ * - **不静默降级**:任何非法结构一律抛 `ParseError`(对照 FI-005)
87
+ * - **递归深度有上限**:默认 512 层(`options.maxDepth`),超限同样抛 `ParseError` ——
88
+ * 输入是不可信文件,不设上限就是让一本畸形书有机会以 `RangeError` 打挂宿主进程
89
+ * - **零 DOM**:只用字符串操作,可在 Node / Worker / 浏览器一致运行
90
+ *
91
+ * 规范子集(EPUB 实际需要):
92
+ * - XML 声明、DOCTYPE(含 PUBLIC 与内部子集,均跳过不解析)
93
+ * - 注释、处理指令(跳过)
94
+ * - CDATA(保留原文,不解码实体)
95
+ * - 元素 / 属性 / 文本、自闭合元素
96
+ * - 5 个预定义实体 + 十进制 / 十六进制字符引用
97
+ * - 换行规范化(XML 1.0 §2.11):字面 CRLF、CR → LF;字符引用产生的 CR 不受影响
98
+ *
99
+ * 有意偏离规范之处(宽容处理,便于兼容真实世界文件):
100
+ * - 结束标记允许尾随空白,如 `</a >`
101
+ * - 未识别实体与非法字符引用按字面量保留,而非报错
102
+ */
103
+
104
+ /**
105
+ * 解析 XML 文本。
106
+ *
107
+ * 文档必须恰好包含一个根元素。解析失败时抛 {@link ParseError},
108
+ * message 中包含 `options.source` 与出错偏移量,便于定位(REQ-EPUB-007)。
109
+ *
110
+ * @param source - XML 文本。
111
+ * @param options - 解析选项;`source` 用于错误定位,`maxDepth` 限定嵌套层数。
112
+ * @returns 根元素。
113
+ * @throws {ParseError} 文档结构非法,**或嵌套超过 `maxDepth`**(默认 512 层)——
114
+ * 后者是有意为之:不设上限时畸形文档会以 `RangeError` 打挂宿主进程,而不是「解析失败」。
115
+ */
116
+ declare function parseXml(source: string, options?: ParseXmlOptions): XmlElement;
117
+
118
+ /**
119
+ * XML 节点查询辅助。
120
+ *
121
+ * 这些函数是纯遍历,不修改文档。查询一律基于 `localName`,
122
+ * 忽略命名空间前缀 —— 对 EPUB 的 OPF / NAV / NCX 而言这已足够
123
+ * (见 ADR-0006 的取舍说明)。
124
+ */
125
+
126
+ /**
127
+ * 按 `localName` 深度优先查找全部匹配元素。
128
+ *
129
+ * 匹配范围为 `root` 的**后代**,不含 `root` 自身。
130
+ *
131
+ * @param root - 查找起点。
132
+ * @param localName - 目标标签名(不含前缀)。
133
+ * @returns 按文档顺序排列的匹配元素;无匹配时为空数组。
134
+ */
135
+ declare function findAll(root: XmlElement, localName: string): XmlElement[];
136
+ /**
137
+ * 按 `localName` 查找首个匹配元素。
138
+ *
139
+ * @returns 首个匹配元素;无匹配时返回 `undefined`。
140
+ */
141
+ declare function findFirst(root: XmlElement, localName: string): XmlElement | undefined;
142
+ /**
143
+ * 读取元素属性。
144
+ *
145
+ * @param element - 目标元素。
146
+ * @param name - 属性名,**含前缀**(如 `xmlns:dc`)。
147
+ * @returns 属性值;属性不存在时返回 `undefined`。
148
+ */
149
+ declare function getAttribute(element: XmlElement, name: string): string | undefined;
150
+ /**
151
+ * 递归拼接元素的全部后代文本。
152
+ *
153
+ * CDATA 段的内容同样计入。不做空白规范化 —— 需要时由调用方处理。
154
+ *
155
+ * @returns 拼接后的文本;无文本节点时返回空字符串。
156
+ */
157
+ declare function getText(element: XmlElement): string;
158
+ /** 判断节点是否为元素。 */
159
+ declare function isElement(node: XmlNode): node is XmlElement;
160
+
161
+ /**
162
+ * ZIP 读取器(ADR-0007)。
163
+ *
164
+ * 实现策略:
165
+ * - **只信中央目录**:条目尺寸一律取自 central directory,忽略 local header 中的值。
166
+ * 流式写入的 ZIP(bit 3 = data descriptor)其 local header 尺寸恒为 0,因此不可信。
167
+ * - **单遍定位 EOCD**:从尾部倒扫,并用「注释长度必须恰好补齐文件末尾」校验,
168
+ * 避免把注释区里偶然出现的 EOCD 签名字节误判为记录头。
169
+ * - **不静默降级**:结构损坏抛 `ParseError`,不支持的特性抛 `FormatError`(对照 FI-005)。
170
+ * - **条目名与查询键同一个函数产出**(FI-X22):中央目录里的名字在**解析那一刻**就送
171
+ * `normalizeArchivePath` 归一化,因此 `entries[].name`、探测层看到的名字、
172
+ * `entry()` / `read()` 的索引键与调用方传进来的路径**是同一份口径**。
173
+ *
174
+ * 已知边界(ADR-0007):不支持 ZIP64 与加密条目 —— 检测到即抛错,不按 32 位误解析。
175
+ */
176
+
177
+ /**
178
+ * 解析 ZIP 归档。
179
+ *
180
+ * 只读取中央目录,不解压任何条目 —— 解压发生在 {@link ZipArchive.read}。
181
+ *
182
+ * @param bytes - 完整 ZIP 字节。归档存活期间不要修改它。
183
+ * @param options - `source` 用于错误定位;`maxEntrySizeBytes` 限定单个条目的解压产物。
184
+ * @returns 只读归档视图。条目名已归一化(见 {@link ZipEntry.name})。
185
+ * @throws {ParseError} 结构损坏(EOCD 缺失、签名错、偏移越界)。
186
+ * @throws {FormatError} 不支持的特性(压缩方法非 0/8、加密、ZIP64),**或条目名不是合法的
187
+ * 归档内路径 / 两条条目名归一化后互相歧义**(FI-X22)。
188
+ */
189
+ declare function parseZip(bytes: Uint8Array, options?: ParseZipOptions): ZipArchive;
190
+
191
+ /**
192
+ * 归档内路径解析。
193
+ *
194
+ * 这是**跨格式能力**:EPUB 的 rootfile `full-path`、manifest `href`,
195
+ * 以及 P3 的 FB2 / CBZ 资源定位都要用同一套规则,因此归 `core` 而非某个格式包
196
+ * (ARCHITECTURE §1 划分判据:若多种格式都要用则属 core)。
197
+ *
198
+ * 统一口径(FI-X22 起,本模块是归档路径的**唯一**归一化点):
199
+ * - `\` 归一化为 `/`
200
+ * - 忽略空段与 `.` 段
201
+ * - `..` 仅在归档根目录之内回退;越界即抛 `FormatError`(防目录穿越)
202
+ * - **不做**百分号解码(ZIP 条目名本身不编码,解码会指向不存在的条目)
203
+ * - 拒绝空路径、规范化后为空的路径、控制字符
204
+ *
205
+ * 消费者(全部走同一份规则):`core/zip/parse.ts` 的**条目名**(FI-X22 起,
206
+ * 在解析中央目录那一刻就送进来,故 `entries[].name` 与查询键不可能分叉)、
207
+ * EPUB 的 `container.ts` / `opf.ts` / `nav.ts`,以及渲染层的 `resources.ts`。
208
+ *
209
+ * 全部失败抛 `FormatError` —— 路径非法属「不符合规范」,而非「数据损坏」。
210
+ *
211
+ * 本模块零 DOM、零运行时依赖(H4 / H12)。
212
+ */
213
+ /** 路径解析的上下文,仅用于生成可定位的错误消息(REQ-EPUB-007)。 */
214
+ interface ArchivePathOptions {
215
+ /** 源标识,通常是文件名或 URL。 */
216
+ readonly source?: string;
217
+ /**
218
+ * 该路径在文档中的语义名,用于错误消息,例如 `rootfile 的 full-path`、`manifest href`。
219
+ *
220
+ * @defaultValue `'归档路径'`
221
+ */
222
+ readonly label?: string;
223
+ }
224
+ /**
225
+ * 把归档内路径规范化到「相对归档根」的绝对形式。
226
+ *
227
+ * @param rawPath - 原始路径,可含前导 `/`、`\`、`.` 与 `..`。
228
+ * @param options - 错误消息上下文。
229
+ * @returns 规范化后的路径,**不含前导 `/`**。
230
+ * @throws {FormatError} 路径为空、规范化后为空、含控制字符、或 `..` 越出归档根。
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * normalizeArchivePath('/OPS/./package.opf'); // 'OPS/package.opf'
235
+ * normalizeArchivePath('OPS\\a.opf'); // 'OPS/a.opf'
236
+ * normalizeArchivePath('OPS/../../a.opf'); // throws FormatError
237
+ * ```
238
+ */
239
+ declare function normalizeArchivePath(rawPath: string, options?: ArchivePathOptions): string;
240
+ /**
241
+ * 相对基准目录解析一个 href。
242
+ *
243
+ * @param basePath - 基准目录(**不含**文件名),例如 OPF 所在目录 `'OPS'`。空串表示归档根。
244
+ * @param href - 待解析的引用,可含前导 `/`(视为相对归档根的绝对路径)。
245
+ * @param options - 错误消息上下文。
246
+ * @returns 规范化后的归档内路径,**不含前导 `/`**。
247
+ * @throws {FormatError} href **不是相对引用**(带 scheme、协议相对 `//…`、或纯 fragment)、
248
+ * href 未指名任何条目、或解析结果越出归档根。
249
+ *
250
+ * ⚠️ 「必须是相对引用」这条判据立在**解析引用**这一侧,**不**立在
251
+ * {@link normalizeArchivePath} 那一侧 —— 后者现在还服务 ZIP 的**条目名**归一化
252
+ * (FI-X22 / 批次 E-8),而 `a:b.png` 这类带冒号的文件名在归档里完全合法(POSIX 允许 `:`)。
253
+ * 把 scheme 判据塞进归一化,等于让一整本档案因为一个合法文件名打不开。
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * resolveArchivePath('OPS', 'images/a.png'); // 'OPS/images/a.png'
258
+ * resolveArchivePath('OPS/sub', '../a.opf'); // 'OPS/a.opf'
259
+ * resolveArchivePath('OPS', '/images/a.png'); // 'images/a.png'
260
+ * resolveArchivePath('OPS', 'http://x/a.png'); // throws FormatError
261
+ * ```
262
+ */
263
+ declare function resolveArchivePath(basePath: string, href: string, options?: ArchivePathOptions): string;
264
+ /**
265
+ * 取归档内路径的所在目录。
266
+ *
267
+ * 纯字符串操作,不做规范化、不抛错 —— 供调用方从 `opfPath` 推导 `basePath`。
268
+ *
269
+ * @param archivePath - 归档内路径。
270
+ * @returns 目录部分(不含结尾 `/`);根目录下的文件返回空串。
271
+ *
272
+ * @example
273
+ * ```ts
274
+ * directoryOf('OPS/package.opf'); // 'OPS'
275
+ * directoryOf('OPS/sub/book.opf'); // 'OPS/sub'
276
+ * directoryOf('package.opf'); // ''
277
+ * ```
278
+ */
279
+ declare function directoryOf(archivePath: string): string;
280
+
281
+ /**
282
+ * 把 CFI 折叠为单点字符串。
283
+ *
284
+ * - 单点入参:原样返回其规范字符串(`toEnd` 无影响)
285
+ * - 范围入参:`toEnd` 为 `false`(默认)取起点,为 `true` 取终点
286
+ *
287
+ * 返回**含 `epubcfi()` 前缀**的字符串(与基线 `collapse` 的返回口径一致)。
288
+ */
289
+ declare function collapseCfi(cfi: Cfi, toEnd?: boolean): string;
290
+
291
+ /**
292
+ * 判定任意值是否为**已解析且结构完整**的 CFI 对象。
293
+ *
294
+ * 不只检查 `kind` —— 仅凭 `kind` 判定会让 `{ kind: 'point' }` 这类半成品蒙混过关,
295
+ * 消费方随后在 `paths` 上解引用就会崩。这里做结构校验,使判定结果真正可用。
296
+ */
297
+ declare function isCfi(value: unknown): value is Cfi;
298
+ /**
299
+ * 判定字符串是否为**可解析**的 CFI。
300
+ *
301
+ * 以「能否被 {@link parseCfi} 解析」为唯一判据 —— 不另立一套宽松的语法,
302
+ * 避免判定与解析口径漂移。因此对畸形 CFI(如 `epubcfi(/a/b)`)返回 `false`。
303
+ */
304
+ declare function isCfiString(value: unknown): value is string;
305
+
306
+ /**
307
+ * 解析 CFI 字符串。
308
+ *
309
+ * 接受带 `epubcfi()` 前缀或裸字符串两种入参。任何无法解析的输入抛
310
+ * `ParseError`;语法合法但违反规范的抛 `FormatError`。返回值为**冻结**对象。
311
+ */
312
+ declare function parseCfi(input: string): Cfi;
313
+
314
+ /**
315
+ * 序列化为规范 CFI 字符串(含 `epubcfi()` 前缀)。
316
+ *
317
+ * 结果与 `parseCfi()` 产出的 `value` 一致 —— 该不变式由测试保证。
318
+ */
319
+ declare function serializeCfi(cfi: Cfi): string;
320
+
321
+ export { type ArchivePathOptions, Cfi, type ParseXmlOptions, ParseZipOptions, type XmlElement, type XmlNode, type XmlText, ZipArchive, collapseCfi, directoryOf, findAll, findFirst, getAttribute, getText, isCfi, isCfiString, isElement, normalizeArchivePath, parseCfi, parseXml, parseZip, resolveArchivePath, serializeCfi };
@@ -0,0 +1,321 @@
1
+ export { A as AbortError, F as FormatError, a as FrondError, b as FrondErrorCode, c as FrondErrorOptions, N as NetworkError, P as ParseError, S as SecurityError, d as StateError, e as createAbortError, i as isAbortError, f as isFrondError, l as linkSignals, o as onAbort, t as throwIfAborted } from '../abort-BY8vBk0v.js';
2
+ import { P as ParseZipOptions, Z as ZipArchive } from '../types-B76GOMxj.js';
3
+ export { a as ZipCompressionMethod, b as ZipEntry } from '../types-B76GOMxj.js';
4
+ export { O as OperationOptions } from '../types-DQYmArgv.js';
5
+ export { a as Book, b as BookFormat, c as BookMetadata, B as BookSource, C as ChapterItem, M as ManifestItem, d as MemoryBookSourceOptions, e as ReadingDirection, R as ResourceIO, f as ResourceItem, S as SpineItem, T as TocItem, g as createMemoryBookSource } from '../types-C-5eHSRH.js';
6
+ import { C as Cfi } from '../types-B7mslPBY.js';
7
+ export { a as CfiPath, b as CfiPoint, c as CfiRange, d as CfiStep } from '../types-B7mslPBY.js';
8
+
9
+ /**
10
+ * XML 解析的类型定义(ADR-0006)。
11
+ *
12
+ * 设计约束:
13
+ * - **不实现完整 DOM**,只覆盖 EPUB 所需的受控子集(`container.xml` / OPF / NAV / NCX / SMIL)
14
+ * - **零 DOM、零运行时依赖**(H4 + H12),因此不使用 `DOMParser` 等平台 API
15
+ * - **命名空间只做前缀分离**,不做完整的 URI 解析与校验 —— EPUB 场景下按 `localName` 查找已足够
16
+ *
17
+ * 与标准 DOM 的差异是**有意的**:本模块产出的是解析结果,不是可变的活文档。
18
+ * 所有字段均为 `readonly`,调用方无法通过它修改文档结构。
19
+ */
20
+ /** 文本节点。 */
21
+ interface XmlText {
22
+ readonly type: 'text';
23
+ /** 文本内容。非 CDATA 段已完成实体解码。 */
24
+ readonly value: string;
25
+ /**
26
+ * 是否来自 CDATA 段。
27
+ *
28
+ * CDATA 内容**不做**实体解码(`&amp;` 保持字面量),因此需要与普通文本区分。
29
+ */
30
+ readonly cdata: boolean;
31
+ }
32
+ /** 元素节点。 */
33
+ interface XmlElement {
34
+ readonly type: 'element';
35
+ /** 原始标签名,含前缀。例如 `dc:title`。 */
36
+ readonly name: string;
37
+ /** 命名空间前缀。无前缀时为空字符串。 */
38
+ readonly prefix: string;
39
+ /** 去掉前缀的标签名。例如 `title`。查询请优先使用本字段。 */
40
+ readonly localName: string;
41
+ /**
42
+ * 属性表:原始属性名 → 属性值。
43
+ *
44
+ * - 键含前缀,例如 `xmlns:dc`、`full-path`
45
+ * - 值已完成实体解码
46
+ * - 保持文档中的出现顺序
47
+ */
48
+ readonly attributes: ReadonlyMap<string, string>;
49
+ /** 子节点,按文档顺序。 */
50
+ readonly children: readonly XmlNode[];
51
+ }
52
+ /** XML 节点。 */
53
+ type XmlNode = XmlElement | XmlText;
54
+ /** 解析选项。 */
55
+ interface ParseXmlOptions {
56
+ /**
57
+ * 源标识,用于错误定位(REQ-EPUB-007)。
58
+ *
59
+ * 通常传入文件路径或条目名,例如 `META-INF/container.xml`。
60
+ * 解析失败时该值会出现在 `ParseError.message` 中。
61
+ */
62
+ readonly source?: string;
63
+ /**
64
+ * **元素**嵌套层数上限,根元素算第 1 层。
65
+ *
66
+ * 存在它只有一个理由:XML 解析是递归下降的,而输入**来自陌生人给的文件** ⇒
67
+ * 不设上限时「10 KB 的 `<n><n><n>…`」会把宿主进程拖进 `RangeError: Maximum call
68
+ * stack size exceeded` —— 那是**引擎崩溃**,不是「这本书解析失败」,既不返回给调用方
69
+ * (绕过 `FrondError` 体系),也救不回同一线程上别的书。
70
+ *
71
+ * 超限抛 `ParseError`,**发生在读到越界的那个开始标签时**(不等闭合标签)。
72
+ * 兄弟节点与自闭合元素不增加深度,故大量同层节点不受影响。
73
+ *
74
+ * @defaultValue 512 —— 真实 XHTML 章节的常见深度在两位数以内,本仓最深的用例是 300 层,
75
+ * 512 留了约 5 倍余量。
76
+ */
77
+ readonly maxDepth?: number;
78
+ }
79
+
80
+ /**
81
+ * 极简 XML 解析器(ADR-0006)。
82
+ *
83
+ * 设计取舍:
84
+ * - **单遍扫描**,不做 token 预切分;用 `charCodeAt` 判断而非正则,避免回溯
85
+ * - **不实现完整 DOM**:无命名空间 URI 解析、无 DTD 校验、无外部实体
86
+ * - **不静默降级**:任何非法结构一律抛 `ParseError`(对照 FI-005)
87
+ * - **递归深度有上限**:默认 512 层(`options.maxDepth`),超限同样抛 `ParseError` ——
88
+ * 输入是不可信文件,不设上限就是让一本畸形书有机会以 `RangeError` 打挂宿主进程
89
+ * - **零 DOM**:只用字符串操作,可在 Node / Worker / 浏览器一致运行
90
+ *
91
+ * 规范子集(EPUB 实际需要):
92
+ * - XML 声明、DOCTYPE(含 PUBLIC 与内部子集,均跳过不解析)
93
+ * - 注释、处理指令(跳过)
94
+ * - CDATA(保留原文,不解码实体)
95
+ * - 元素 / 属性 / 文本、自闭合元素
96
+ * - 5 个预定义实体 + 十进制 / 十六进制字符引用
97
+ * - 换行规范化(XML 1.0 §2.11):字面 CRLF、CR → LF;字符引用产生的 CR 不受影响
98
+ *
99
+ * 有意偏离规范之处(宽容处理,便于兼容真实世界文件):
100
+ * - 结束标记允许尾随空白,如 `</a >`
101
+ * - 未识别实体与非法字符引用按字面量保留,而非报错
102
+ */
103
+
104
+ /**
105
+ * 解析 XML 文本。
106
+ *
107
+ * 文档必须恰好包含一个根元素。解析失败时抛 {@link ParseError},
108
+ * message 中包含 `options.source` 与出错偏移量,便于定位(REQ-EPUB-007)。
109
+ *
110
+ * @param source - XML 文本。
111
+ * @param options - 解析选项;`source` 用于错误定位,`maxDepth` 限定嵌套层数。
112
+ * @returns 根元素。
113
+ * @throws {ParseError} 文档结构非法,**或嵌套超过 `maxDepth`**(默认 512 层)——
114
+ * 后者是有意为之:不设上限时畸形文档会以 `RangeError` 打挂宿主进程,而不是「解析失败」。
115
+ */
116
+ declare function parseXml(source: string, options?: ParseXmlOptions): XmlElement;
117
+
118
+ /**
119
+ * XML 节点查询辅助。
120
+ *
121
+ * 这些函数是纯遍历,不修改文档。查询一律基于 `localName`,
122
+ * 忽略命名空间前缀 —— 对 EPUB 的 OPF / NAV / NCX 而言这已足够
123
+ * (见 ADR-0006 的取舍说明)。
124
+ */
125
+
126
+ /**
127
+ * 按 `localName` 深度优先查找全部匹配元素。
128
+ *
129
+ * 匹配范围为 `root` 的**后代**,不含 `root` 自身。
130
+ *
131
+ * @param root - 查找起点。
132
+ * @param localName - 目标标签名(不含前缀)。
133
+ * @returns 按文档顺序排列的匹配元素;无匹配时为空数组。
134
+ */
135
+ declare function findAll(root: XmlElement, localName: string): XmlElement[];
136
+ /**
137
+ * 按 `localName` 查找首个匹配元素。
138
+ *
139
+ * @returns 首个匹配元素;无匹配时返回 `undefined`。
140
+ */
141
+ declare function findFirst(root: XmlElement, localName: string): XmlElement | undefined;
142
+ /**
143
+ * 读取元素属性。
144
+ *
145
+ * @param element - 目标元素。
146
+ * @param name - 属性名,**含前缀**(如 `xmlns:dc`)。
147
+ * @returns 属性值;属性不存在时返回 `undefined`。
148
+ */
149
+ declare function getAttribute(element: XmlElement, name: string): string | undefined;
150
+ /**
151
+ * 递归拼接元素的全部后代文本。
152
+ *
153
+ * CDATA 段的内容同样计入。不做空白规范化 —— 需要时由调用方处理。
154
+ *
155
+ * @returns 拼接后的文本;无文本节点时返回空字符串。
156
+ */
157
+ declare function getText(element: XmlElement): string;
158
+ /** 判断节点是否为元素。 */
159
+ declare function isElement(node: XmlNode): node is XmlElement;
160
+
161
+ /**
162
+ * ZIP 读取器(ADR-0007)。
163
+ *
164
+ * 实现策略:
165
+ * - **只信中央目录**:条目尺寸一律取自 central directory,忽略 local header 中的值。
166
+ * 流式写入的 ZIP(bit 3 = data descriptor)其 local header 尺寸恒为 0,因此不可信。
167
+ * - **单遍定位 EOCD**:从尾部倒扫,并用「注释长度必须恰好补齐文件末尾」校验,
168
+ * 避免把注释区里偶然出现的 EOCD 签名字节误判为记录头。
169
+ * - **不静默降级**:结构损坏抛 `ParseError`,不支持的特性抛 `FormatError`(对照 FI-005)。
170
+ * - **条目名与查询键同一个函数产出**(FI-X22):中央目录里的名字在**解析那一刻**就送
171
+ * `normalizeArchivePath` 归一化,因此 `entries[].name`、探测层看到的名字、
172
+ * `entry()` / `read()` 的索引键与调用方传进来的路径**是同一份口径**。
173
+ *
174
+ * 已知边界(ADR-0007):不支持 ZIP64 与加密条目 —— 检测到即抛错,不按 32 位误解析。
175
+ */
176
+
177
+ /**
178
+ * 解析 ZIP 归档。
179
+ *
180
+ * 只读取中央目录,不解压任何条目 —— 解压发生在 {@link ZipArchive.read}。
181
+ *
182
+ * @param bytes - 完整 ZIP 字节。归档存活期间不要修改它。
183
+ * @param options - `source` 用于错误定位;`maxEntrySizeBytes` 限定单个条目的解压产物。
184
+ * @returns 只读归档视图。条目名已归一化(见 {@link ZipEntry.name})。
185
+ * @throws {ParseError} 结构损坏(EOCD 缺失、签名错、偏移越界)。
186
+ * @throws {FormatError} 不支持的特性(压缩方法非 0/8、加密、ZIP64),**或条目名不是合法的
187
+ * 归档内路径 / 两条条目名归一化后互相歧义**(FI-X22)。
188
+ */
189
+ declare function parseZip(bytes: Uint8Array, options?: ParseZipOptions): ZipArchive;
190
+
191
+ /**
192
+ * 归档内路径解析。
193
+ *
194
+ * 这是**跨格式能力**:EPUB 的 rootfile `full-path`、manifest `href`,
195
+ * 以及 P3 的 FB2 / CBZ 资源定位都要用同一套规则,因此归 `core` 而非某个格式包
196
+ * (ARCHITECTURE §1 划分判据:若多种格式都要用则属 core)。
197
+ *
198
+ * 统一口径(FI-X22 起,本模块是归档路径的**唯一**归一化点):
199
+ * - `\` 归一化为 `/`
200
+ * - 忽略空段与 `.` 段
201
+ * - `..` 仅在归档根目录之内回退;越界即抛 `FormatError`(防目录穿越)
202
+ * - **不做**百分号解码(ZIP 条目名本身不编码,解码会指向不存在的条目)
203
+ * - 拒绝空路径、规范化后为空的路径、控制字符
204
+ *
205
+ * 消费者(全部走同一份规则):`core/zip/parse.ts` 的**条目名**(FI-X22 起,
206
+ * 在解析中央目录那一刻就送进来,故 `entries[].name` 与查询键不可能分叉)、
207
+ * EPUB 的 `container.ts` / `opf.ts` / `nav.ts`,以及渲染层的 `resources.ts`。
208
+ *
209
+ * 全部失败抛 `FormatError` —— 路径非法属「不符合规范」,而非「数据损坏」。
210
+ *
211
+ * 本模块零 DOM、零运行时依赖(H4 / H12)。
212
+ */
213
+ /** 路径解析的上下文,仅用于生成可定位的错误消息(REQ-EPUB-007)。 */
214
+ interface ArchivePathOptions {
215
+ /** 源标识,通常是文件名或 URL。 */
216
+ readonly source?: string;
217
+ /**
218
+ * 该路径在文档中的语义名,用于错误消息,例如 `rootfile 的 full-path`、`manifest href`。
219
+ *
220
+ * @defaultValue `'归档路径'`
221
+ */
222
+ readonly label?: string;
223
+ }
224
+ /**
225
+ * 把归档内路径规范化到「相对归档根」的绝对形式。
226
+ *
227
+ * @param rawPath - 原始路径,可含前导 `/`、`\`、`.` 与 `..`。
228
+ * @param options - 错误消息上下文。
229
+ * @returns 规范化后的路径,**不含前导 `/`**。
230
+ * @throws {FormatError} 路径为空、规范化后为空、含控制字符、或 `..` 越出归档根。
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * normalizeArchivePath('/OPS/./package.opf'); // 'OPS/package.opf'
235
+ * normalizeArchivePath('OPS\\a.opf'); // 'OPS/a.opf'
236
+ * normalizeArchivePath('OPS/../../a.opf'); // throws FormatError
237
+ * ```
238
+ */
239
+ declare function normalizeArchivePath(rawPath: string, options?: ArchivePathOptions): string;
240
+ /**
241
+ * 相对基准目录解析一个 href。
242
+ *
243
+ * @param basePath - 基准目录(**不含**文件名),例如 OPF 所在目录 `'OPS'`。空串表示归档根。
244
+ * @param href - 待解析的引用,可含前导 `/`(视为相对归档根的绝对路径)。
245
+ * @param options - 错误消息上下文。
246
+ * @returns 规范化后的归档内路径,**不含前导 `/`**。
247
+ * @throws {FormatError} href **不是相对引用**(带 scheme、协议相对 `//…`、或纯 fragment)、
248
+ * href 未指名任何条目、或解析结果越出归档根。
249
+ *
250
+ * ⚠️ 「必须是相对引用」这条判据立在**解析引用**这一侧,**不**立在
251
+ * {@link normalizeArchivePath} 那一侧 —— 后者现在还服务 ZIP 的**条目名**归一化
252
+ * (FI-X22 / 批次 E-8),而 `a:b.png` 这类带冒号的文件名在归档里完全合法(POSIX 允许 `:`)。
253
+ * 把 scheme 判据塞进归一化,等于让一整本档案因为一个合法文件名打不开。
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * resolveArchivePath('OPS', 'images/a.png'); // 'OPS/images/a.png'
258
+ * resolveArchivePath('OPS/sub', '../a.opf'); // 'OPS/a.opf'
259
+ * resolveArchivePath('OPS', '/images/a.png'); // 'images/a.png'
260
+ * resolveArchivePath('OPS', 'http://x/a.png'); // throws FormatError
261
+ * ```
262
+ */
263
+ declare function resolveArchivePath(basePath: string, href: string, options?: ArchivePathOptions): string;
264
+ /**
265
+ * 取归档内路径的所在目录。
266
+ *
267
+ * 纯字符串操作,不做规范化、不抛错 —— 供调用方从 `opfPath` 推导 `basePath`。
268
+ *
269
+ * @param archivePath - 归档内路径。
270
+ * @returns 目录部分(不含结尾 `/`);根目录下的文件返回空串。
271
+ *
272
+ * @example
273
+ * ```ts
274
+ * directoryOf('OPS/package.opf'); // 'OPS'
275
+ * directoryOf('OPS/sub/book.opf'); // 'OPS/sub'
276
+ * directoryOf('package.opf'); // ''
277
+ * ```
278
+ */
279
+ declare function directoryOf(archivePath: string): string;
280
+
281
+ /**
282
+ * 把 CFI 折叠为单点字符串。
283
+ *
284
+ * - 单点入参:原样返回其规范字符串(`toEnd` 无影响)
285
+ * - 范围入参:`toEnd` 为 `false`(默认)取起点,为 `true` 取终点
286
+ *
287
+ * 返回**含 `epubcfi()` 前缀**的字符串(与基线 `collapse` 的返回口径一致)。
288
+ */
289
+ declare function collapseCfi(cfi: Cfi, toEnd?: boolean): string;
290
+
291
+ /**
292
+ * 判定任意值是否为**已解析且结构完整**的 CFI 对象。
293
+ *
294
+ * 不只检查 `kind` —— 仅凭 `kind` 判定会让 `{ kind: 'point' }` 这类半成品蒙混过关,
295
+ * 消费方随后在 `paths` 上解引用就会崩。这里做结构校验,使判定结果真正可用。
296
+ */
297
+ declare function isCfi(value: unknown): value is Cfi;
298
+ /**
299
+ * 判定字符串是否为**可解析**的 CFI。
300
+ *
301
+ * 以「能否被 {@link parseCfi} 解析」为唯一判据 —— 不另立一套宽松的语法,
302
+ * 避免判定与解析口径漂移。因此对畸形 CFI(如 `epubcfi(/a/b)`)返回 `false`。
303
+ */
304
+ declare function isCfiString(value: unknown): value is string;
305
+
306
+ /**
307
+ * 解析 CFI 字符串。
308
+ *
309
+ * 接受带 `epubcfi()` 前缀或裸字符串两种入参。任何无法解析的输入抛
310
+ * `ParseError`;语法合法但违反规范的抛 `FormatError`。返回值为**冻结**对象。
311
+ */
312
+ declare function parseCfi(input: string): Cfi;
313
+
314
+ /**
315
+ * 序列化为规范 CFI 字符串(含 `epubcfi()` 前缀)。
316
+ *
317
+ * 结果与 `parseCfi()` 产出的 `value` 一致 —— 该不变式由测试保证。
318
+ */
319
+ declare function serializeCfi(cfi: Cfi): string;
320
+
321
+ export { type ArchivePathOptions, Cfi, type ParseXmlOptions, ParseZipOptions, type XmlElement, type XmlNode, type XmlText, ZipArchive, collapseCfi, directoryOf, findAll, findFirst, getAttribute, getText, isCfi, isCfiString, isElement, normalizeArchivePath, parseCfi, parseXml, parseZip, resolveArchivePath, serializeCfi };