feat(document-schema.js): record what a node's content was in the source - #1202
Conversation
fcbwilliams
commented
Sep 10, 2026
The content model deliberately flattens different source constructs onto the same node, and a consumer holding one cannot tell which it has. A `ContentTable` is a native table, a chart's cached series/category data, or a spreadsheet range; a `ContentParagraph` is body prose or a SmartArt node's label. That distinction is not cosmetic. A chart's cached numbers are exact and quotable; a SmartArt diagram's labels have lost the relationships between them (a five-stage process arrives as five labels with no indication it is a sequence); and once a vision pass exists, an image's recovered text is a model's reading rather than the document's words. A consumer that cannot tell them apart treats all three as equally authoritative. `ContentOrigin` is `"chart" | "diagram" | "image"`, optional on `ContentParagraph`, `ContentTable` and `ContentImageBlock` -- the three variants that can carry content from a construct other than their own kind. Absence is the common case and means the node is exactly what its kind says: authored body content. Deliberately not added to `pageBreak`/`constructStart`/`constructEnd`, which have no content to have an origin for. Each value is here because a reader can actually distinguish it and a consumer can act on it; the vocabulary is open to extension rather than complete. `styleId` was not a candidate: it is a producer's own style name, meaningful only to a consumer that already knows that producer's convention, and it says nothing at all for a chart or a diagram. ooxml.js sets the two it can today: `readChartTable` marks its table `"chart"`, and `readDiagramText` marks every node paragraph `"diagram"`. `"image"` is reserved for the vision work in #1197 and set by nobody yet. Additive and optional throughout, so no existing document, reader, writer or consumer changes behaviour. Two notes for review, both places the change had to be made twice: `ContentTable`'s TypeScript interface is hand-written rather than inferred (z.lazy collapses a recursive child to `unknown` in the pinned Zod version), so the field is declared on both the interface and the schema. Adding it to only one compiles away silently -- the runtime validated it while `tsc` denied it existed, which is how I found this. Six hand-authored JSON Schema fragments needed it, not three: `HeadingParagraph`, `ListParagraph` and `ContentSheetImage` inherit the field by extending their base schemas. The live-`z.toJSONSchema()` comparison test caught every one. Refs #1197.
a04805f to
e6f10c3
Compare
|
Your branch is rebased onto current main with the representation side resolved against the landed schema work: the schema-side hunks of your PR (the three-value What your branch now carries is the durable part: the ooxml.js reader wiring ( One naming note: your absence-means-authored-body reading is preserved by the merged shape — absence on the six-value enum still means "the reader has nothing to say", so nothing in your wiring needs to mark ordinary prose. |
|
🎉 This PR is included in version 8.12.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |