ooxml.js 2.6.13 → 2.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +2 -2
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -218,14 +218,14 @@ The package is layered from a lossless core outward to lossy convenience views:
218
218
  - **`src/package-io/`** — `read.ts` and `write.ts` sit between the zip and XML layers: unzip a package into path -> bytes, classify each entry as XML or binary (`looksLikeXml` sniffs the leading non-whitespace byte for `<`), and parse/serialize accordingly.
219
219
  - **`src/codec.ts`** — the public round-trip surface: `packageCodec`/`xmlCodec` are `z.codec()` pairs, and `decodePackage`/`encodePackage` are the ergonomic wrappers around them.
220
220
  - **`src/compact.ts`** — the ooxml.js format: `compactCodec` (`z.codec(PackageSchema, CompactPackageSchema, …)`) maps `Package ⇄ CompactPackage`, with `toCompact`/`fromCompact` as the ergonomic wrappers. `compactPackageCodec` composes `packageCodec` and `compactCodec` into a direct bytes ⇄ `CompactPackage` codec (`decodeCompactPackage`/`encodeCompactPackage`), so all three format pairs — bytes/`Package`, `Package`/`CompactPackage`, bytes/`CompactPackage` — have a named codec rather than requiring callers to chain two.
221
- - **`src/typed/`** — one-way, lossy projections that read the generic `Package` into ergonomic document/presentation/workbook models. `docx/` and `pptx/` share one block content model — `ContentParagraph`/`ContentTable`/`ContentImageBlock`/`ContentPageBreak`, discriminated as `ContentBlock`, imported from the sibling [`document-schema.js`](https://github.com/ExaDev/document-schema.js) package rather than defined here (see below) — instead of each keeping its own, disjoint shape: `readDocx` resolves the full WordprocessingML style cascade (`docx/styles.ts`: `docDefaults` → named-style `basedOn` chains → paragraph-mark run properties → character styles → direct formatting) into ordered `sections` of paragraphs/tables/page-breaks (document order preserved, including inside tables), plus `comments`, `footnotes`, and `headers`/`footers`; `readPptx` resolves the placeholder → layout → master → theme inheritance cascade (`pptx/inherit.ts`) into `slides` of positioned, styled `shapes` (geometry, run/paragraph formatting, embedded images, tables, speaker notes) in presentation order (`p:sldIdLst`, never slide filename order); `readXlsx` covers cell values and formulas, merged ranges and defined names. `typed/shared/` holds the OOXML-specific primitives both `docx/` and `pptx/` build on: `drawingml.ts` (DrawingML `a:xfrm` geometry, theme/colour resolution, group-transform composition including `ColorTransform`/`applyColorTransforms`, the shade/tint/lumMod/lumOff cascade maths, which stays here rather than in `document-schema.js` since it's OOXML-cascade-resolution logic, not a content-model shape), `units.ts` (OOXML unit conversions — EMU/twip/half-point), `metadata.ts` (`docProps/core.xml` + `docProps/app.xml` → `DocumentMetadata`, shared verbatim across docx/pptx/xlsx), and `source-path.ts` (stamps a deterministic, document-order path like `sections[0].blocks[2].runs[1]` onto every `ContentRun`/`ContentBlock`/`ContentShape`, so a downstream consumer can trace a rendered item back to where it came from — see `document-schema.js`'s own `sourcePath` field). Geometry (`Box`/`PageSize`/`Margins`), colour (`Color`/`ColorSchema`), and alignment (`Alignment`) types are imported from `document-schema.js`, not defined locally. `src/image/sniff.ts` (magic-byte PNG/JPEG detection) supports `readPptx`'s picture-shape reading. None of `readDocx`/`readPptx`/`readXlsx` can be encoded back to a `Package` — round-tripping those always goes through `decodePackage`/`encodePackage`, never through a typed view; see `src/typed/xlsx/` below for this package's one write-back exception. `typed/util.ts` holds the shared XML-walking helpers (`walk`, `elementsWithTag`, `childrenWithTag`, `attr`, `rootElement`, `textContent`, entity decoding, `resolveRelationships`) every typed reader builds on.
221
+ - **`src/typed/`** — one-way, lossy projections that read the generic `Package` into ergonomic document/presentation/workbook models. `docx/` and `pptx/` share one block content model — `ContentParagraph`/`ContentTable`/`ContentImageBlock`/`ContentPageBreak`, discriminated as `ContentBlock`, imported from the sibling [`document-schema.js`](https://github.com/ExaDev/document-schema.js) package rather than defined here (see below) — instead of each keeping its own, disjoint shape: `readDocx` resolves the full WordprocessingML style cascade (`docx/styles.ts`: `docDefaults` → named-style `basedOn` chains → paragraph-mark run properties → character styles → direct formatting) into ordered `sections` of paragraphs/tables/page-breaks (document order preserved, including inside tables), plus `comments`, `footnotes`, and `headers`/`footers`; `readPptx` resolves the placeholder → layout → master → theme inheritance cascade (`pptx/inherit.ts`) into `slides` of positioned, styled `shapes` (geometry, run/paragraph formatting, embedded images, tables, speaker notes) in presentation order (`p:sldIdLst`, never slide filename order); `readXlsx` covers cell values and formulas, merged ranges and defined names. `typed/shared/` holds the OOXML-specific primitives both `docx/` and `pptx/` build on: `drawingml.ts` (DrawingML `a:xfrm` geometry, theme/colour resolution, group-transform composition), `color.ts` (`ColorTransform`/`applyColorTransforms`, the shade/tint/lumMod/lumOff cascade maths `drawingml.ts`'s own colour resolution applies — kept in its own module rather than `document-schema.js` since it's OOXML-cascade-resolution logic, not a content-model shape), `units.ts` (OOXML unit conversions — EMU/twip/half-point), `metadata.ts` (`docProps/core.xml` + `docProps/app.xml` → `DocumentMetadata`, shared verbatim across docx/pptx/xlsx), and `source-path.ts` (stamps a deterministic, document-order path like `sections[0].blocks[2].runs[1]` onto every `ContentRun`/`ContentBlock`/`ContentShape`, so a downstream consumer can trace a rendered item back to where it came from — see `document-schema.js`'s own `sourcePath` field). Geometry (`Box`/`PageSize`/`Margins`), colour (`Color`/`ColorSchema`), and alignment (`Alignment`) types are imported from `document-schema.js`, not defined locally. `src/image/sniff.ts` (magic-byte PNG/JPEG detection) supports `readPptx`'s picture-shape reading. None of `readDocx`/`readPptx`/`readXlsx` can be encoded back to a `Package` — round-tripping those always goes through `decodePackage`/`encodePackage`, never through a typed view; see `src/typed/xlsx/` below for this package's one write-back exception. `typed/util.ts` holds the shared XML-walking helpers (`walk`, `elementsWithTag`, `childrenWithTag`, `attr`, `rootElement`, `textContent`, entity decoding, `resolveRelationships`) every typed reader builds on.
222
222
  - **`src/typed/xlsx/`** — a second, `ContentDocument`-shaped xlsx pair alongside the lossy `readXlsx` above, not a replacement for it (both stay exported; they serve different callers). `content.ts`'s `readXlsxContent` reads a `Package` straight into `ContentDocument` (`kind: 'spreadsheet'`): real column widths, row heights, hidden rows/columns, merged ranges (resolved onto the anchor cell's `colSpan`/`rowSpan`), every cell value kind xlsx itself distinguishes, and a genuinely populated `ContentSheetPrintSettings` — matching the bar the sibling `odf.js` package's own `readOds` already sets, rather than `readXlsx`'s flattened `XlsxWorkbook`/`XlsxCell` shape. `build.ts`'s `buildXlsxPackage` is `readXlsxContent`'s write-side inverse and this package's first writer of genuinely new content: given a `ContentDocument`, it constructs a complete xlsx `Package` from scratch — workbook, worksheets, a minimal-but-real `xl/styles.xml`, shared strings, core/app properties — via `xml/fragment.ts`'s `el`/`txt`, rather than editing whatever package `readXlsxContent` itself decoded. `number-format.ts`/`styles.ts`/`serial.ts` are the number-format engine, and run both ways: xlsx has no cell type for a percentage, an amount of money, a date, or a time — all four are plain numeric cells wearing a `numFmt` style — so, reading, `styles.ts` resolves a cell's own style index to a format code, `number-format.ts` tokenizes and classifies that code (its `BUILTIN_NUMBER_FORMATS` table for `numFmtId` 0-49 is fed through the same classifier as a producer-declared code, never a second table of pre-decided kinds), and `serial.ts` converts a date/time serial to the canonical ISO spelling `document-schema.js` fixes. Writing, the same three run in reverse: `number-format.ts` holds the small vocabulary of formats `buildXlsxPackage` emits (built-in ids for a percentage and a time of day; ISO-ordered date/dateTime codes; `[$GBP]#,##0.00` for a currency, so the ISO code itself survives where a bare symbol would not; LibreOffice's own `"TRUE";"TRUE";"FALSE"` for a boolean, so real Excel and Calc display one as TRUE/FALSE rather than as a bare 1/0), `styles.ts`'s `CellFormatTable` interns them on demand into a real `<numFmts>`/`<cellXfs>` pair (a `SharedStringTable` for cell formats — same format, same index; no custom format used, no `<numFmts>` element written at all), and `serial.ts` converts an ISO spelling back to the serial the cell actually carries. Every code the writer emits is fed back through the reader's own classifier in the test suite, so the two halves cannot drift. It classifies rather than renders: `displayText` stays the plain typed-value spelling, not the producer's own formatted string. The remaining scope limits: a `currency` value naming no ISO 4217 code writes as a plain amount format and so reads back as a `number` (nothing in `#,##0.00` says money, and prefixing every such amount with a generic ¤ to keep the kind would change a file's appearance for something its author never asked for); a temporal value that is not the canonical `ContentCellValue` spelling, or that names a moment with no serial, degrades to a text cell carrying the original string verbatim rather than to a fabricated serial; and no per-cell rich-text runs on either side.
223
223
 
224
224
  ## Conventions
225
225
 
226
226
  - **Zod-first schema/type/guard.** Every model type is inferred from its Zod schema (`z.infer<typeof XSchema>`), not hand-written — schema, type, and validator stay in lockstep.
227
227
  - **`XmlNode` uses a recursive structural guard, not `z.lazy`.** `z.lazy` collapses to `unknown` for the element-children case in the Zod version this project pins, so `XmlElementSchema` validates `children` via `z.custom<XmlNode>(isXmlNode)`, a hand-written recursive type guard in `model/node.ts`. Any change to `XmlNode`'s shape must update `isXmlNode` in step. `src/compact.ts`'s `CompactXmlNode` (`isCompactXmlNode` + `z.custom`) reuses the same pattern for the same reason; `document-schema.js`'s own `ContentBlock` (`isContentBlock` + `z.custom`, since a table cell's blocks can themselves contain a table) does too, one level up the dependency graph.
228
- - **Lossless core vs. lossy views is a hard boundary.** `decodePackage`/`encodePackage` (and the underlying codecs) must stay byte/part faithful — every part round-trips unchanged. `src/typed/*`'s lossy readers (`readDocx`, `readPptx`, `readXlsx`) are explicitly one-way and are allowed to drop information (documented per-reader, e.g. `readDocx` resolves cached field-result text rather than re-evaluating live `PAGE`/`NUMPAGES` fields, docx's own `w:themeColor` references aren't resolved, and `readXlsx` drops cell styles, formats and charts). Don't blur this line by adding write-back support to one of those readers; a full round-trip of a package one of them decoded always goes through the generic `Package`. `readXlsxContent`/`buildXlsxPackage` (`src/typed/xlsx/`) are a deliberate, separate exception, not a violation of this rule: they were designed together as a genuine read/write pair around the shared `ContentDocument` model — matching the sibling `odf.js`/`documents.js` packages' own established convention of building fresh output from a `ContentDocument`, rather than editing a decoded package in place — and `buildXlsxPackage` never touches whatever package `readXlsxContent` itself decoded.
228
+ - **Lossless core vs. lossy views is a hard boundary.** `decodePackage`/`encodePackage` (and the underlying codecs) must stay byte/part faithful — every part round-trips unchanged. `src/typed/*`'s lossy readers (`readDocx`, `readPptx`, `readXlsx`) are explicitly one-way and are allowed to drop information (documented per-reader, e.g. `readDocx` resolves cached field-result text rather than re-evaluating live `PAGE`/`NUMPAGES` fields, docx's own `w:themeShade`/`w:themeTint` refinement of an already-resolved theme colour isn't applied, and `readXlsx` drops cell styles, formats and charts). Don't blur this line by adding write-back support to one of those readers; a full round-trip of a package one of them decoded always goes through the generic `Package`. `readXlsxContent`/`buildXlsxPackage` (`src/typed/xlsx/`) are a deliberate, separate exception, not a violation of this rule: they were designed together as a genuine read/write pair around the shared `ContentDocument` model — matching the sibling `odf.js`/`documents.js` packages' own established convention of building fresh output from a `ContentDocument`, rather than editing a decoded package in place — and `buildXlsxPackage` never touches whatever package `readXlsxContent` itself decoded.
229
229
  - **XML entities stay raw in the lossless layer.** `parseXml` runs with `processEntities: false` so encoded entities (e.g. `&amp;`) are preserved verbatim for round-trip fidelity; typed readers decode the five standard entities (`decodeEntities` in `typed/util.ts`) only in their own lossy projection, never in the core model.
230
230
  - **No type assertions.** `eslint.config.ts` runs `@typescript-eslint/consistent-type-assertions` with `assertionStyle: "never"`, banning `as` and angle-bracket casts outright, with `linterOptions.noInlineConfig: true` so there is no `eslint-disable` escape hatch either — narrow with a guard or parse with Zod. An exception would have to be scoped structurally, as a `files`-matched override block in `eslint.config.ts`, not an inline comment.
231
231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ooxml.js",
3
- "version": "2.6.13",
3
+ "version": "2.6.15",
4
4
  "description": "Type-safe, lossless round-trip conversion between OOXML packages (docx, pptx, xlsx) and JSON, built on Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -69,7 +69,7 @@
69
69
  "license": "MIT",
70
70
  "packageManager": "pnpm@11.6.0",
71
71
  "dependencies": {
72
- "document-schema.js": "^2.3.0",
72
+ "document-schema.js": "^2.3.1",
73
73
  "fast-xml-parser": "^5.10.1",
74
74
  "fflate": "^0.8.3",
75
75
  "zod": "^4.4.3"