odf.js 1.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 CHANGED
@@ -329,8 +329,367 @@ function encodePackage(pkg) {
329
329
  return zod.z.encode(packageCodec, pkg);
330
330
  }
331
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
332
684
  exports.AttributeSchema = AttributeSchema;
333
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;
334
693
  exports.PackageSchema = PackageSchema;
335
694
  exports.PartSchema = PartSchema;
336
695
  exports.XmlCdataSchema = XmlCdataSchema;
@@ -342,15 +701,29 @@ exports.XmlPartSchema = XmlPartSchema;
342
701
  exports.XmlPiSchema = XmlPiSchema;
343
702
  exports.XmlTextSchema = XmlTextSchema;
344
703
  exports.base64ToBytes = base64ToBytes;
704
+ exports.buildManifest = buildManifest;
345
705
  exports.buildXml = buildXml;
346
706
  exports.bytesToBase64 = bytesToBase64;
347
707
  exports.decodePackage = decodePackage;
708
+ exports.el = el;
348
709
  exports.encodePackage = encodePackage;
710
+ exports.encodeXmlText = encodeXmlText;
349
711
  exports.isXmlNode = isXmlNode;
712
+ exports.mediaTypeForExtension = mediaTypeForExtension;
350
713
  exports.packageCodec = packageCodec;
351
714
  exports.parsePackage = parsePackage;
352
715
  exports.parseXml = parseXml;
716
+ exports.readManifest = readManifest;
717
+ exports.readMimetype = readMimetype;
353
718
  exports.serializePackage = serializePackage;
719
+ exports.setDocumentMediaType = setDocumentMediaType;
720
+ exports.sniffImageFormat = sniffImageFormat;
721
+ exports.syncManifest = syncManifest;
722
+ exports.txt = txt;
354
723
  exports.unzipPackage = unzipPackage;
724
+ exports.validateManifest = validateManifest;
725
+ exports.writeManifest = writeManifest;
726
+ exports.writeMimetype = writeMimetype;
355
727
  exports.xmlCodec = xmlCodec;
728
+ exports.xmlnsAttributes = xmlnsAttributes;
356
729
  exports.zipPackage = zipPackage;
package/dist/index.d.cts CHANGED
@@ -263,6 +263,8 @@ declare function encodePackage(pkg: Package): Uint8Array<ArrayBuffer>;
263
263
  declare function parsePackage(bytes: Uint8Array<ArrayBuffer>): Package;
264
264
  //#endregion
265
265
  //#region src/package-io/write.d.ts
266
+ declare const MIMETYPE_PART = "mimetype";
267
+ declare const MANIFEST_PART = "META-INF/manifest.xml";
266
268
  declare function serializePackage(pkg: Package): Uint8Array<ArrayBuffer>;
267
269
  //#endregion
268
270
  //#region src/xml/parse.d.ts
@@ -283,4 +285,105 @@ declare function zipPackage(entries: readonly (readonly [string, ZipEntry])[]):
283
285
  declare function bytesToBase64(bytes: Uint8Array<ArrayBuffer>): string;
284
286
  declare function base64ToBytes(b64: string): Uint8Array<ArrayBuffer>;
285
287
  //#endregion
