odf.js 0.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,729 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let zod = require("zod");
3
+ let fast_xml_parser = require("fast-xml-parser");
4
+ let fflate = require("fflate");
5
+ //#region src/model/node.ts
6
+ const AttributeSchema = zod.z.object({
7
+ name: zod.z.string(),
8
+ value: zod.z.string()
9
+ });
10
+ const XmlTextSchema = zod.z.object({
11
+ type: zod.z.literal("text"),
12
+ value: zod.z.string()
13
+ });
14
+ const XmlCdataSchema = zod.z.object({
15
+ type: zod.z.literal("cdata"),
16
+ value: zod.z.string()
17
+ });
18
+ const XmlCommentSchema = zod.z.object({
19
+ type: zod.z.literal("comment"),
20
+ value: zod.z.string()
21
+ });
22
+ const XmlDeclarationSchema = zod.z.object({
23
+ type: zod.z.literal("declaration"),
24
+ attributes: zod.z.array(AttributeSchema)
25
+ });
26
+ const XmlPiSchema = zod.z.object({
27
+ type: zod.z.literal("pi"),
28
+ target: zod.z.string(),
29
+ content: zod.z.string()
30
+ });
31
+ function isRecord$1(value) {
32
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33
+ }
34
+ function isAttribute(value) {
35
+ return isRecord$1(value) && typeof value.name === "string" && typeof value.value === "string";
36
+ }
37
+ function isXmlNode(value) {
38
+ if (!isRecord$1(value)) return false;
39
+ const t = value.type;
40
+ if (t === "text" || t === "cdata" || t === "comment") return typeof value.value === "string";
41
+ if (t === "declaration") return Array.isArray(value.attributes) && value.attributes.every(isAttribute);
42
+ if (t === "pi") return typeof value.target === "string" && typeof value.content === "string";
43
+ if (t === "element") return typeof value.tag === "string" && Array.isArray(value.attributes) && value.attributes.every(isAttribute) && Array.isArray(value.children) && value.children.every(isXmlNode);
44
+ return false;
45
+ }
46
+ const XmlElementSchema = zod.z.object({
47
+ type: zod.z.literal("element"),
48
+ tag: zod.z.string(),
49
+ attributes: zod.z.array(AttributeSchema),
50
+ children: zod.z.array(zod.z.custom(isXmlNode))
51
+ });
52
+ const XmlNodeSchema = zod.z.discriminatedUnion("type", [
53
+ XmlTextSchema,
54
+ XmlCdataSchema,
55
+ XmlCommentSchema,
56
+ XmlDeclarationSchema,
57
+ XmlPiSchema,
58
+ XmlElementSchema
59
+ ]);
60
+ //#endregion
61
+ //#region src/model/package.ts
62
+ const XmlPartSchema = zod.z.object({
63
+ kind: zod.z.literal("xml"),
64
+ nodes: zod.z.array(XmlNodeSchema)
65
+ });
66
+ const BinaryPartSchema = zod.z.object({
67
+ kind: zod.z.literal("binary"),
68
+ base64: zod.z.string()
69
+ });
70
+ const PartSchema = zod.z.discriminatedUnion("kind", [XmlPartSchema, BinaryPartSchema]);
71
+ const PackageSchema = zod.z.object({ parts: zod.z.record(zod.z.string(), PartSchema) });
72
+ //#endregion
73
+ //#region src/util/base64.ts
74
+ const TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
75
+ const DECODE = (() => {
76
+ const map = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
77
+ for (let i = 0; i < 64; i = i + 1) map[TABLE.charCodeAt(i)] = i;
78
+ return map;
79
+ })();
80
+ function bytesToBase64(bytes) {
81
+ let out = "";
82
+ const len = bytes.length;
83
+ for (let i = 0; i < len; i = i + 3) {
84
+ const b0 = bytes[i];
85
+ const b1 = i + 1 < len ? bytes[i + 1] : 0;
86
+ const b2 = i + 2 < len ? bytes[i + 2] : 0;
87
+ out += TABLE[b0 >> 2];
88
+ out += TABLE[(b0 & 3) << 4 | b1 >> 4];
89
+ out += i + 1 < len ? TABLE[(b1 & 15) << 2 | b2 >> 6] : "=";
90
+ out += i + 2 < len ? TABLE[b2 & 63] : "=";
91
+ }
92
+ return out;
93
+ }
94
+ function base64ToBytes(b64) {
95
+ const clean = b64.replace(/[^A-Za-z0-9+/=]/g, "");
96
+ const len = clean.length;
97
+ const out = new Uint8Array(len * 3 / 4 | 0);
98
+ let p = 0;
99
+ for (let i = 0; i < len; i = i + 4) {
100
+ const c0 = DECODE[clean.charCodeAt(i)];
101
+ const c1 = DECODE[clean.charCodeAt(i + 1)];
102
+ const c2 = clean.charCodeAt(i + 2);
103
+ const c3 = clean.charCodeAt(i + 3);
104
+ if (c0 === 255 || c1 === 255) throw new Error("invalid base64 input");
105
+ out[p++] = c0 << 2 | c1 >> 4;
106
+ if (c2 !== 61) {
107
+ const d2 = DECODE[c2];
108
+ out[p++] = (c1 & 15) << 4 | d2 >> 2;
109
+ if (c3 !== 61) {
110
+ const d3 = DECODE[c3];
111
+ out[p++] = (d2 & 3) << 6 | d3;
112
+ }
113
+ }
114
+ }
115
+ return out.subarray(0, p);
116
+ }
117
+ //#endregion
118
+ //#region src/xml/parse.ts
119
+ const PARSER = new fast_xml_parser.XMLParser({
120
+ preserveOrder: true,
121
+ attributeNamePrefix: "@_",
122
+ ignoreAttributes: false,
123
+ textNodeName: "#text",
124
+ cdataPropName: "__cdata",
125
+ commentPropName: "__comment",
126
+ processEntities: false,
127
+ parseTagValue: false,
128
+ trimValues: false
129
+ });
130
+ function parseXml(xml) {
131
+ return parseNodes(PARSER.parse(xml));
132
+ }
133
+ function isRecord(value) {
134
+ return typeof value === "object" && value !== null && !Array.isArray(value);
135
+ }
136
+ function isUnknownArray(value) {
137
+ return Array.isArray(value);
138
+ }
139
+ function asString(value) {
140
+ if (typeof value !== "string") throw new Error(`expected string while parsing XML, got ${typeof value}`);
141
+ return value;
142
+ }
143
+ function parseNodes(raw) {
144
+ if (!isUnknownArray(raw)) throw new Error("fast-xml-parser output was not an ordered array");
145
+ return raw.map(parseNode);
146
+ }
147
+ function parseNode(raw) {
148
+ if (!isRecord(raw)) throw new Error("fast-xml-parser node was not an object");
149
+ let tagKey;
150
+ for (const key of Object.keys(raw)) if (key !== ":@") {
151
+ if (tagKey !== void 0) throw new Error("XML node had multiple tag keys");
152
+ tagKey = key;
153
+ }
154
+ if (tagKey === void 0) throw new Error("XML node had no tag key");
155
+ const attributes = parseAttributes(raw[":@"]);
156
+ if (tagKey === "#text") return {
157
+ type: "text",
158
+ value: asString(raw["#text"])
159
+ };
160
+ if (tagKey === "__comment") return {
161
+ type: "comment",
162
+ value: scalarText(raw.__comment)
163
+ };
164
+ if (tagKey === "__cdata") return {
165
+ type: "cdata",
166
+ value: scalarText(raw.__cdata)
167
+ };
168
+ if (tagKey === "?xml") return {
169
+ type: "declaration",
170
+ attributes
171
+ };
172
+ if (tagKey.startsWith("?")) return {
173
+ type: "pi",
174
+ target: tagKey.slice(1),
175
+ content: scalarText(raw[tagKey])
176
+ };
177
+ return {
178
+ type: "element",
179
+ tag: tagKey,
180
+ attributes,
181
+ children: parseNodes(raw[tagKey])
182
+ };
183
+ }
184
+ function parseAttributes(raw) {
185
+ if (raw === void 0) return [];
186
+ if (!isRecord(raw)) throw new Error("XML attributes were not an object");
187
+ const attrs = [];
188
+ for (const key of Object.keys(raw)) {
189
+ if (!key.startsWith("@_")) throw new Error(`unexpected attribute key without @_ prefix: ${key}`);
190
+ attrs.push({
191
+ name: key.slice(2),
192
+ value: asString(raw[key])
193
+ });
194
+ }
195
+ return attrs;
196
+ }
197
+ function scalarText(raw) {
198
+ if (!isUnknownArray(raw) || raw.length === 0) throw new Error("expected a scalar-text wrapper array");
199
+ const first = raw[0];
200
+ if (!isRecord(first)) throw new Error("scalar-text wrapper was not an object");
201
+ return asString(first["#text"]);
202
+ }
203
+ //#endregion
204
+ //#region src/zip.ts
205
+ function unzipPackage(bytes) {
206
+ return (0, fflate.unzipSync)(bytes);
207
+ }
208
+ function zipPackage(entries) {
209
+ const data = {};
210
+ for (const [path, entry] of entries) if (entry.stored === true) data[path] = [entry.bytes, { level: 0 }];
211
+ else data[path] = entry.bytes;
212
+ return (0, fflate.zipSync)(data);
213
+ }
214
+ //#endregion
215
+ //#region src/package-io/read.ts
216
+ function parsePackage(bytes) {
217
+ const entries = unzipPackage(bytes);
218
+ const parts = {};
219
+ for (const [path, partBytes] of Object.entries(entries)) if (looksLikeXml(partBytes)) parts[path] = {
220
+ kind: "xml",
221
+ nodes: parseXml(new TextDecoder("utf-8").decode(partBytes))
222
+ };
223
+ else parts[path] = {
224
+ kind: "binary",
225
+ base64: bytesToBase64(partBytes)
226
+ };
227
+ return { parts };
228
+ }
229
+ function looksLikeXml(bytes) {
230
+ let i = 0;
231
+ if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) i = 3;
232
+ while (i < bytes.length) {
233
+ const b = bytes[i];
234
+ if (b === 32 || b === 9 || b === 10 || b === 13) {
235
+ i = i + 1;
236
+ continue;
237
+ }
238
+ return b === 60;
239
+ }
240
+ return false;
241
+ }
242
+ //#endregion
243
+ //#region src/xml/build.ts
244
+ const BUILDER = new fast_xml_parser.XMLBuilder({
245
+ preserveOrder: true,
246
+ attributeNamePrefix: "@_",
247
+ ignoreAttributes: false,
248
+ textNodeName: "#text",
249
+ cdataPropName: "__cdata",
250
+ commentPropName: "__comment",
251
+ processEntities: false,
252
+ format: false,
253
+ suppressEmptyNode: false
254
+ });
255
+ function buildXml(nodes) {
256
+ const out = BUILDER.build(toOrdered(nodes));
257
+ if (typeof out !== "string") throw new Error("XMLBuilder did not return a string");
258
+ return out;
259
+ }
260
+ function toOrdered(nodes) {
261
+ return nodes.map(toOrderedNode);
262
+ }
263
+ function attrsObject(attributes) {
264
+ const obj = {};
265
+ for (const a of attributes) obj[`@_${a.name}`] = a.value;
266
+ return obj;
267
+ }
268
+ function toOrderedNode(node) {
269
+ switch (node.type) {
270
+ case "text": return { "#text": node.value };
271
+ case "comment": return { __comment: [{ "#text": node.value }] };
272
+ case "cdata": return { __cdata: [{ "#text": node.value }] };
273
+ case "pi": return { [`?${node.target}`]: [{ "#text": node.content }] };
274
+ case "declaration": return {
275
+ "?xml": [{ "#text": "" }],
276
+ ":@": attrsObject(node.attributes)
277
+ };
278
+ case "element": {
279
+ const obj = { [node.tag]: toOrdered(node.children) };
280
+ const attrs = attrsObject(node.attributes);
281
+ if (Object.keys(attrs).length > 0) obj[":@"] = attrs;
282
+ return obj;
283
+ }
284
+ }
285
+ }
286
+ //#endregion
287
+ //#region src/package-io/write.ts
288
+ const MIMETYPE_PART = "mimetype";
289
+ const MANIFEST_PART = "META-INF/manifest.xml";
290
+ function serializePackage(pkg) {
291
+ const remaining = new Map(Object.entries(pkg.parts));
292
+ const entries = [];
293
+ const mimetype = remaining.get(MIMETYPE_PART);
294
+ if (mimetype !== void 0) {
295
+ entries.push([MIMETYPE_PART, {
296
+ bytes: partToBytes(mimetype),
297
+ stored: true
298
+ }]);
299
+ remaining.delete(MIMETYPE_PART);
300
+ }
301
+ const manifest = remaining.get(MANIFEST_PART);
302
+ if (manifest !== void 0) {
303
+ entries.push([MANIFEST_PART, { bytes: partToBytes(manifest) }]);
304
+ remaining.delete(MANIFEST_PART);
305
+ }
306
+ for (const [path, part] of remaining) entries.push([path, { bytes: partToBytes(part) }]);
307
+ return zipPackage(entries);
308
+ }
309
+ function partToBytes(part) {
310
+ switch (part.kind) {
311
+ case "xml": return new TextEncoder().encode(buildXml(part.nodes));
312
+ case "binary": return base64ToBytes(part.base64);
313
+ }
314
+ }
315
+ //#endregion
316
+ //#region src/codec.ts
317
+ const xmlCodec = zod.z.codec(zod.z.string(), zod.z.array(XmlNodeSchema), {
318
+ decode: (xml) => parseXml(xml),
319
+ encode: (nodes) => buildXml(nodes)
320
+ });
321
+ const packageCodec = zod.z.codec(zod.z.instanceof(Uint8Array), PackageSchema, {
322
+ decode: (bytes) => parsePackage(bytes),
323
+ encode: (pkg) => serializePackage(pkg)
324
+ });
325
+ function decodePackage(bytes) {
326
+ return zod.z.decode(packageCodec, bytes);
327
+ }
328
+ function encodePackage(pkg) {
329
+ return zod.z.encode(packageCodec, pkg);
330
+ }
331
+ //#endregion
332
+ //#region src/ns.ts
333
+ const ODF_NAMESPACES = Object.freeze({
334
+ office: "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
335
+ style: "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
336
+ text: "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
337
+ table: "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
338
+ draw: "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
339
+ fo: "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
340
+ svg: "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
341
+ xlink: "http://www.w3.org/1999/xlink",
342
+ dc: "http://purl.org/dc/elements/1.1/",
343
+ meta: "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
344
+ number: "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
345
+ chart: "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
346
+ dr3d: "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
347
+ math: "http://www.w3.org/1998/Math/MathML",
348
+ form: "urn:oasis:names:tc:opendocument:xmlns:form:1.0",
349
+ script: "urn:oasis:names:tc:opendocument:xmlns:script:1.0",
350
+ config: "urn:oasis:names:tc:opendocument:xmlns:config:1.0",
351
+ presentation: "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
352
+ smil: "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0",
353
+ anim: "urn:oasis:names:tc:opendocument:xmlns:animation:1.0",
354
+ xforms: "http://www.w3.org/2002/xforms",
355
+ xsd: "http://www.w3.org/2001/XMLSchema",
356
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
357
+ manifest: "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
358
+ });
359
+ function xmlnsAttributes(prefixes) {
360
+ const attrs = {};
361
+ for (const prefix of prefixes) attrs[`xmlns:${prefix}`] = ODF_NAMESPACES[prefix];
362
+ return attrs;
363
+ }
364
+ //#endregion
365
+ //#region src/media-type.ts
366
+ const ODF_MEDIA_TYPES = Object.freeze({
367
+ odt: "application/vnd.oasis.opendocument.text",
368
+ ott: "application/vnd.oasis.opendocument.text-template",
369
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
370
+ ots: "application/vnd.oasis.opendocument.spreadsheet-template",
371
+ odp: "application/vnd.oasis.opendocument.presentation",
372
+ otp: "application/vnd.oasis.opendocument.presentation-template",
373
+ odg: "application/vnd.oasis.opendocument.graphics",
374
+ otg: "application/vnd.oasis.opendocument.graphics-template",
375
+ odf: "application/vnd.oasis.opendocument.formula",
376
+ otf: "application/vnd.oasis.opendocument.formula-template",
377
+ odm: "application/vnd.oasis.opendocument.text-master",
378
+ otm: "application/vnd.oasis.opendocument.text-master-template",
379
+ odb: "application/vnd.oasis.opendocument.base"
380
+ });
381
+ function isOdfExtension(extension) {
382
+ return Object.hasOwn(ODF_MEDIA_TYPES, extension);
383
+ }
384
+ function mediaTypeForExtension(extension) {
385
+ const lower = extension.toLowerCase();
386
+ return isOdfExtension(lower) ? ODF_MEDIA_TYPES[lower] : void 0;
387
+ }
388
+ //#endregion
389
+ //#region src/image/sniff.ts
390
+ const PNG_SIGNATURE = [
391
+ 137,
392
+ 80,
393
+ 78,
394
+ 71,
395
+ 13,
396
+ 10,
397
+ 26,
398
+ 10
399
+ ];
400
+ const JPEG_SIGNATURE = [
401
+ 255,
402
+ 216,
403
+ 255
404
+ ];
405
+ function startsWith(bytes, signature) {
406
+ if (bytes.length < signature.length) return false;
407
+ for (let i = 0; i < signature.length; i++) if (bytes[i] !== signature[i]) return false;
408
+ return true;
409
+ }
410
+ function sniffImageFormat(bytes) {
411
+ if (startsWith(bytes, PNG_SIGNATURE)) return "png";
412
+ if (startsWith(bytes, JPEG_SIGNATURE)) return "jpeg";
413
+ }
414
+ //#endregion
415
+ //#region src/mimetype.ts
416
+ function readMimetype(pkg) {
417
+ const part = pkg.parts[MIMETYPE_PART];
418
+ if (part?.kind !== "binary") return;
419
+ return new TextDecoder("utf-8").decode(base64ToBytes(part.base64));
420
+ }
421
+ function writeMimetype(pkg, mediaType) {
422
+ pkg.parts[MIMETYPE_PART] = {
423
+ kind: "binary",
424
+ base64: bytesToBase64(new TextEncoder().encode(mediaType))
425
+ };
426
+ }
427
+ //#endregion
428
+ //#region src/xml/fragment.ts
429
+ function el(tag, attrs = {}, children = []) {
430
+ return {
431
+ type: "element",
432
+ tag,
433
+ attributes: Object.entries(attrs).map(([name, value]) => ({
434
+ name,
435
+ value
436
+ })),
437
+ children
438
+ };
439
+ }
440
+ function txt(value) {
441
+ return {
442
+ type: "text",
443
+ value
444
+ };
445
+ }
446
+ //#endregion
447
+ //#region src/xml/entities.ts
448
+ function encodeXmlText(value) {
449
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
450
+ }
451
+ //#endregion
452
+ //#region src/manifest.ts
453
+ const ManifestEntrySchema = zod.z.object({
454
+ fullPath: zod.z.string(),
455
+ mediaType: zod.z.string(),
456
+ version: zod.z.string().optional()
457
+ });
458
+ const ManifestSchema = zod.z.object({
459
+ version: zod.z.string(),
460
+ entries: zod.z.array(ManifestEntrySchema)
461
+ });
462
+ const ManifestProblemSchema = zod.z.object({
463
+ severity: zod.z.enum(["error", "warning"]),
464
+ message: zod.z.string(),
465
+ path: zod.z.string().optional()
466
+ });
467
+ const DEFAULT_MANIFEST_VERSION = "1.3";
468
+ const STANDARD_XML_PART_NAMES = /* @__PURE__ */ new Set([
469
+ "content.xml",
470
+ "styles.xml",
471
+ "meta.xml",
472
+ "settings.xml"
473
+ ]);
474
+ function findChildElement(nodes, tag) {
475
+ for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
476
+ }
477
+ function attrValue(element, name) {
478
+ return element.attributes.find((attribute) => attribute.name === name)?.value;
479
+ }
480
+ function readManifest(pkg) {
481
+ const part = pkg.parts[MANIFEST_PART];
482
+ if (part?.kind !== "xml") throw new Error(`package has no ${MANIFEST_PART} XML part to read`);
483
+ const root = findChildElement(part.nodes, "manifest:manifest");
484
+ if (root === void 0) throw new Error(`${MANIFEST_PART} has no manifest:manifest root element`);
485
+ const version = attrValue(root, "manifest:version");
486
+ if (version === void 0) throw new Error(`${MANIFEST_PART}'s manifest:manifest root is missing the required manifest:version attribute`);
487
+ const entries = [];
488
+ for (const child of root.children) {
489
+ if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
490
+ const fullPath = attrValue(child, "manifest:full-path");
491
+ const mediaType = attrValue(child, "manifest:media-type");
492
+ if (fullPath === void 0 || mediaType === void 0) throw new Error(`${MANIFEST_PART} has a manifest:file-entry missing manifest:full-path or manifest:media-type`);
493
+ const entryVersion = attrValue(child, "manifest:version");
494
+ entries.push(entryVersion === void 0 ? {
495
+ fullPath,
496
+ mediaType
497
+ } : {
498
+ fullPath,
499
+ mediaType,
500
+ version: entryVersion
501
+ });
502
+ }
503
+ return {
504
+ version,
505
+ entries
506
+ };
507
+ }
508
+ function subdocumentDirectories(partPaths) {
509
+ const dirs = [];
510
+ for (const path of partPaths) if (path.endsWith("/content.xml")) dirs.push(path.slice(0, path.length - 11));
511
+ return dirs;
512
+ }
513
+ function resolvePartMediaType(path, bytes, overrides) {
514
+ const override = overrides?.[path];
515
+ if (override !== void 0) return override;
516
+ const baseName = path.slice(path.lastIndexOf("/") + 1);
517
+ if (STANDARD_XML_PART_NAMES.has(baseName)) return "text/xml";
518
+ const dotIndex = baseName.lastIndexOf(".");
519
+ const extension = dotIndex === -1 ? "" : baseName.slice(dotIndex + 1);
520
+ const byExtension = extension === "" ? void 0 : mediaTypeForExtension(extension);
521
+ if (byExtension !== void 0) return byExtension;
522
+ if (bytes !== void 0) {
523
+ const sniffed = sniffImageFormat(bytes);
524
+ if (sniffed === "png") return "image/png";
525
+ if (sniffed === "jpeg") return "image/jpeg";
526
+ }
527
+ return "";
528
+ }
529
+ function buildManifest(pkg, options = {}) {
530
+ const version = options.version ?? DEFAULT_MANIFEST_VERSION;
531
+ const documentMediaType = options.documentMediaType ?? readMimetype(pkg);
532
+ if (documentMediaType === void 0) throw new Error("buildManifest: package has no \"mimetype\" part and no documentMediaType override was supplied -- the manifest root entry requires a known document media type");
533
+ const entries = [{
534
+ fullPath: "/",
535
+ mediaType: documentMediaType,
536
+ version
537
+ }];
538
+ const partPaths = Object.keys(pkg.parts);
539
+ for (const dir of new Set(subdocumentDirectories(partPaths))) entries.push({
540
+ fullPath: dir,
541
+ mediaType: resolvePartMediaType(dir, void 0, options.mediaTypeOverrides)
542
+ });
543
+ for (const [path, part] of Object.entries(pkg.parts)) {
544
+ if (path === "mimetype" || path === "META-INF/manifest.xml") continue;
545
+ const bytes = part.kind === "binary" ? base64ToBytes(part.base64) : void 0;
546
+ entries.push({
547
+ fullPath: path,
548
+ mediaType: resolvePartMediaType(path, bytes, options.mediaTypeOverrides)
549
+ });
550
+ }
551
+ return {
552
+ version,
553
+ entries
554
+ };
555
+ }
556
+ function buildManifestNodes(manifest) {
557
+ const fileEntries = manifest.entries.map((entry) => {
558
+ const attrs = { "manifest:full-path": encodeXmlText(entry.fullPath) };
559
+ if (entry.version !== void 0) attrs["manifest:version"] = encodeXmlText(entry.version);
560
+ attrs["manifest:media-type"] = encodeXmlText(entry.mediaType);
561
+ return el("manifest:file-entry", attrs);
562
+ });
563
+ return [{
564
+ type: "declaration",
565
+ attributes: [{
566
+ name: "version",
567
+ value: "1.0"
568
+ }, {
569
+ name: "encoding",
570
+ value: "UTF-8"
571
+ }]
572
+ }, el("manifest:manifest", {
573
+ ...xmlnsAttributes(["manifest"]),
574
+ "manifest:version": encodeXmlText(manifest.version)
575
+ }, fileEntries)];
576
+ }
577
+ function writeManifest(pkg, manifest) {
578
+ pkg.parts[MANIFEST_PART] = {
579
+ kind: "xml",
580
+ nodes: buildManifestNodes(manifest)
581
+ };
582
+ }
583
+ function syncManifest(pkg, options) {
584
+ writeManifest(pkg, buildManifest(pkg, options));
585
+ }
586
+ function validateManifest(pkg) {
587
+ const problems = [];
588
+ const manifestPart = pkg.parts[MANIFEST_PART];
589
+ if (manifestPart === void 0) {
590
+ problems.push({
591
+ severity: "error",
592
+ message: `package has no ${MANIFEST_PART} part`
593
+ });
594
+ return problems;
595
+ }
596
+ if (manifestPart.kind !== "xml") {
597
+ problems.push({
598
+ severity: "error",
599
+ message: `${MANIFEST_PART} part is not XML`
600
+ });
601
+ return problems;
602
+ }
603
+ const root = findChildElement(manifestPart.nodes, "manifest:manifest");
604
+ if (root === void 0) {
605
+ problems.push({
606
+ severity: "error",
607
+ message: `${MANIFEST_PART} has no manifest:manifest root element`
608
+ });
609
+ return problems;
610
+ }
611
+ let manifest;
612
+ try {
613
+ manifest = readManifest(pkg);
614
+ } catch (error) {
615
+ problems.push({
616
+ severity: "error",
617
+ message: `failed to parse ${MANIFEST_PART}: ${error instanceof Error ? error.message : String(error)}`
618
+ });
619
+ return problems;
620
+ }
621
+ const rootEntry = manifest.entries.find((entry) => entry.fullPath === "/");
622
+ if (rootEntry === void 0) problems.push({
623
+ severity: "error",
624
+ message: "manifest has no root (\"/\") entry"
625
+ });
626
+ else {
627
+ const documentMediaType = readMimetype(pkg);
628
+ if (documentMediaType !== void 0 && documentMediaType !== rootEntry.mediaType) problems.push({
629
+ severity: "error",
630
+ message: `manifest root entry media type "${rootEntry.mediaType}" does not match the mimetype part's media type "${documentMediaType}"`,
631
+ path: "/"
632
+ });
633
+ }
634
+ const partPaths = new Set(Object.keys(pkg.parts).filter((path) => path !== "mimetype" && path !== "META-INF/manifest.xml"));
635
+ const manifestPaths = new Set(manifest.entries.map((entry) => entry.fullPath));
636
+ for (const entry of manifest.entries) {
637
+ if (entry.fullPath === "/" || entry.fullPath.endsWith("/")) continue;
638
+ if (!partPaths.has(entry.fullPath)) problems.push({
639
+ severity: "warning",
640
+ message: `manifest lists "${entry.fullPath}" but the package has no such part`,
641
+ path: entry.fullPath
642
+ });
643
+ }
644
+ for (const path of partPaths) if (!manifestPaths.has(path)) problems.push({
645
+ severity: "warning",
646
+ message: `package has part "${path}" not listed in the manifest`,
647
+ path
648
+ });
649
+ for (const child of root.children) {
650
+ if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
651
+ if (!child.children.some((grandchild) => grandchild.type === "element" && grandchild.tag === "manifest:encryption-data")) continue;
652
+ const fullPath = attrValue(child, "manifest:full-path");
653
+ if (fullPath === void 0) continue;
654
+ problems.push({
655
+ severity: "warning",
656
+ message: `entry "${fullPath}" carries manifest:encryption-data -- odf.js does not implement ODF encryption/decryption`,
657
+ path: fullPath
658
+ });
659
+ }
660
+ return problems;
661
+ }
662
+ function setDocumentMediaType(pkg, mediaType, version = DEFAULT_MANIFEST_VERSION) {
663
+ writeMimetype(pkg, mediaType);
664
+ const rootEntry = {
665
+ fullPath: "/",
666
+ mediaType,
667
+ version
668
+ };
669
+ if (pkg.parts["META-INF/manifest.xml"] === void 0) {
670
+ writeManifest(pkg, {
671
+ version,
672
+ entries: [rootEntry]
673
+ });
674
+ return;
675
+ }
676
+ const existing = readManifest(pkg);
677
+ const rootIndex = existing.entries.findIndex((entry) => entry.fullPath === "/");
678
+ writeManifest(pkg, {
679
+ version,
680
+ entries: rootIndex === -1 ? [rootEntry, ...existing.entries] : existing.entries.map((entry, index) => index === rootIndex ? rootEntry : entry)
681
+ });
682
+ }
683
+ //#endregion
684
+ exports.AttributeSchema = AttributeSchema;
685
+ exports.BinaryPartSchema = BinaryPartSchema;
686
+ exports.MANIFEST_PART = MANIFEST_PART;
687
+ exports.MIMETYPE_PART = MIMETYPE_PART;
688
+ exports.ManifestEntrySchema = ManifestEntrySchema;
689
+ exports.ManifestProblemSchema = ManifestProblemSchema;
690
+ exports.ManifestSchema = ManifestSchema;
691
+ exports.ODF_MEDIA_TYPES = ODF_MEDIA_TYPES;
692
+ exports.ODF_NAMESPACES = ODF_NAMESPACES;
693
+ exports.PackageSchema = PackageSchema;
694
+ exports.PartSchema = PartSchema;
695
+ exports.XmlCdataSchema = XmlCdataSchema;
696
+ exports.XmlCommentSchema = XmlCommentSchema;
697
+ exports.XmlDeclarationSchema = XmlDeclarationSchema;
698
+ exports.XmlElementSchema = XmlElementSchema;
699
+ exports.XmlNodeSchema = XmlNodeSchema;
700
+ exports.XmlPartSchema = XmlPartSchema;
701
+ exports.XmlPiSchema = XmlPiSchema;
702
+ exports.XmlTextSchema = XmlTextSchema;
703
+ exports.base64ToBytes = base64ToBytes;
704
+ exports.buildManifest = buildManifest;
705
+ exports.buildXml = buildXml;
706
+ exports.bytesToBase64 = bytesToBase64;
707
+ exports.decodePackage = decodePackage;
708
+ exports.el = el;
709
+ exports.encodePackage = encodePackage;
710
+ exports.encodeXmlText = encodeXmlText;
711
+ exports.isXmlNode = isXmlNode;
712
+ exports.mediaTypeForExtension = mediaTypeForExtension;
713
+ exports.packageCodec = packageCodec;
714
+ exports.parsePackage = parsePackage;
715
+ exports.parseXml = parseXml;
716
+ exports.readManifest = readManifest;
717
+ exports.readMimetype = readMimetype;
718
+ exports.serializePackage = serializePackage;
719
+ exports.setDocumentMediaType = setDocumentMediaType;
720
+ exports.sniffImageFormat = sniffImageFormat;
721
+ exports.syncManifest = syncManifest;
722
+ exports.txt = txt;
723
+ exports.unzipPackage = unzipPackage;
724
+ exports.validateManifest = validateManifest;
725
+ exports.writeManifest = writeManifest;
726
+ exports.writeMimetype = writeMimetype;
727
+ exports.xmlCodec = xmlCodec;
728
+ exports.xmlnsAttributes = xmlnsAttributes;
729
+ exports.zipPackage = zipPackage;