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 +373 -0
- package/dist/index.d.cts +104 -1
- package/dist/index.d.ts +104 -1
- package/dist/index.js +353 -1
- package/package.json +1 -1
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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.
|
|
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": {
|