286
- export { type Attribute, AttributeSchema, type BinaryPart, BinaryPartSchema, type Package, PackageSchema, type Part, PartSchema, type XmlCdata, XmlCdataSchema, type XmlComment, XmlCommentSchema, type XmlDeclaration, XmlDeclarationSchema, type XmlElement, XmlElementSchema, type XmlNode, XmlNodeSchema, type XmlPart, XmlPartSchema, type XmlPi, XmlPiSchema, type XmlText, XmlTextSchema, type ZipEntry, base64ToBytes, buildXml, bytesToBase64, decodePackage, encodePackage, isXmlNode, packageCodec, parsePackage, parseXml, serializePackage, unzipPackage, xmlCodec, zipPackage };
288
+ //#region src/ns.d.ts
289
+ declare const ODF_NAMESPACES: Readonly<{
290
+ office: "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
291
+ style: "urn:oasis:names:tc:opendocument:xmlns:style:1.0";
292
+ text: "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
293
+ table: "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
294
+ draw: "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0";
295
+ fo: "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0";
296
+ svg: "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0";
297
+ xlink: "http://www.w3.org/1999/xlink";
298
+ dc: "http://purl.org/dc/elements/1.1/";
299
+ meta: "urn:oasis:names:tc:opendocument:xmlns:meta:1.0";
300
+ number: "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0";
301
+ chart: "urn:oasis:names:tc:opendocument:xmlns:chart:1.0";
302
+ dr3d: "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0";
303
+ math: "http://www.w3.org/1998/Math/MathML";
304
+ form: "urn:oasis:names:tc:opendocument:xmlns:form:1.0";
305
+ script: "urn:oasis:names:tc:opendocument:xmlns:script:1.0";
306
+ config: "urn:oasis:names:tc:opendocument:xmlns:config:1.0";
307
+ presentation: "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0";
308
+ smil: "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0";
309
+ anim: "urn:oasis:names:tc:opendocument:xmlns:animation:1.0";
310
+ xforms: "http://www.w3.org/2002/xforms";
311
+ xsd: "http://www.w3.org/2001/XMLSchema";
312
+ xsi: "http://www.w3.org/2001/XMLSchema-instance";
313
+ manifest: "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0";
314
+ }>;
315
+ type OdfNamespacePrefix = keyof typeof ODF_NAMESPACES;
316
+ declare function xmlnsAttributes(prefixes: readonly OdfNamespacePrefix[]): Record<string, string>;
317
+ //#endregion
318
+ //#region src/media-type.d.ts
319
+ declare const ODF_MEDIA_TYPES: Readonly<{
320
+ odt: "application/vnd.oasis.opendocument.text";
321
+ ott: "application/vnd.oasis.opendocument.text-template";
322
+ ods: "application/vnd.oasis.opendocument.spreadsheet";
323
+ ots: "application/vnd.oasis.opendocument.spreadsheet-template";
324
+ odp: "application/vnd.oasis.opendocument.presentation";
325
+ otp: "application/vnd.oasis.opendocument.presentation-template";
326
+ odg: "application/vnd.oasis.opendocument.graphics";
327
+ otg: "application/vnd.oasis.opendocument.graphics-template";
328
+ odf: "application/vnd.oasis.opendocument.formula";
329
+ otf: "application/vnd.oasis.opendocument.formula-template";
330
+ odm: "application/vnd.oasis.opendocument.text-master";
331
+ otm: "application/vnd.oasis.opendocument.text-master-template";
332
+ odb: "application/vnd.oasis.opendocument.base";
333
+ }>;
334
+ type OdfExtension = keyof typeof ODF_MEDIA_TYPES;
335
+ declare function mediaTypeForExtension(extension: string): string | undefined;
336
+ //#endregion
337
+ //#region src/image/sniff.d.ts
338
+ type ImageFormat = 'png' | 'jpeg';
339
+ declare function sniffImageFormat(bytes: Uint8Array<ArrayBuffer>): ImageFormat | undefined;
340
+ //#endregion
341
+ //#region src/mimetype.d.ts
342
+ declare function readMimetype(pkg: Package): string | undefined;
343
+ declare function writeMimetype(pkg: Package, mediaType: string): void;
344
+ //#endregion
345
+ //#region src/xml/fragment.d.ts
346
+ declare function el(tag: string, attrs?: Record<string, string>, children?: XmlNode[]): XmlElement;
347
+ declare function txt(value: string): XmlText;
348
+ //#endregion
349
+ //#region src/xml/entities.d.ts
350
+ declare function encodeXmlText(value: string): string;
351
+ //#endregion
352
+ //#region src/manifest.d.ts
353
+ declare const ManifestEntrySchema: z.ZodObject<{
354
+ fullPath: z.ZodString;
355
+ mediaType: z.ZodString;
356
+ version: z.ZodOptional<z.ZodString>;
357
+ }, z.core.$strip>;
358
+ type ManifestEntry = z.infer<typeof ManifestEntrySchema>;
359
+ declare const ManifestSchema: z.ZodObject<{
360
+ version: z.ZodString;
361
+ entries: z.ZodArray<z.ZodObject<{
362
+ fullPath: z.ZodString;
363
+ mediaType: z.ZodString;
364
+ version: z.ZodOptional<z.ZodString>;
365
+ }, z.core.$strip>>;
366
+ }, z.core.$strip>;
367
+ type Manifest = z.infer<typeof ManifestSchema>;
368
+ declare const ManifestProblemSchema: z.ZodObject<{
369
+ severity: z.ZodEnum<{
370
+ error: "error";
371
+ warning: "warning";
372
+ }>;
373
+ message: z.ZodString;
374
+ path: z.ZodOptional<z.ZodString>;
375
+ }, z.core.$strip>;
376
+ type ManifestProblem = z.infer<typeof ManifestProblemSchema>;
377
+ declare function readManifest(pkg: Package): Manifest;
378
+ interface BuildManifestOptions {
379
+ documentMediaType?: string;
380
+ version?: string;
381
+ mediaTypeOverrides?: Readonly<Record<string, string>>;
382
+ }
383
+ declare function buildManifest(pkg: Package, options?: BuildManifestOptions): Manifest;
384
+ declare function writeManifest(pkg: Package, manifest: Manifest): void;
385
+ declare function syncManifest(pkg: Package, options?: BuildManifestOptions): void;
386
+ declare function validateManifest(pkg: Package): ManifestProblem[];
387
+ declare function setDocumentMediaType(pkg: Package, mediaType: string, version?: string): void;
388
+ //#endregion
389
+ export { type Attribute, AttributeSchema, type BinaryPart, BinaryPartSchema, type BuildManifestOptions, type ImageFormat, MANIFEST_PART, MIMETYPE_PART, type Manifest, type ManifestEntry, ManifestEntrySchema, type ManifestProblem, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, type OdfExtension, type OdfNamespacePrefix, type Package, PackageSchema, type Part, PartSchema, type XmlCdata, XmlCdataSchema, type XmlComment, XmlCommentSchema, type XmlDeclaration, XmlDeclarationSchema, type XmlElement, XmlElementSchema, type XmlNode, XmlNodeSchema, type XmlPart, XmlPartSchema, type XmlPi, XmlPiSchema, type XmlText, XmlTextSchema, type ZipEntry, base64ToBytes, buildManifest, buildXml, bytesToBase64, decodePackage, el, encodePackage, encodeXmlText, isXmlNode, mediaTypeForExtension, packageCodec, parsePackage, parseXml, readManifest, readMimetype, serializePackage, setDocumentMediaType, sniffImageFormat, syncManifest, txt, unzipPackage, validateManifest, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
package/dist/index.d.ts CHANGED
@@ -263,6 +263,8 @@ declare function encodePackage(pkg: Package): Uint8Array<ArrayBuffer>;
263
263
  declare function parsePackage(bytes: Uint8Array<ArrayBuffer>): Package;
264
264
  //#endregion
265
265
  //#region src/package-io/write.d.ts
266
+ declare const MIMETYPE_PART = "mimetype";
267
+ declare const MANIFEST_PART = "META-INF/manifest.xml";
266
268
  declare function serializePackage(pkg: Package): Uint8Array<ArrayBuffer>;
267
269
  //#endregion
268
270
  //#region src/xml/parse.d.ts
@@ -283,4 +285,105 @@ declare function zipPackage(entries: readonly (readonly [string, ZipEntry])[]):
283
285
  declare function bytesToBase64(bytes: Uint8Array<ArrayBuffer>): string;
284
286
  declare function base64ToBytes(b64: string): Uint8Array<ArrayBuffer>;
285
287
  //#endregion
286
- export { type Attribute, AttributeSchema, type BinaryPart, BinaryPartSchema, type Package, PackageSchema, type Part, PartSchema, type XmlCdata, XmlCdataSchema, type XmlComment, XmlCommentSchema, type XmlDeclaration, XmlDeclarationSchema, type XmlElement, XmlElementSchema, type XmlNode, XmlNodeSchema, type XmlPart, XmlPartSchema, type XmlPi, XmlPiSchema, type XmlText, XmlTextSchema, type ZipEntry, base64ToBytes, buildXml, bytesToBase64, decodePackage, encodePackage, isXmlNode, packageCodec, parsePackage, parseXml, serializePackage, unzipPackage, xmlCodec, zipPackage };
288
+ //#region src/ns.d.ts
289
+ declare const ODF_NAMESPACES: Readonly<{
290
+ office: "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
291
+ style: "urn:oasis:names:tc:opendocument:xmlns:style:1.0";
292
+ text: "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
293
+ table: "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
294
+ draw: "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0";
295
+ fo: "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0";
296
+ svg: "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0";
297
+ xlink: "http://www.w3.org/1999/xlink";
298
+ dc: "http://purl.org/dc/elements/1.1/";
299
+ meta: "urn:oasis:names:tc:opendocument:xmlns:meta:1.0";
300
+ number: "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0";
301
+ chart: "urn:oasis:names:tc:opendocument:xmlns:chart:1.0";
302
+ dr3d: "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0";
303
+ math: "http://www.w3.org/1998/Math/MathML";
304
+ form: "urn:oasis:names:tc:opendocument:xmlns:form:1.0";
305
+ script: "urn:oasis:names:tc:opendocument:xmlns:script:1.0";
306
+ config: "urn:oasis:names:tc:opendocument:xmlns:config:1.0";
307
+ presentation: "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0";
308
+ smil: "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0";
309
+ anim: "urn:oasis:names:tc:opendocument:xmlns:animation:1.0";
310
+ xforms: "http://www.w3.org/2002/xforms";
311
+ xsd: "http://www.w3.org/2001/XMLSchema";
312
+ xsi: "http://www.w3.org/2001/XMLSchema-instance";
313
+ manifest: "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0";
314
+ }>;
315
+ type OdfNamespacePrefix = keyof typeof ODF_NAMESPACES;
316
+ declare function xmlnsAttributes(prefixes: readonly OdfNamespacePrefix[]): Record<string, string>;
317
+ //#endregion
318
+ //#region src/media-type.d.ts
319
+ declare const ODF_MEDIA_TYPES: Readonly<{
320
+ odt: "application/vnd.oasis.opendocument.text";
321
+ ott: "application/vnd.oasis.opendocument.text-template";
322
+ ods: "application/vnd.oasis.opendocument.spreadsheet";
323
+ ots: "application/vnd.oasis.opendocument.spreadsheet-template";
324
+ odp: "application/vnd.oasis.opendocument.presentation";
325
+ otp: "application/vnd.oasis.opendocument.presentation-template";
326
+ odg: "application/vnd.oasis.opendocument.graphics";
327
+ otg: "application/vnd.oasis.opendocument.graphics-template";
328
+ odf: "application/vnd.oasis.opendocument.formula";
329
+ otf: "application/vnd.oasis.opendocument.formula-template";
330
+ odm: "application/vnd.oasis.opendocument.text-master";
331
+ otm: "application/vnd.oasis.opendocument.text-master-template";
332
+ odb: "application/vnd.oasis.opendocument.base";
333
+ }>;
334
+ type OdfExtension = keyof typeof ODF_MEDIA_TYPES;
335
+ declare function mediaTypeForExtension(extension: string): string | undefined;
336
+ //#endregion
337
+ //#region src/image/sniff.d.ts
338
+ type ImageFormat = 'png' | 'jpeg';
339
+ declare function sniffImageFormat(bytes: Uint8Array<ArrayBuffer>): ImageFormat | undefined;
340
+ //#endregion
341
+ //#region src/mimetype.d.ts
342
+ declare function readMimetype(pkg: Package): string | undefined;
343
+ declare function writeMimetype(pkg: Package, mediaType: string): void;
344
+ //#endregion
345
+ //#region src/xml/fragment.d.ts
346
+ declare function el(tag: string, attrs?: Record<string, string>, children?: XmlNode[]): XmlElement;
347
+ declare function txt(value: string): XmlText;
348
+ //#endregion
349
+ //#region src/xml/entities.d.ts
350
+ declare function encodeXmlText(value: string): string;
351
+ //#endregion
352
+ //#region src/manifest.d.ts
353
+ declare const ManifestEntrySchema: z.ZodObject<{
354
+ fullPath: z.ZodString;
355
+ mediaType: z.ZodString;
356
+ version: z.ZodOptional<z.ZodString>;
357
+ }, z.core.$strip>;
358
+ type ManifestEntry = z.infer<typeof ManifestEntrySchema>;
359
+ declare const ManifestSchema: z.ZodObject<{
360
+ version: z.ZodString;
361
+ entries: z.ZodArray<z.ZodObject<{
362
+ fullPath: z.ZodString;
363
+ mediaType: z.ZodString;
364
+ version: z.ZodOptional<z.ZodString>;
365
+ }, z.core.$strip>>;
366
+ }, z.core.$strip>;
367
+ type Manifest = z.infer<typeof ManifestSchema>;
368
+ declare const ManifestProblemSchema: z.ZodObject<{
369
+ severity: z.ZodEnum<{
370
+ error: "error";
371
+ warning: "warning";
372
+ }>;
373
+ message: z.ZodString;
374
+ path: z.ZodOptional<z.ZodString>;
375
+ }, z.core.$strip>;
376
+ type ManifestProblem = z.infer<typeof ManifestProblemSchema>;
377
+ declare function readManifest(pkg: Package): Manifest;
378
+ interface BuildManifestOptions {
379
+ documentMediaType?: string;
380
+ version?: string;
381
+ mediaTypeOverrides?: Readonly<Record<string, string>>;
382
+ }
383
+ declare function buildManifest(pkg: Package, options?: BuildManifestOptions): Manifest;
384
+ declare function writeManifest(pkg: Package, manifest: Manifest): void;
385
+ declare function syncManifest(pkg: Package, options?: BuildManifestOptions): void;
386
+ declare function validateManifest(pkg: Package): ManifestProblem[];
387
+ declare function setDocumentMediaType(pkg: Package, mediaType: string, version?: string): void;
388
+ //#endregion
389
+ export { type Attribute, AttributeSchema, type BinaryPart, BinaryPartSchema, type BuildManifestOptions, type ImageFormat, MANIFEST_PART, MIMETYPE_PART, type Manifest, type ManifestEntry, ManifestEntrySchema, type ManifestProblem, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, type OdfExtension, type OdfNamespacePrefix, type Package, PackageSchema, type Part, PartSchema, type XmlCdata, XmlCdataSchema, type XmlComment, XmlCommentSchema, type XmlDeclaration, XmlDeclarationSchema, type XmlElement, XmlElementSchema, type XmlNode, XmlNodeSchema, type XmlPart, XmlPartSchema, type XmlPi, XmlPiSchema, type XmlText, XmlTextSchema, type ZipEntry, base64ToBytes, buildManifest, buildXml, bytesToBase64, decodePackage, el, encodePackage, encodeXmlText, isXmlNode, mediaTypeForExtension, packageCodec, parsePackage, parseXml, readManifest, readMimetype, serializePackage, setDocumentMediaType, sniffImageFormat, syncManifest, txt, unzipPackage, validateManifest, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
package/dist/index.js CHANGED
@@ -328,4 +328,356 @@ function encodePackage(pkg) {
328
328
  return z.encode(packageCodec, pkg);
329
329
  }
330
330
  //#endregion
331
- export { AttributeSchema, BinaryPartSchema, PackageSchema, PartSchema, XmlCdataSchema, XmlCommentSchema, XmlDeclarationSchema, XmlElementSchema, XmlNodeSchema, XmlPartSchema, XmlPiSchema, XmlTextSchema, base64ToBytes, buildXml, bytesToBase64, decodePackage, encodePackage, isXmlNode, packageCodec, parsePackage, parseXml, serializePackage, unzipPackage, xmlCodec, zipPackage };
331
+ //#region src/ns.ts
332
+ const ODF_NAMESPACES = Object.freeze({
333
+ office: "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
334
+ style: "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
335
+ text: "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
336
+ table: "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
337
+ draw: "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
338
+ fo: "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
339
+ svg: "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
340
+ xlink: "http://www.w3.org/1999/xlink",
341
+ dc: "http://purl.org/dc/elements/1.1/",
342
+ meta: "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
343
+ number: "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
344
+ chart: "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
345
+ dr3d: "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
346
+ math: "http://www.w3.org/1998/Math/MathML",
347
+ form: "urn:oasis:names:tc:opendocument:xmlns:form:1.0",
348
+ script: "urn:oasis:names:tc:opendocument:xmlns:script:1.0",
349
+ config: "urn:oasis:names:tc:opendocument:xmlns:config:1.0",
350
+ presentation: "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
351
+ smil: "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0",
352
+ anim: "urn:oasis:names:tc:opendocument:xmlns:animation:1.0",
353
+ xforms: "http://www.w3.org/2002/xforms",
354
+ xsd: "http://www.w3.org/2001/XMLSchema",
355
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
356
+ manifest: "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
357
+ });
358
+ function xmlnsAttributes(prefixes) {
359
+ const attrs = {};
360
+ for (const prefix of prefixes) attrs[`xmlns:${prefix}`] = ODF_NAMESPACES[prefix];
361
+ return attrs;
362
+ }
363
+ //#endregion
364
+ //#region src/media-type.ts
365
+ const ODF_MEDIA_TYPES = Object.freeze({
366
+ odt: "application/vnd.oasis.opendocument.text",
367
+ ott: "application/vnd.oasis.opendocument.text-template",
368
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
369
+ ots: "application/vnd.oasis.opendocument.spreadsheet-template",
370
+ odp: "application/vnd.oasis.opendocument.presentation",
371
+ otp: "application/vnd.oasis.opendocument.presentation-template",
372
+ odg: "application/vnd.oasis.opendocument.graphics",
373
+ otg: "application/vnd.oasis.opendocument.graphics-template",
374
+ odf: "application/vnd.oasis.opendocument.formula",
375
+ otf: "application/vnd.oasis.opendocument.formula-template",
376
+ odm: "application/vnd.oasis.opendocument.text-master",
377
+ otm: "application/vnd.oasis.opendocument.text-master-template",
378
+ odb: "application/vnd.oasis.opendocument.base"
379
+ });
380
+ function isOdfExtension(extension) {
381
+ return Object.hasOwn(ODF_MEDIA_TYPES, extension);
382
+ }
383
+ function mediaTypeForExtension(extension) {
384
+ const lower = extension.toLowerCase();
385
+ return isOdfExtension(lower) ? ODF_MEDIA_TYPES[lower] : void 0;
386
+ }
387
+ //#endregion
388
+ //#region src/image/sniff.ts
389
+ const PNG_SIGNATURE = [
390
+ 137,
391
+ 80,
392
+ 78,
393
+ 71,
394
+ 13,
395
+ 10,
396
+ 26,
397
+ 10
398
+ ];
399
+ const JPEG_SIGNATURE = [
400
+ 255,
401
+ 216,
402
+ 255
403
+ ];
404
+ function startsWith(bytes, signature) {
405
+ if (bytes.length < signature.length) return false;
406
+ for (let i = 0; i < signature.length; i++) if (bytes[i] !== signature[i]) return false;
407
+ return true;
408
+ }
409
+ function sniffImageFormat(bytes) {
410
+ if (startsWith(bytes, PNG_SIGNATURE)) return "png";
411
+ if (startsWith(bytes, JPEG_SIGNATURE)) return "jpeg";
412
+ }
413
+ //#endregion
414
+ //#region src/mimetype.ts
415
+ function readMimetype(pkg) {
416
+ const part = pkg.parts[MIMETYPE_PART];
417
+ if (part?.kind !== "binary") return;
418
+ return new TextDecoder("utf-8").decode(base64ToBytes(part.base64));
419
+ }
420
+ function writeMimetype(pkg, mediaType) {
421
+ pkg.parts[MIMETYPE_PART] = {
422
+ kind: "binary",
423
+ base64: bytesToBase64(new TextEncoder().encode(mediaType))
424
+ };
425
+ }
426
+ //#endregion
427
+ //#region src/xml/fragment.ts
428
+ function el(tag, attrs = {}, children = []) {
429
+ return {
430
+ type: "element",
431
+ tag,
432
+ attributes: Object.entries(attrs).map(([name, value]) => ({
433
+ name,
434
+ value
435
+ })),
436
+ children
437
+ };
438
+ }
439
+ function txt(value) {
440
+ return {
441
+ type: "text",
442
+ value
443
+ };
444
+ }
445
+ //#endregion
446
+ //#region src/xml/entities.ts
447
+ function encodeXmlText(value) {
448
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
449
+ }
450
+ //#endregion
451
+ //#region src/manifest.ts
452
+ const ManifestEntrySchema = z.object({
453
+ fullPath: z.string(),
454
+ mediaType: z.string(),
455
+ version: z.string().optional()
456
+ });
457
+ const ManifestSchema = z.object({
458
+ version: z.string(),
459
+ entries: z.array(ManifestEntrySchema)
460
+ });
461
+ const ManifestProblemSchema = z.object({
462
+ severity: z.enum(["error", "warning"]),
463
+ message: z.string(),
464
+ path: z.string().optional()
465
+ });
466
+ const DEFAULT_MANIFEST_VERSION = "1.3";
467
+ const STANDARD_XML_PART_NAMES = /* @__PURE__ */ new Set([
468
+ "content.xml",
469
+ "styles.xml",
470
+ "meta.xml",
471
+ "settings.xml"
472
+ ]);
473
+ function findChildElement(nodes, tag) {
474
+ for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
475
+ }
476
+ function attrValue(element, name) {
477
+ return element.attributes.find((attribute) => attribute.name === name)?.value;
478
+ }
479
+ function readManifest(pkg) {
480
+ const part = pkg.parts[MANIFEST_PART];
481
+ if (part?.kind !== "xml") throw new Error(`package has no ${MANIFEST_PART} XML part to read`);
482
+ const root = findChildElement(part.nodes, "manifest:manifest");
483
+ if (root === void 0) throw new Error(`${MANIFEST_PART} has no manifest:manifest root element`);
484
+ const version = attrValue(root, "manifest:version");
485
+ if (version === void 0) throw new Error(`${MANIFEST_PART}'s manifest:manifest root is missing the required manifest:version attribute`);
486
+ const entries = [];
487
+ for (const child of root.children) {
488
+ if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
489
+ const fullPath = attrValue(child, "manifest:full-path");
490
+ const mediaType = attrValue(child, "manifest:media-type");
491
+ 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`);
492
+ const entryVersion = attrValue(child, "manifest:version");
493
+ entries.push(entryVersion === void 0 ? {
494
+ fullPath,
495
+ mediaType
496
+ } : {
497
+ fullPath,
498
+ mediaType,
499
+ version: entryVersion
500
+ });
501
+ }
502
+ return {
503
+ version,
504
+ entries
505
+ };
506
+ }
507
+ function subdocumentDirectories(partPaths) {
508
+ const dirs = [];
509
+ for (const path of partPaths) if (path.endsWith("/content.xml")) dirs.push(path.slice(0, path.length - 11));
510
+ return dirs;
511
+ }
512
+ function resolvePartMediaType(path, bytes, overrides) {
513
+ const override = overrides?.[path];
514
+ if (override !== void 0) return override;
515
+ const baseName = path.slice(path.lastIndexOf("/") + 1);
516
+ if (STANDARD_XML_PART_NAMES.has(baseName)) return "text/xml";
517
+ const dotIndex = baseName.lastIndexOf(".");
518
+ const extension = dotIndex === -1 ? "" : baseName.slice(dotIndex + 1);
519
+ const byExtension = extension === "" ? void 0 : mediaTypeForExtension(extension);
520
+ if (byExtension !== void 0) return byExtension;
521
+ if (bytes !== void 0) {
522
+ const sniffed = sniffImageFormat(bytes);
523
+ if (sniffed === "png") return "image/png";
524
+ if (sniffed === "jpeg") return "image/jpeg";
525
+ }
526
+ return "";
527
+ }
528
+ function buildManifest(pkg, options = {}) {
529
+ const version = options.version ?? DEFAULT_MANIFEST_VERSION;
530
+ const documentMediaType = options.documentMediaType ?? readMimetype(pkg);
531
+ 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");
532
+ const entries = [{
533
+ fullPath: "/",
534
+ mediaType: documentMediaType,
535
+ version
536
+ }];
537
+ const partPaths = Object.keys(pkg.parts);
538
+ for (const dir of new Set(subdocumentDirectories(partPaths))) entries.push({
539
+ fullPath: dir,
540
+ mediaType: resolvePartMediaType(dir, void 0, options.mediaTypeOverrides)
541
+ });
542
+ for (const [path, part] of Object.entries(pkg.parts)) {
543
+ if (path === "mimetype" || path === "META-INF/manifest.xml") continue;
544
+ const bytes = part.kind === "binary" ? base64ToBytes(part.base64) : void 0;
545
+ entries.push({
546
+ fullPath: path,
547
+ mediaType: resolvePartMediaType(path, bytes, options.mediaTypeOverrides)
548
+ });
549
+ }
550
+ return {
551
+ version,
552
+ entries
553
+ };
554
+ }
555
+ function buildManifestNodes(manifest) {
556
+ const fileEntries = manifest.entries.map((entry) => {
557
+ const attrs = { "manifest:full-path": encodeXmlText(entry.fullPath) };
558
+ if (entry.version !== void 0) attrs["manifest:version"] = encodeXmlText(entry.version);
559
+ attrs["manifest:media-type"] = encodeXmlText(entry.mediaType);
560
+ return el("manifest:file-entry", attrs);
561
+ });
562
+ return [{
563
+ type: "declaration",
564
+ attributes: [{
565
+ name: "version",
566
+ value: "1.0"
567
+ }, {
568
+ name: "encoding",
569
+ value: "UTF-8"
570
+ }]
571
+ }, el("manifest:manifest", {
572
+ ...xmlnsAttributes(["manifest"]),
573
+ "manifest:version": encodeXmlText(manifest.version)
574
+ }, fileEntries)];
575
+ }
576
+ function writeManifest(pkg, manifest) {
577
+ pkg.parts[MANIFEST_PART] = {
578
+ kind: "xml",
579
+ nodes: buildManifestNodes(manifest)
580
+ };
581
+ }
582
+ function syncManifest(pkg, options) {
583
+ writeManifest(pkg, buildManifest(pkg, options));
584
+ }
585
+ function validateManifest(pkg) {
586
+ const problems = [];
587
+ const manifestPart = pkg.parts[MANIFEST_PART];
588
+ if (manifestPart === void 0) {
589
+ problems.push({
590
+ severity: "error",
591
+ message: `package has no ${MANIFEST_PART} part`
592
+ });
593
+ return problems;
594
+ }
595
+ if (manifestPart.kind !== "xml") {
596
+ problems.push({
597
+ severity: "error",
598
+ message: `${MANIFEST_PART} part is not XML`
599
+ });
600
+ return problems;
601
+ }
602
+ const root = findChildElement(manifestPart.nodes, "manifest:manifest");
603
+ if (root === void 0) {
604
+ problems.push({
605
+ severity: "error",
606
+ message: `${MANIFEST_PART} has no manifest:manifest root element`
607
+ });
608
+ return problems;
609
+ }
610
+ let manifest;
611
+ try {
612
+ manifest = readManifest(pkg);
613
+ } catch (error) {
614
+ problems.push({
615
+ severity: "error",
616
+ message: `failed to parse ${MANIFEST_PART}: ${error instanceof Error ? error.message : String(error)}`
617
+ });
618
+ return problems;
619
+ }
620
+ const rootEntry = manifest.entries.find((entry) => entry.fullPath === "/");
621
+ if (rootEntry === void 0) problems.push({
622
+ severity: "error",
623
+ message: "manifest has no root (\"/\") entry"
624
+ });
625
+ else {
626
+ const documentMediaType = readMimetype(pkg);
627
+ if (documentMediaType !== void 0 && documentMediaType !== rootEntry.mediaType) problems.push({
628
+ severity: "error",
629
+ message: `manifest root entry media type "${rootEntry.mediaType}" does not match the mimetype part's media type "${documentMediaType}"`,
630
+ path: "/"
631
+ });
632
+ }
633
+ const partPaths = new Set(Object.keys(pkg.parts).filter((path) => path !== "mimetype" && path !== "META-INF/manifest.xml"));
634
+ const manifestPaths = new Set(manifest.entries.map((entry) => entry.fullPath));
635
+ for (const entry of manifest.entries) {
636
+ if (entry.fullPath === "/" || entry.fullPath.endsWith("/")) continue;
637
+ if (!partPaths.has(entry.fullPath)) problems.push({
638
+ severity: "warning",
639
+ message: `manifest lists "${entry.fullPath}" but the package has no such part`,
640
+ path: entry.fullPath
641
+ });
642
+ }
643
+ for (const path of partPaths) if (!manifestPaths.has(path)) problems.push({
644
+ severity: "warning",
645
+ message: `package has part "${path}" not listed in the manifest`,
646
+ path
647
+ });
648
+ for (const child of root.children) {
649
+ if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
650
+ if (!child.children.some((grandchild) => grandchild.type === "element" && grandchild.tag === "manifest:encryption-data")) continue;
651
+ const fullPath = attrValue(child, "manifest:full-path");
652
+ if (fullPath === void 0) continue;
653
+ problems.push({
654
+ severity: "warning",
655
+ message: `entry "${fullPath}" carries manifest:encryption-data -- odf.js does not implement ODF encryption/decryption`,
656
+ path: fullPath
657
+ });
658
+ }
659
+ return problems;
660
+ }
661
+ function setDocumentMediaType(pkg, mediaType, version = DEFAULT_MANIFEST_VERSION) {
662
+ writeMimetype(pkg, mediaType);
663
+ const rootEntry = {
664
+ fullPath: "/",
665
+ mediaType,
666
+ version
667
+ };
668
+ if (pkg.parts["META-INF/manifest.xml"] === void 0) {
669
+ writeManifest(pkg, {
670
+ version,
671
+ entries: [rootEntry]
672
+ });
673
+ return;
674
+ }
675
+ const existing = readManifest(pkg);
676
+ const rootIndex = existing.entries.findIndex((entry) => entry.fullPath === "/");
677
+ writeManifest(pkg, {
678
+ version,
679
+ entries: rootIndex === -1 ? [rootEntry, ...existing.entries] : existing.entries.map((entry, index) => index === rootIndex ? rootEntry : entry)
680
+ });
681
+ }
682
+ //#endregion
683
+ export { AttributeSchema, BinaryPartSchema, MANIFEST_PART, MIMETYPE_PART, ManifestEntrySchema, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, PackageSchema, PartSchema, XmlCdataSchema, XmlCommentSchema, XmlDeclarationSchema, XmlElementSchema, XmlNodeSchema, XmlPartSchema, XmlPiSchema, XmlTextSchema, base64ToBytes, buildManifest, buildXml, bytesToBase64, decodePackage, el, encodePackage, encodeXmlText, isXmlNode, mediaTypeForExtension, packageCodec, parsePackage, parseXml, readManifest, readMimetype, serializePackage, setDocumentMediaType, sniffImageFormat, syncManifest, txt, unzipPackage, validateManifest, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odf.js",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Type-safe, lossless round-trip conversion between OpenDocument Format packages (odt, ods, odp) and JSON, hand-written and dependency-minimal, built on Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {