odf.js 2.6.8 → 2.6.9
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/README.md +53 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
> A hand-written, dependency-minimal codec for the OpenDocument Format (ODF — OASIS/ISO 26300): `.odt`/`.ods`/`.odp`/`.odg`/`.odf`/`.odb`/`.odm` and their template variants, built on [Zod 4](https://zod.dev) codecs.
|
|
6
6
|
|
|
7
|
-
`odf.js` is the ODF sibling of [`ooxml.js`](https://github.com/ExaDev/ooxml.js), mirroring its architecture
|
|
7
|
+
`odf.js` is the ODF sibling of [`ooxml.js`](https://github.com/ExaDev/ooxml.js), mirroring its architecture: a lossless ZIP-of-XML core that round-trips any package byte-for-content-faithful, with typed readers layered on top. Two ODF-specific differences shape the design: ODF has no relationship mechanism (inter-part references are direct paths, with an exhaustive `META-INF/manifest.xml`), and ODF has no inline/direct formatting — every formatting difference must be a named "automatic style," so `odf.js` owns a style-interning subsystem (`src/styles/`) with no OOXML equivalent.
|
|
8
8
|
|
|
9
|
-
**This package does not depend on `ooxml.js
|
|
10
|
-
|
|
11
|
-
Both packages **do** depend on [`document-schema.js`](https://github.com/ExaDev/document-schema.js), the genuinely shared canonical schema for `ContentDocument`/`LayoutDocument` — the semantic content model (paragraphs, runs, tables, shapes, slides) both an ODF and an OOXML reader ultimately produce. `odf.js`'s typed readers return the real, imported `ContentSection`/`ContentSlide`/etc. types from that package, not a structurally-similar lookalike, so a downstream consumer (`documents.js`) can run an `.odt` through the exact same layout/pagination engine it already uses for `.docx`, unmodified.
|
|
9
|
+
**This package does not depend on `ooxml.js`.** `ooxml.js`'s branding and SBOM are scoped to ECMA-376/OOXML; depending on it would be the wrong signal for an OASIS-standard codec and would force a breaking `ooxml.js` release for every ODF-only fix. The small generic ZIP/XML/`Package` layer is duplicated, kept structurally identical so TypeScript's structural typing makes both packages' values interchangeable for a shared consumer like `documents.js`. Both depend on [`document-schema.js`](https://github.com/ExaDev/document-schema.js) for the genuinely identical `ContentDocument`/`LayoutDocument` content model.
|
|
12
10
|
|
|
13
11
|
```mermaid
|
|
14
12
|
graph TD
|
|
@@ -54,21 +52,19 @@ graph TD
|
|
|
54
52
|
|
|
55
53
|
## Status
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- **Lossless core** — generic ZIP-of-XML primitives (`Package`/`XmlNode`/`XmlElement`, XML parse/build, zip/unzip, base64, the `packageCodec`/`xmlCodec` `z.codec()` pairs) with zero ODF-specific knowledge.
|
|
60
|
-
- **Namespaces, media types, mimetype, manifest** (`src/ns.ts`, `src/media-type.ts`, `src/mimetype.ts`, `src/manifest.ts`) — full read *and* write, including `META-INF/manifest.xml`'s exhaustive per-part enumeration and the mimetype part's mandatory first-entry/stored/uncompressed byte layout, verified against real LibreOffice-produced output.
|
|
61
|
-
- **Style interning** (`src/styles/`) — `StyleRegistry`: adopts a part's existing automatic styles on construction, finds-or-mints on `intern()`, fingerprints on canonical serialized properties plus parent style name (never `JSON.stringify`), and is collision-checked across all four style containers a document can have.
|
|
62
|
-
- **Shared typed primitives** (`src/typed/shared/`) — ODF length-unit parsing (`cm`/`mm`/`in`/`pt`/`pc`/`px`), A1-style spreadsheet cell-reference computation with repeat-count cursor advancement, colour/geometry/master-page-size parsing into `document-schema.js`'s own types, ODF's `text:s`/`text:tab`/`text:line-break` whitespace-run decoding, the read-side style cascade (`style:default-style` → parent chain → the referenced style — one layer shorter than OOXML's, since ODF has no separate direct-formatting layer on top), the shared `text:p` → `ContentParagraph`/`ContentRun` and `table:table` → `ContentTable` readers (`readOdfParagraph`/`readOdfTable`) every typed reader below builds on, the `draw:transform`/`draw:g` group-flattening geometry resolver, an `svg:d`/`draw:points` vector-path grammar parser, and `meta.xml` reading.
|
|
63
|
-
- **Typed readers** — `readOdt` (wordprocessing), `readOdp` (presentation slides: `draw:frame`/`draw:g` text/image/table content), `readOdg` (drawing pages: every vector primitive — `draw:rect`/`draw:ellipse`/`draw:circle`/`draw:line`/`draw:path`/`draw:polygon`/`draw:polyline`, plus a recognised `draw:custom-shape` preset subset — in real `draw:z-index`-aware paint order), and `readOds` (spreadsheets: geometry- and print-settings-rich, every `office:value-type` variant with its own OpenFormula string, plus cell- and page-anchored `draw:frame` images and embedded ODF sub-documents) each resolve a `Package` into `document-schema.js`'s own `ContentSection`/`ContentSlide`/`ContentDrawPage`/`ContentSheet` shapes.
|
|
64
|
-
- **`readOdfFormula`** resolves a standalone or embedded `.odf` formula's `content.xml` — bare MathML with no `office:document-content` wrapper, confirmed against real LibreOffice output — into its raw MathML nodes plus, when present, the formula's own native StarMath annotation string, in this reader's own bespoke `OdfFormulaDocument` shape. **`readOdfFormulaDocument`** wraps that same result into a real `document-schema.js` `ContentDocument` of kind `'formula'` — `document-schema.js` 2.0.0 added a `MathMlNode`/`ContentFormula` pivot shape for exactly this, a structural mirror of this package's own `XmlNode` that `readOdfFormula`'s real output assigns to with zero cast.
|
|
65
|
-
- **`readOdm`** resolves a `.odm` master document's own `content.xml` into an ordered list of chapter references (`{ name, href, filterName? }`, one per top-level linked `text:section`) without opening the external `.odt` files those references point at. A master document's chapters are genuinely external files by ODF design, not embedded package sub-documents — confirmed against real LibreOffice output, which never caches a chapter's own content inside the master document itself (see `src/typed/odm/read.ts`'s own top-of-file note).
|
|
66
|
-
- **`readOdbInventory`** resolves a `.odb` database front-end package into connection info, its table *names*, its query *definitions* (name, real `db:command` SQL text, and `db:escape-processing` when declared), and its forms/reports as `{ name, href }` pairs. Confirmed against real LibreOffice output that `office:database` lives directly in the package's ordinary `content.xml` (no separate `database/connection.xml` part, contrary to the OASIS schema's own chapter layout suggesting one), that queries are declared inline (`db:queries/db:query`, no manifest part of their own), and that a live engine's own tables have no ODF-level manifest listing at all. A form's/report's sub-document directory is named after an opaque *persistent* name (`forms/Obj11`), **not** after the form/report — the user-visible name lives only in `content.xml`'s own `db:forms`/`db:reports` registry, which is where this reader takes both the name and the href from. See `src/typed/odb/read.ts`'s own top-of-file note for the full findings.
|
|
67
|
-
- **`readOdbForm`** and **`readOdbReport`** open one of those sub-documents and extract its *static structure*, executing nothing. A form sub-document is a complete, ordinary ODF text document (`readOdt` reads it unmodified through `subDocumentPackage`, a synthetic sub-`Package` view over the sub-document's own directory) plus an `office:text/office:forms` control tree: per form, its `form:command`/`form:command-type` binding, every control's real element tag, UNO control implementation and `form:data-field` binding, and any genuinely nested sub-form with its own independent binding. A report sub-document uses the `rpt:` Report Builder vocabulary: the `rpt:command`/`rpt:command-type` data binding, the band stack (report header, page header, detail, page footer, report footer), the recursive group tree with each group's own expression/sort/page-break attributes and header/footer bands, every band's bound fields (`field:[COLUMN]`, unwrapped into a real column name) and computed expressions (`rpt:SUM([AMOUNT])`, left verbatim), and the report's own `rpt:function` declarations. Both are grounded in a real, LibreOffice-generated fixture rather than in the schema — which is what caught the two shapes an assumed reading gets wrong: the detail band is nested *inside the innermost group*, not a sibling of the other bands, and a group's key is a formula (`rpt:HASCHANGED("REGION")`), not a bare column name. See `src/typed/odb/form.ts`'s and `src/typed/odb/report.ts`'s own top-of-file notes.
|
|
55
|
+
Under active development. Built and shipped:
|
|
68
56
|
|
|
69
|
-
|
|
57
|
+
- **Lossless core** — ZIP-of-XML primitives (`Package`/`XmlNode`/`XmlElement`, XML parse/build, zip/unzip, base64, the `packageCodec`/`xmlCodec` `z.codec()` pairs).
|
|
58
|
+
- **Namespaces, media types, mimetype, manifest** (`src/ns.ts`, `src/media-type.ts`, `src/mimetype.ts`, `src/manifest.ts`) — full read and write, including `META-INF/manifest.xml` and the mimetype part's mandatory first-entry/stored/uncompressed layout.
|
|
59
|
+
- **Style interning** (`src/styles/`) — `StyleRegistry` adopts existing automatic styles, finds-or-mints on `intern()`, fingerprints on canonical serialized properties (never `JSON.stringify`), collision-checked across all four style containers.
|
|
60
|
+
- **Shared typed primitives** (`src/typed/shared/`) — unit parsing, A1 cell-reference computation with repeat-count cursor advancement, colour/geometry/master-page parsing into `document-schema.js` types, whitespace-run decoding, the read-side style cascade, shared `readOdfParagraph`/`readOdfTable`, the `draw:transform`/`draw:g` group-flattening geometry resolver, an `svg:d`/`draw:points` path parser, and `meta.xml` reading.
|
|
61
|
+
- **Typed readers** — `readOdt` (wordprocessing), `readOdp` (presentation), `readOdg` (drawing: vector primitives in `draw:z-index`-aware paint order), and `readOds` (spreadsheet: every `office:value-type`, cell/page-anchored images, and embedded sub-documents) each resolve a `Package` into `document-schema.js`'s own `ContentSection`/`ContentSlide`/`ContentDrawPage`/`ContentSheet` shapes.
|
|
62
|
+
- **`readOdfFormula`** — resolves a standalone/embedded `.odf` formula's bare-MathML `content.xml` into raw MathML nodes plus a StarMath annotation. **`readOdfFormulaDocument`** wraps that into a real `'formula'`-kind `ContentDocument`.
|
|
63
|
+
- **`readOdm`** — resolves a `.odm` master document into an ordered list of chapter references (`{ name, href, filterName? }`); chapters are genuinely external `.odt` files by ODF design, never cached.
|
|
64
|
+
- **`readOdbInventory`** — resolves a `.odb` into connection info, table names, query definitions (`{ name, command, escapeProcessing? }` with real SQL text), and form/report `{ name, href }` pairs. A sub-document directory is named after an opaque *persistent* name (`forms/Obj11`), not the user-visible name.
|
|
65
|
+
- **`readOdbForm`/`readOdbReport`** — extract one sub-document's *static structure*, executing nothing: a form's control tree and data bindings, or a report's band stack, recursive group tree, bound fields, and computed expressions.
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
Not yet built: live-view editors and the `.odb` database-table-export subsystem. A general-purpose SQL query engine for rendering a Report against its data is **deliberately not attempted** — building even a bounded SQL engine means reimplementing HSQLDB's/Firebird's query semantics, a materially different undertaking from decoding their file formats, with unreviewed licensing questions. Gated on the requesting engineer's explicit sign-off.
|
|
72
68
|
|
|
73
69
|
## Getting started
|
|
74
70
|
|
|
@@ -125,7 +121,7 @@ syncManifest(pkg); // rebuilds manifest.xml to exactly match pkg's current parts
|
|
|
125
121
|
readMimetype(pkg); // 'application/vnd.oasis.opendocument.text'
|
|
126
122
|
```
|
|
127
123
|
|
|
128
|
-
Every module is also importable directly by its own subpath, without going through the barrel
|
|
124
|
+
Every module is also importable directly by its own subpath, without going through the barrel:
|
|
129
125
|
|
|
130
126
|
```ts
|
|
131
127
|
import { parseOdfLength } from 'odf.js/typed/shared/units';
|
|
@@ -133,68 +129,64 @@ import { parseOdfLength } from 'odf.js/typed/shared/units';
|
|
|
133
129
|
parseOdfLength('2.5cm'); // 70.86614173228347
|
|
134
130
|
```
|
|
135
131
|
|
|
136
|
-
Any `src/**/*.ts` module (excluding tests and
|
|
132
|
+
Any `src/**/*.ts` module (excluding tests and `test-support/` fixtures) resolves at its path relative to `src/` — `src/manifest.ts` as `odf.js/manifest`, `src/typed/odt/read.ts` as `odf.js/typed/odt/read`, and so on.
|
|
137
133
|
|
|
138
134
|
## Architecture
|
|
139
135
|
|
|
140
|
-
Layered from a lossless core outward, mirroring `ooxml.js
|
|
141
|
-
|
|
142
|
-
- **`src/model/`** — `Package`/`XmlNode`/`XmlElement
|
|
143
|
-
- **`src/xml/`** —
|
|
144
|
-
- **`src/image/`** — `sniffImageFormat
|
|
145
|
-
- **`src/zip.ts`** — takes *ordered* `[path, entry]` tuples, not a `Record`,
|
|
146
|
-
- **`src/package-io/`** — `write.ts` hoists
|
|
147
|
-
- **`src/manifest.ts`** —
|
|
148
|
-
- **`src/styles/`** — `properties.ts
|
|
149
|
-
- **`src/typed/shared/`** —
|
|
150
|
-
- **`src/typed/odt/`, `
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
See the top of this README — the short version: `ooxml.js`'s branding and signed SBOM make it the wrong dependency for an OASIS-standard package regardless of how much low-level code the two could share; `document-schema.js` is the neutral package both actually depend on for the parts that are genuinely, permanently identical (the semantic content vocabulary), while the ZIP-of-XML primitive layer stays duplicated on purpose.
|
|
136
|
+
Layered from a lossless core outward, mirroring `ooxml.js`:
|
|
137
|
+
|
|
138
|
+
- **`src/model/`** — `Package`/`XmlNode`/`XmlElement`: a duplicate-by-design copy of `ooxml.js`'s equivalent.
|
|
139
|
+
- **`src/xml/`** — XML parse/build (`fast-xml-parser`), production element/text-node construction, entity encoding, and tree-query helpers.
|
|
140
|
+
- **`src/image/`** — `sniffImageFormat`: a PNG/JPEG magic-byte sniffer consumed by `src/manifest.ts` and `src/typed/draw/shapes.ts`.
|
|
141
|
+
- **`src/zip.ts`** — takes *ordered* `[path, entry]` tuples, not a `Record`, so the mimetype-first/stored/uncompressed requirement doesn't depend on insertion order surviving a Zod round trip.
|
|
142
|
+
- **`src/package-io/`** — `write.ts` hoists `mimetype` first (stored) and `META-INF/manifest.xml` second if present; never fabricates either as a side effect.
|
|
143
|
+
- **`src/manifest.ts`** — full manifest read/write; the manifest is ODF's one mandatory part, unlike `ooxml.js`'s read-only OPC-relationship stance.
|
|
144
|
+
- **`src/styles/`** — `properties.ts`/`serialize.ts` (canonical property-bag ↔ XML attributes), `registry.ts` (`StyleRegistry`, the mandatory style-interning layer), `span.ts` (character-range `text:span` wrapping).
|
|
145
|
+
- **`src/typed/shared/`** — ODF-specific typed primitives every reader builds on (units, A1 cursors, colour/geometry, whitespace runs, style cascade, shared paragraph/table readers, transform/path parsing, metadata).
|
|
146
|
+
- **`src/typed/odt/`, `odp/`, `odg/`, `ods/`** — the `readOdt`/`readOdp`/`readOdg`/`readOds` readers.
|
|
147
|
+
- **`src/typed/draw/`** — the shared `draw:frame`/`draw:g`/vector shape vocabulary (`shapes.ts`), plus `embedded.ts` (`readDrawObjectReference`, `readDrawImageBlock`).
|
|
148
|
+
- **`src/typed/formula/`, `odm/`** — `readOdfFormula`/`readOdfFormulaDocument` and `readOdm`.
|
|
149
|
+
- **`src/typed/odb/`** — `readOdbInventory`, `readOdbForm`/`readOdbReport`, `resolveOdbComponent`, `subDocumentPackage`.
|
|
155
150
|
|
|
156
151
|
## Conventions
|
|
157
152
|
|
|
158
153
|
- **Zod-first schema/type/guard**, matching `ooxml.js`/`document-schema.js`: every model type is inferred from its Zod schema, never hand-written.
|
|
159
|
-
- **Recursive types use a hand-written structural guard, not `z.lazy`**
|
|
160
|
-
- **No type assertions anywhere** — `assertionStyle: 'never'`, `noInlineConfig: true
|
|
161
|
-
- **Ground truth over memory for every ODF spec fact
|
|
154
|
+
- **Recursive types use a hand-written structural guard, not `z.lazy`** (collapses to `unknown` in the pinned Zod version).
|
|
155
|
+
- **No type assertions anywhere** — `assertionStyle: 'never'`, `noInlineConfig: true`.
|
|
156
|
+
- **Ground truth over memory for every ODF spec fact** — namespace URIs, media types, and attribute names are verified against the OASIS spec or real LibreOffice output, never assumed from an OOXML analogue (see [Gotchas](#gotchas-and-quirks)).
|
|
162
157
|
|
|
163
158
|
## Gotchas and quirks
|
|
164
159
|
|
|
165
|
-
- **Several ODF namespace URIs are not what you'd guess from the prefix.** `draw:` is `...
|
|
166
|
-
- **`.odb`'s
|
|
167
|
-
-
|
|
168
|
-
- **`meta:keyword` appears once per keyword**, unlike OOXML's single comma-separated `cp:keywords
|
|
169
|
-
- **`table:number-columns-repeated
|
|
170
|
-
- **ODF cells carry no explicit cell-reference attribute
|
|
171
|
-
- **A rotated `draw:rect`/`
|
|
172
|
-
- **Every `ContentShape`/`ContentVector` a
|
|
173
|
-
- **`svg:fill-rule`
|
|
174
|
-
- **`readOds
|
|
175
|
-
- **`readOds`
|
|
176
|
-
- **`readDrawObjectReference`
|
|
177
|
-
- **An embedded
|
|
178
|
-
- **A `draw:frame`'s alternative text (`svg:title`, falling back to `svg:desc`)
|
|
179
|
-
- **`
|
|
180
|
-
-
|
|
181
|
-
- **`.odb` Form/Report structure extraction is real and working, grounded in a genuine LibreOffice-generated fixture, not blocked.** `readOdbForm`/`readOdbReport` open a form's or report's own sub-document (via `subDocumentPackage`, a synthetic sub-`Package` view over its directory) and extract its complete static structure — command bindings, control trees, nested sub-forms, the band/group hierarchy, bound and computed expressions — executing nothing. See the Architecture section above and `src/typed/odb/form.ts`/`report.ts`'s own top-of-file notes for the two real shapes this fixture caught that an assumed reading would have got wrong. **A SQL/`rpt:` rendering engine to actually execute a query or evaluate a report's own totals against real data is deliberately not attempted**, not merely unstarted — that is a materially different, larger undertaking (reimplementing a slice of HSQLDB's/Firebird's own query semantics) with its own unreviewed licensing question, gated on the requesting engineer's explicit sign-off after reading the relevant engine source. See the bounded-SQL-subset assessment immediately below for exactly how far the *reading* side alone gets you.
|
|
182
|
-
|
|
183
|
-
- **A bounded-SQL-subset assessment for Report rendering exists for exactly one real report, and it should be read that way.** `src/typed/odb/fixtures/form-and-report.odb`'s own "SalesByRegion" report is bound (`rpt:command-type="query"`) to a saved query whose real SQL text is `SELECT "SALES"."REGION", "SALES"."QUARTER", "SALES"."CUSTOMER", "SALES"."AMOUNT" FROM "SALES" WHERE "SALES"."AMOUNT" >= 100 ORDER BY "SALES"."REGION" ASC, "SALES"."QUARTER" ASC, "SALES"."AMOUNT" DESC` — single-table, a simple comparison `WHERE`, a multi-column `ORDER BY`, no `JOIN`, no subquery, and (in the SQL itself) no `GROUP BY` or aggregate function at all. That one query sits entirely inside the bounded subset described above. The fixture's bound form is even simpler: its top-level `form:form` binds directly to the bare table name `SALES` (`form:command-type="table"`), equivalent to an unconditional `SELECT * FROM "SALES"`. On this single data point, 100% of the real `.odb` command bindings seen so far (one query, one table binding, one repeat use of the same query from a nested sub-form) fall inside the bounded subset — but a sample of one report from one fixture says essentially nothing about the real-world distribution of `.odb` files in the wild, and should not be quoted as a coverage percentage beyond "the one file we have." **A more consequential finding sits alongside the SQL text itself: even a fully bounded SQL engine would not be sufficient to render this one report.** The report's own grouping breaks (`rpt:HASCHANGED("REGION")`, `rpt:HASCHANGED("LEFT_QUARTER")`), its prefix-character grouping function (`rpt:LEFT([QUARTER];2)`), and its running per-group/per-report totals (`rpt:SUM([AMOUNT])`) are all evaluated by Report Builder's own `rpt:` formula language over the plain, ungrouped, ordered row stream the SQL query returns — LibreOffice does not express any of that as SQL `GROUP BY`/aggregate syntax at all. Rendering even this one simple report therefore needs a bounded SQL engine *and* a separate `rpt:` formula evaluator (`HASCHANGED`, `LEFT`, `SUM`, and whatever else real reports use) — two genuinely different pieces of engine-semantics reimplementation, not one.
|
|
160
|
+
- **Several ODF namespace URIs are not what you'd guess from the prefix.** `draw:` is `...drawing:1.0`, `number:` is `...datastyle:1.0`, `fo:`/`svg:`/`smil:` are `*-compatible:1.0`. See `src/ns.ts`.
|
|
161
|
+
- **`.odb`'s media type is `application/vnd.oasis.opendocument.base`**, not `...database`.
|
|
162
|
+
- **`dc:creator` records whoever most recently *saved* the document, not the author** — the original author is `meta:initial-creator`.
|
|
163
|
+
- **`meta:keyword` appears once per keyword**, unlike OOXML's single comma-separated `cp:keywords`.
|
|
164
|
+
- **`table:number-columns-repeated`/`-rows-repeated` must be cursor-advanced, never materialized** — real sheets have trailing repeat counts over a million.
|
|
165
|
+
- **ODF cells carry no explicit cell-reference attribute** (unlike xlsx's `r="B7"`) — `typed/shared/a1.ts` computes references from a running cursor.
|
|
166
|
+
- **A rotated `draw:rect`/`ellipse`/`path`/`custom-shape` reads its own `rotationDeg`** via the same `resolveOdfShapeGeometry` machinery `draw:frame` uses, composing any enclosing `draw:g` rotation.
|
|
167
|
+
- **Every `ContentShape`/`ContentVector` carries a resolved `paintOrder`** so true relative paint order survives across the independently-ordered `shapes`/`vectors` arrays.
|
|
168
|
+
- **`svg:fill-rule` and `draw:stroke` map onto `ContentVector.fillRule`/`ContentStroke.style`.** A dotted pattern and `"double"` stroke have no ODF vector-stroke counterpart and remain unread.
|
|
169
|
+
- **`readOds`/`readTableCell` resolve cell `background`/`borders`/`alignment`/`verticalAlignment` from the real style cascade.** An explicit `fo:border-*` of `"none"`/`"hidden"` clears an inherited edge.
|
|
170
|
+
- **`readOds` reads sheet-anchored drawings** — cell-anchored `draw:frame`s (coordinates relative to the cell) and page-anchored ones (in `table:shapes`). A sheet cannot carry a floating text box, bare vector, or embedded chart; each is skipped.
|
|
171
|
+
- **`readDrawObjectReference` resolves a frame's embedded sub-document kind from its own `content.xml`, not the manifest.** A `draw:object` must be checked *before* the frame's preview image, since an embedded-object frame also carries a preview `draw:image`.
|
|
172
|
+
- **An embedded Math object in a spreadsheet cell reads as `objectKind: 'formula'`** — its `content.xml` root *is* the MathML root, so `readDrawObjectReference` falls back to `findMathRoot` and dispatches to `readOdfFormulaDocument`.
|
|
173
|
+
- **A `draw:frame`'s alternative text (`svg:title`, falling back to `svg:desc`) reads into `ContentImageBlock.altText`.**
|
|
174
|
+
- **`readOdbInventory`'s `queries` carry real `db:command` SQL text**, not just names — a breaking rename from `string[]` to `OdbQueryInfo[]`.
|
|
175
|
+
- **`.odb` Form/Report structure extraction is real** (`readOdbForm`/`readOdbReport`), grounded in a genuine fixture. **A SQL/`rpt:` rendering engine to execute a query or evaluate report totals is deliberately not attempted** — see the Status section. Even a fully bounded SQL engine would not suffice to render a report: grouping breaks (`rpt:HASCHANGED`), prefix functions (`rpt:LEFT`), and running totals (`rpt:SUM`) are evaluated by Report Builder's own `rpt:` formula language, not by SQL.
|
|
184
176
|
|
|
185
177
|
## Release and publishing
|
|
186
178
|
|
|
187
|
-
`.github/workflows/ci.yml` runs commitlint, lint, typecheck,
|
|
179
|
+
`.github/workflows/ci.yml` runs commitlint, lint, typecheck, unit, and smoke tests on every push/PR. On a push to `main` where all pass, `release.config.ts` drives [semantic-release](https://semantic-release.gitbook.io/semantic-release): version bump from commit history, `CHANGELOG.md`/`package.json` committed back, GitHub Release cut, and npm publish via OIDC trusted publishing (no `NPM_TOKEN`). Once a release publishes (detected by diffing `package.json`'s version): a `sibling-released` event dispatches to `documents.js`/`document-cli`, the build republishes under `@exadev/odf.js` to GitHub Packages, and an SPDX SBOM plus build-provenance attestation are signed against the tarball.
|
|
188
180
|
|
|
189
181
|
## Contributing
|
|
190
182
|
|
|
191
|
-
|
|
183
|
+
Conventional Commits (`feat:`, `fix:`, `test:`, `chore:`, …), enforced by commitlint via a husky `commit-msg` hook and a CI job. A `pre-commit` hook runs `lint-staged` (`eslint --fix` on staged `*.ts`); `pre-push` runs the test suite. Single `main` branch, no open PR workflow.
|
|
192
184
|
|
|
193
185
|
## References
|
|
194
186
|
|
|
195
|
-
- [ooxml.js](https://github.com/ExaDev/ooxml.js) — the
|
|
196
|
-
- [document-schema.js](https://github.com/ExaDev/document-schema.js) — the canonical `ContentDocument`/`LayoutDocument` schema
|
|
197
|
-
- [documents.js](https://github.com/ExaDev/documents.js) — the downstream consumer
|
|
187
|
+
- [ooxml.js](https://github.com/ExaDev/ooxml.js) — the OOXML sibling; architecturally mirrored, deliberately not depended on.
|
|
188
|
+
- [document-schema.js](https://github.com/ExaDev/document-schema.js) — the canonical `ContentDocument`/`LayoutDocument` schema both packages depend on.
|
|
189
|
+
- [documents.js](https://github.com/ExaDev/documents.js) — the downstream consumer; its `readOdtContent`/`readOdpContent`/`readOdsContent`/`readOdgContent` are thin adapters over this package's readers.
|
|
198
190
|
|
|
199
191
|
## License
|
|
200
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odf.js",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.9",
|
|
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": {
|