What the pkgPackage / pkgManifest / pkgMimetype modules read and write: the
ZIP layout shared by .odt, .ods and .odp.
Prerequisites. @awacloud/odf and @awacloud/fw, with a runtime wired as in
Getting started (fw_require + modules registered on an
@awacloud/fw ModuleRuntime); the snippets below reuse that runtime.
A .odt / .ods / .odp file is a ZIP archive with a convention:
.odt
├── mimetype ← STORED uncompressed, FIRST entry
├── META-INF/
│ └── manifest.xml ← declares the file-entries + media-types
├── content.xml ← main content
├── styles.xml ← named styles + master pages
├── meta.xml ← Dublin Core + ODF metadata
└── settings.xml ← UI config (volatile)
Constraints
mimetypemust be the first ZIP entry.- It must be STORED (compression method 0, no DEFLATE).
- No extra field on the
mimetypeentry.
These constraints let a naive tool sniff an ODF file's type by reading the first ~38 bytes of the file.
Implementation
The pkgPackage module:
- Forces
mimetypeto be the first key of the object passed tozip.zipSync. - Uses the per-file option
[bytes, { level: 0 }]for STORED. - Reads/writes
META-INF/manifest.xmlviapkgManifest.
Minimal API
const pkg = runtime.resolve('pkgPackage');
const mt = runtime.resolve('pkgMimetype');
const xmlBytes = new TextEncoder().encode('<office:document-content/>');
const pngBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
const p = pkg.empty(mt.CT_ODT);
pkg.setPart(p, 'content.xml', xmlBytes, 'text/xml');
pkg.setPart(p, 'Pictures/img1.png', pngBytes, 'image/png');
const bytes = pkg.write(p); // → valid ZIP
Round-trip: what travels
write(read(x)) on odt, ods and odp starts from a fresh package and
fills it in this order:
- Regenerated —
content.xmlfrom the typed model, and the three sidecarsmeta.xml,settings.xml,styles.xml(odfShared.REGENERATED_PARTS). Each sidecar comes fromopts.*, else from the read model'sdoc.meta/doc.settings/doc.styles, else from an empty model, so named styles and settings survive a re-write.meta:generatornames the application that last wrote the file: it is rewritten to@awacloud/odfunless the caller passesopts.meta.generator. - Writer-supplied — parts the facade emits itself (for
odt,doc.pictures). - Carried — every other part of
doc.package(thumbnails, pictures,Configurations2/…, embedded objects), byte-for-byte, with the media type the source manifest declared (application/octet-streamwhen none), plus the source manifest's directory entries (fullPathending in/). - Namespace declarations — every regenerated part declares, on its root,
each namespace prefix it uses. The source part's own root declarations are
reused first (
odfShared.sourceNamespaces), so third-party extension markup kept in_extrasstays bound to its namespace; the known table (odfShared.ODF_PREFIXES) covers the rest. A prefix nobody declares makes the write throwRenderErrorwith codeodf/render-error/namespace.
A regenerated part always wins over a writer-supplied one, which wins over a
carried copy. To drop carried material, delete it from doc.package.parts or
delete doc.package altogether.
Not carried: the extra attributes and children of manifest entries
(manifest:size, encryption data). Not restored: the original position of
a frame inside a paragraph — the read side keeps it in the paragraph's
_extras, which a re-write emits after the runs. Inside a text:span the
markup is kept in place: spacing elements (text:s, text:tab,
text:line-break), nested spans with their own styles, links, fields and
frames anchored in the span keep their position (see
text/paragraph — Span runs). Zip bytes are not identical
across writes (entry timestamps); parts and manifest entries are.
const odt = runtime.resolve('odt');
const pkg = runtime.resolve('pkgPackage');
const src = pkg.read(odt.write(odt.fromText(['x'])));
pkg.setPart(src, 'Thumbnails/thumbnail.png', new Uint8Array([0x89, 0x50, 0x4e, 0x47]), 'image/png');
const doc = odt.read(pkg.write(src));
const out = pkg.read(odt.write(doc));
out.parts['Thumbnails/thumbnail.png']; // carried, same bytes, 'image/png'
delete doc.package;
pkg.read(odt.write(doc)).parts['Thumbnails/thumbnail.png']; // undefined
Difference vs OPC (OOXML)
| Aspect | OPC (OOXML) | Pkg (ODF) |
|---|---|---|
| Type identity | [Content_Types].xml overrides |
STORED mimetype file |
| Relations | Per-part .rels |
Inline xlink:href + global manifest |
| Part naming | Absolute slash /word/document.xml |
No leading slash content.xml |
| Sub-package | Not standard | Sub-document in a directory (Object 1/…), carried as ordinary parts |