ooxml.js 2.12.2 → 2.13.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/README.md CHANGED
@@ -106,7 +106,7 @@ const doc = readDocx(decodePackage(bytes));
106
106
  // inside tables); each run already carries its cascade-resolved bold/italic/colour/font.
107
107
  ```
108
108
 
109
- `readXlsx` is the lossy, cell-values-only view. `readXlsxContent`/`buildXlsxPackage` are a separate `ContentDocument`-shaped pair — a richer reader (column widths, row heights, hidden rows/columns, merged ranges, every cell value kind, print settings) matched with this package's first writer, round-tripping a spreadsheet through the same `ContentDocument` shape `documents.js`/`odf.js` use:
109
+ `readXlsx` is the lossy, cell-values-only view. `readXlsxContent`/`buildXlsxPackage` are a separate `ContentDocument`-shaped pair — a richer reader (column widths, row heights, hidden rows/columns, merged ranges, every cell value kind, cell comments, print settings) matched with this package's first writer, round-tripping a spreadsheet through the same `ContentDocument` shape `documents.js`/`odf.js` use:
110
110
 
111
111
  ```ts
112
112
  import { buildXlsxPackage, decodePackage, readXlsxContent } from 'ooxml.js';
@@ -229,7 +229,7 @@ The package layers a lossless core outward to lossy convenience views:
229
229
  - **`src/codec.ts`** — public round-trip surface: `packageCodec`/`xmlCodec` (`z.codec()` pairs) plus `decodePackage`/`encodePackage` wrappers.
230
230
  - **`src/compact.ts`** — the ooxml.js format: `compactCodec`/`compactPackageCodec` plus `toCompact`/`fromCompact`/`decodeCompactPackage`/`encodeCompactPackage` wrappers.
231
231
  - **`src/typed/`** — one-way, lossy projections. `readDocx` resolves the full style cascade (`docDefaults` → `basedOn` → paragraph-mark → character styles → direct formatting) into ordered `sections` plus comments/footnotes/headers/footers/numbering; `readPptx` resolves placeholder → layout → master → theme inheritance into `slides` (presentation order via `p:sldIdLst`); `readXlsx` covers cell values/formulas, merged ranges, defined names. `typed/shared/` holds shared OOXML primitives (`drawingml.ts` geometry/theme/colour, `color.ts` `ColorTransform` cascade, `units.ts`, `metadata.ts`, `source-path.ts`). Types come from `document-schema.js`. None encodes back to a `Package` — round-trip goes through `decodePackage`/`encodePackage` (see `src/typed/xlsx/` for the one write-back exception).
232
- - **`src/typed/xlsx/`** — a `ContentDocument`-shaped read/write pair alongside the lossy `readXlsx` (both exported; different callers). `readXlsxContent` reads column widths, row heights, hidden rows/columns, merged ranges, every cell value kind, print settings; `buildXlsxPackage` builds a complete xlsx `Package` from scratch (never editing the decoded package). `number-format.ts`/`styles.ts`/`serial.ts` run both ways: reading classifies style index → format code → kind (`percentage`/`currency`/`date`/`time`/`dateTime`); writing emits interned `numFmt` codes, fed back through the classifier in tests. The classifier is not a formatter (`displayText` is the typed-value spelling). Scope limits: `currency` with no ISO code writes as plain `number`; non-canonical temporal values degrade to text.
232
+ - **`src/typed/xlsx/`** — a `ContentDocument`-shaped read/write pair alongside the lossy `readXlsx` (both exported; different callers). `readXlsxContent` reads column widths, row heights, hidden rows/columns, merged ranges, every cell value kind, print settings, and cell comments (`comments.ts`: legacy `xl/comments{N}.xml` notes plus `[MS-XLSX]` threaded comments, both resolved through the worksheet part's own relationships, never by part name); `buildXlsxPackage` builds a complete xlsx `Package` from scratch (never editing the decoded package). `number-format.ts`/`styles.ts`/`serial.ts` run both ways: reading classifies style index → format code → kind (`percentage`/`currency`/`date`/`time`/`dateTime`); writing emits interned `numFmt` codes, fed back through the classifier in tests. The classifier is not a formatter (`displayText` is the typed-value spelling). Scope limits: `currency` with no ISO code writes as plain `number`; non-canonical temporal values degrade to text; cell comments read but do not write (`buildXlsxPackage` emits no comment part, so they do not survive this pair).
233
233
 
234
234
  ## Conventions
235
235
 
@@ -0,0 +1,139 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_typed_util = require("../util.cjs");
3
+ let document_schema_js = require("document-schema.js");
4
+ //#region src/typed/xlsx/comments.ts
5
+ const REL_COMMENTS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
6
+ const REL_THREADED_COMMENTS = "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment";
7
+ const REL_PERSON = "http://schemas.microsoft.com/office/2017/10/relationships/person";
8
+ function localName(tag) {
9
+ const colon = tag.lastIndexOf(":");
10
+ return colon === -1 ? tag : tag.slice(colon + 1);
11
+ }
12
+ function childrenWithLocalName(element, local) {
13
+ const out = [];
14
+ for (const child of element.children) if (child.type === "element" && localName(child.tag) === local) out.push(child);
15
+ return out;
16
+ }
17
+ function normalizeGuid(value) {
18
+ return value.replaceAll("{", "").replaceAll("}", "").toLowerCase();
19
+ }
20
+ function relatedPartPaths(pkg, partPath, relType) {
21
+ const paths = [];
22
+ for (const rel of require_typed_util.resolveRelationships(pkg, partPath).values()) if (rel.type === relType) paths.push(rel.target);
23
+ return paths;
24
+ }
25
+ function readLegacyCommentText(text) {
26
+ let value = "";
27
+ for (const t of require_typed_util.elementsWithTag(text.children, "t")) value += require_typed_util.textContent(t);
28
+ return value === "" ? require_typed_util.textContent(text) : value;
29
+ }
30
+ function readLegacyComments(pkg, sheetPath, into) {
31
+ for (const path of relatedPartPaths(pkg, sheetPath, REL_COMMENTS)) {
32
+ const root = require_typed_util.rootElement(pkg.parts[path]);
33
+ if (root === void 0) continue;
34
+ const authorsEl = require_typed_util.childrenWithTag(root, "authors")[0];
35
+ const authors = authorsEl === void 0 ? [] : require_typed_util.childrenWithTag(authorsEl, "author").map(require_typed_util.textContent);
36
+ const commentList = require_typed_util.childrenWithTag(root, "commentList")[0];
37
+ if (commentList === void 0) continue;
38
+ for (const comment of require_typed_util.childrenWithTag(commentList, "comment")) {
39
+ const ref = require_typed_util.attr(comment, "ref");
40
+ const position = ref === void 0 ? void 0 : (0, document_schema_js.parseCellReference)(ref);
41
+ const textEl = require_typed_util.childrenWithTag(comment, "text")[0];
42
+ if (position === void 0 || textEl === void 0) continue;
43
+ const entry = { text: readLegacyCommentText(textEl) };
44
+ const authorIdRaw = require_typed_util.attr(comment, "authorId");
45
+ const authorIndex = authorIdRaw === void 0 ? void 0 : Number.parseInt(authorIdRaw, 10);
46
+ const author = authorIndex === void 0 ? void 0 : authors[authorIndex];
47
+ if (author !== void 0) entry.author = author;
48
+ into.set(`${position.row}:${position.column}`, {
49
+ row: position.row,
50
+ column: position.column,
51
+ comment: entry
52
+ });
53
+ }
54
+ }
55
+ }
56
+ function readPersons(pkg, sheetPath) {
57
+ const persons = /* @__PURE__ */ new Map();
58
+ for (const path of relatedPartPaths(pkg, sheetPath, REL_PERSON)) {
59
+ const root = require_typed_util.rootElement(pkg.parts[path]);
60
+ if (root === void 0) continue;
61
+ for (const person of childrenWithLocalName(root, "person")) {
62
+ const id = require_typed_util.attr(person, "id");
63
+ const displayName = require_typed_util.attr(person, "displayName");
64
+ if (id !== void 0 && displayName !== void 0) persons.set(normalizeGuid(id), displayName);
65
+ }
66
+ }
67
+ return persons;
68
+ }
69
+ function readThreadedAuthor(element, persons) {
70
+ const displayName = require_typed_util.attr(element, "displayName");
71
+ if (displayName !== void 0) return displayName;
72
+ const personId = require_typed_util.attr(element, "personId");
73
+ return personId === void 0 ? void 0 : persons.get(normalizeGuid(personId));
74
+ }
75
+ function readThreadedCreatedAt(element) {
76
+ const dT = require_typed_util.attr(element, "dT");
77
+ if (dT !== void 0) return dT;
78
+ const dCreation = require_typed_util.attr(element, "dCreation");
79
+ if (dCreation === void 0) return;
80
+ const ms = Number(dCreation);
81
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
82
+ }
83
+ function readThreadedComments(pkg, sheetPath, into) {
84
+ const partPaths = relatedPartPaths(pkg, sheetPath, REL_THREADED_COMMENTS);
85
+ if (partPaths.length === 0) return;
86
+ const persons = readPersons(pkg, sheetPath);
87
+ for (const path of partPaths) {
88
+ const root = require_typed_util.rootElement(pkg.parts[path]);
89
+ if (root === void 0) continue;
90
+ const groups = /* @__PURE__ */ new Map();
91
+ for (const element of childrenWithLocalName(root, "threadedComment")) {
92
+ const ref = require_typed_util.attr(element, "ref");
93
+ const position = ref === void 0 ? void 0 : (0, document_schema_js.parseCellReference)(ref);
94
+ const textEl = childrenWithLocalName(element, "text")[0];
95
+ if (position === void 0 || textEl === void 0) continue;
96
+ const entry = {
97
+ row: position.row,
98
+ column: position.column,
99
+ text: require_typed_util.textContent(textEl)
100
+ };
101
+ const author = readThreadedAuthor(element, persons);
102
+ if (author !== void 0) entry.author = author;
103
+ const createdAt = readThreadedCreatedAt(element);
104
+ if (createdAt !== void 0) entry.createdAt = createdAt;
105
+ const parentId = require_typed_util.attr(element, "parentId") ?? require_typed_util.attr(element, "parent");
106
+ if (parentId !== void 0) entry.parentId = parentId;
107
+ const key = `${position.row}:${position.column}`;
108
+ const group = groups.get(key);
109
+ if (group === void 0) groups.set(key, [entry]);
110
+ else group.push(entry);
111
+ }
112
+ for (const [key, group] of groups) {
113
+ const rootEntry = group.find((entry) => entry.parentId === void 0) ?? group.at(0);
114
+ if (rootEntry === void 0) continue;
115
+ const comment = { text: rootEntry.text };
116
+ if (rootEntry.author !== void 0) comment.author = rootEntry.author;
117
+ if (rootEntry.createdAt !== void 0) comment.createdAt = rootEntry.createdAt;
118
+ const replies = group.filter((entry) => entry !== rootEntry);
119
+ if (replies.length > 0) comment.replies = replies.map((reply) => {
120
+ const answer = { text: reply.text };
121
+ if (reply.author !== void 0) answer.author = reply.author;
122
+ return answer;
123
+ });
124
+ into.set(key, {
125
+ row: rootEntry.row,
126
+ column: rootEntry.column,
127
+ comment
128
+ });
129
+ }
130
+ }
131
+ }
132
+ function readSheetCellComments(pkg, sheetPath) {
133
+ const comments = /* @__PURE__ */ new Map();
134
+ readLegacyComments(pkg, sheetPath, comments);
135
+ readThreadedComments(pkg, sheetPath, comments);
136
+ return comments;
137
+ }
138
+ //#endregion
139
+ exports.readSheetCellComments = readSheetCellComments;
@@ -0,0 +1,11 @@
1
+ import { r as Package } from "../../package-L24lkba-.cjs";
2
+ import { ContentSheetCellComment } from "document-schema.js";
3
+ //#region src/typed/xlsx/comments.d.ts
4
+ interface SheetCellComment {
5
+ row: number;
6
+ column: number;
7
+ comment: ContentSheetCellComment;
8
+ }
9
+ declare function readSheetCellComments(pkg: Package, sheetPath: string): Map<string, SheetCellComment>;
10
+ //#endregion
11
+ export { SheetCellComment, readSheetCellComments };
@@ -0,0 +1,11 @@
1
+ import { r as Package } from "../../package-BUojjTXf.js";
2
+ import { ContentSheetCellComment } from "document-schema.js";
3
+ //#region src/typed/xlsx/comments.d.ts
4
+ interface SheetCellComment {
5
+ row: number;
6
+ column: number;
7
+ comment: ContentSheetCellComment;
8
+ }
9
+ declare function readSheetCellComments(pkg: Package, sheetPath: string): Map<string, SheetCellComment>;
10
+ //#endregion
11
+ export { SheetCellComment, readSheetCellComments };
@@ -0,0 +1,138 @@
1
+ import { attr, childrenWithTag, elementsWithTag, resolveRelationships, rootElement, textContent } from "../util.js";
2
+ import { parseCellReference } from "document-schema.js";
3
+ //#region src/typed/xlsx/comments.ts
4
+ const REL_COMMENTS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
5
+ const REL_THREADED_COMMENTS = "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment";
6
+ const REL_PERSON = "http://schemas.microsoft.com/office/2017/10/relationships/person";
7
+ function localName(tag) {
8
+ const colon = tag.lastIndexOf(":");
9
+ return colon === -1 ? tag : tag.slice(colon + 1);
10
+ }
11
+ function childrenWithLocalName(element, local) {
12
+ const out = [];
13
+ for (const child of element.children) if (child.type === "element" && localName(child.tag) === local) out.push(child);
14
+ return out;
15
+ }
16
+ function normalizeGuid(value) {
17
+ return value.replaceAll("{", "").replaceAll("}", "").toLowerCase();
18
+ }
19
+ function relatedPartPaths(pkg, partPath, relType) {
20
+ const paths = [];
21
+ for (const rel of resolveRelationships(pkg, partPath).values()) if (rel.type === relType) paths.push(rel.target);
22
+ return paths;
23
+ }
24
+ function readLegacyCommentText(text) {
25
+ let value = "";
26
+ for (const t of elementsWithTag(text.children, "t")) value += textContent(t);
27
+ return value === "" ? textContent(text) : value;
28
+ }
29
+ function readLegacyComments(pkg, sheetPath, into) {
30
+ for (const path of relatedPartPaths(pkg, sheetPath, REL_COMMENTS)) {
31
+ const root = rootElement(pkg.parts[path]);
32
+ if (root === void 0) continue;
33
+ const authorsEl = childrenWithTag(root, "authors")[0];
34
+ const authors = authorsEl === void 0 ? [] : childrenWithTag(authorsEl, "author").map(textContent);
35
+ const commentList = childrenWithTag(root, "commentList")[0];
36
+ if (commentList === void 0) continue;
37
+ for (const comment of childrenWithTag(commentList, "comment")) {
38
+ const ref = attr(comment, "ref");
39
+ const position = ref === void 0 ? void 0 : parseCellReference(ref);
40
+ const textEl = childrenWithTag(comment, "text")[0];
41
+ if (position === void 0 || textEl === void 0) continue;
42
+ const entry = { text: readLegacyCommentText(textEl) };
43
+ const authorIdRaw = attr(comment, "authorId");
44
+ const authorIndex = authorIdRaw === void 0 ? void 0 : Number.parseInt(authorIdRaw, 10);
45
+ const author = authorIndex === void 0 ? void 0 : authors[authorIndex];
46
+ if (author !== void 0) entry.author = author;
47
+ into.set(`${position.row}:${position.column}`, {
48
+ row: position.row,
49
+ column: position.column,
50
+ comment: entry
51
+ });
52
+ }
53
+ }
54
+ }
55
+ function readPersons(pkg, sheetPath) {
56
+ const persons = /* @__PURE__ */ new Map();
57
+ for (const path of relatedPartPaths(pkg, sheetPath, REL_PERSON)) {
58
+ const root = rootElement(pkg.parts[path]);
59
+ if (root === void 0) continue;
60
+ for (const person of childrenWithLocalName(root, "person")) {
61
+ const id = attr(person, "id");
62
+ const displayName = attr(person, "displayName");
63
+ if (id !== void 0 && displayName !== void 0) persons.set(normalizeGuid(id), displayName);
64
+ }
65
+ }
66
+ return persons;
67
+ }
68
+ function readThreadedAuthor(element, persons) {
69
+ const displayName = attr(element, "displayName");
70
+ if (displayName !== void 0) return displayName;
71
+ const personId = attr(element, "personId");
72
+ return personId === void 0 ? void 0 : persons.get(normalizeGuid(personId));
73
+ }
74
+ function readThreadedCreatedAt(element) {
75
+ const dT = attr(element, "dT");
76
+ if (dT !== void 0) return dT;
77
+ const dCreation = attr(element, "dCreation");
78
+ if (dCreation === void 0) return;
79
+ const ms = Number(dCreation);
80
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
81
+ }
82
+ function readThreadedComments(pkg, sheetPath, into) {
83
+ const partPaths = relatedPartPaths(pkg, sheetPath, REL_THREADED_COMMENTS);
84
+ if (partPaths.length === 0) return;
85
+ const persons = readPersons(pkg, sheetPath);
86
+ for (const path of partPaths) {
87
+ const root = rootElement(pkg.parts[path]);
88
+ if (root === void 0) continue;
89
+ const groups = /* @__PURE__ */ new Map();
90
+ for (const element of childrenWithLocalName(root, "threadedComment")) {
91
+ const ref = attr(element, "ref");
92
+ const position = ref === void 0 ? void 0 : parseCellReference(ref);
93
+ const textEl = childrenWithLocalName(element, "text")[0];
94
+ if (position === void 0 || textEl === void 0) continue;
95
+ const entry = {
96
+ row: position.row,
97
+ column: position.column,
98
+ text: textContent(textEl)
99
+ };
100
+ const author = readThreadedAuthor(element, persons);
101
+ if (author !== void 0) entry.author = author;
102
+ const createdAt = readThreadedCreatedAt(element);
103
+ if (createdAt !== void 0) entry.createdAt = createdAt;
104
+ const parentId = attr(element, "parentId") ?? attr(element, "parent");
105
+ if (parentId !== void 0) entry.parentId = parentId;
106
+ const key = `${position.row}:${position.column}`;
107
+ const group = groups.get(key);
108
+ if (group === void 0) groups.set(key, [entry]);
109
+ else group.push(entry);
110
+ }
111
+ for (const [key, group] of groups) {
112
+ const rootEntry = group.find((entry) => entry.parentId === void 0) ?? group.at(0);
113
+ if (rootEntry === void 0) continue;
114
+ const comment = { text: rootEntry.text };
115
+ if (rootEntry.author !== void 0) comment.author = rootEntry.author;
116
+ if (rootEntry.createdAt !== void 0) comment.createdAt = rootEntry.createdAt;
117
+ const replies = group.filter((entry) => entry !== rootEntry);
118
+ if (replies.length > 0) comment.replies = replies.map((reply) => {
119
+ const answer = { text: reply.text };
120
+ if (reply.author !== void 0) answer.author = reply.author;
121
+ return answer;
122
+ });
123
+ into.set(key, {
124
+ row: rootEntry.row,
125
+ column: rootEntry.column,
126
+ comment
127
+ });
128
+ }
129
+ }
130
+ }
131
+ function readSheetCellComments(pkg, sheetPath) {
132
+ const comments = /* @__PURE__ */ new Map();
133
+ readLegacyComments(pkg, sheetPath, comments);
134
+ readThreadedComments(pkg, sheetPath, comments);
135
+ return comments;
136
+ }
137
+ //#endregion
138
+ export { readSheetCellComments };
@@ -8,6 +8,7 @@ const require_typed_xlsx_util = require("./util.cjs");
8
8
  const require_typed_xlsx_print_settings = require("./print-settings.cjs");
9
9
  const require_typed_xlsx_serial = require("./serial.cjs");
10
10
  const require_typed_xlsx_styles = require("./styles.cjs");
11
+ const require_typed_xlsx_comments = require("./comments.cjs");
11
12
  const require_typed_xlsx_units = require("./units.cjs");
12
13
  let document_schema_js = require("document-schema.js");
13
14
  //#region src/typed/xlsx/content.ts
@@ -296,6 +297,27 @@ function readCells(worksheet, sharedStrings, context) {
296
297
  applyMergedRanges(worksheet, cells);
297
298
  return cells;
298
299
  }
300
+ function applyCellComments(comments, cells) {
301
+ if (comments.size === 0) return;
302
+ const byPosition = /* @__PURE__ */ new Map();
303
+ for (const cell of cells) byPosition.set(`${cell.row}:${cell.column}`, cell);
304
+ for (const [key, { row, column, comment }] of comments) {
305
+ const existing = byPosition.get(key);
306
+ if (existing !== void 0) {
307
+ existing.comment = comment;
308
+ continue;
309
+ }
310
+ const materialised = {
311
+ row,
312
+ column,
313
+ value: { kind: "empty" },
314
+ displayText: "",
315
+ comment
316
+ };
317
+ cells.push(materialised);
318
+ byPosition.set(key, materialised);
319
+ }
320
+ }
299
321
  function readSheet(pkg, entry, sheetIndex, sharedStrings, definedNamesBySheet, context) {
300
322
  const worksheet = require_typed_util.rootElement(pkg.parts[entry.path]);
301
323
  if (worksheet === void 0) return {
@@ -306,9 +328,11 @@ function readSheet(pkg, entry, sheetIndex, sharedStrings, definedNamesBySheet, c
306
328
  images: [],
307
329
  printSettings: require_typed_xlsx_print_settings.readPrintSettings(fallbackEmptyWorksheet(), sheetIndex, definedNamesBySheet)
308
330
  };
331
+ const cells = readCells(worksheet, sharedStrings, context);
332
+ applyCellComments(require_typed_xlsx_comments.readSheetCellComments(pkg, entry.path), cells);
309
333
  return {
310
334
  name: entry.name,
311
- cells: readCells(worksheet, sharedStrings, context),
335
+ cells,
312
336
  columns: readColumns(worksheet),
313
337
  rows: readRows(worksheet),
314
338
  images: [],
@@ -7,6 +7,7 @@ import { readXmlBool } from "./util.js";
7
7
  import { readPrintSettings } from "./print-settings.js";
8
8
  import { readDate1904, serialToIsoDate, serialToIsoDateTime, serialToIsoTime } from "./serial.js";
9
9
  import { readCellStyles } from "./styles.js";
10
+ import { readSheetCellComments } from "./comments.js";
10
11
  import { columnWidthCharsToPt } from "./units.js";
11
12
  import { CONTENT_FORMAT_VERSION, parseCellReference, parseRangeReference } from "document-schema.js";
12
13
  //#region src/typed/xlsx/content.ts
@@ -295,6 +296,27 @@ function readCells(worksheet, sharedStrings, context) {
295
296
  applyMergedRanges(worksheet, cells);
296
297
  return cells;
297
298
  }
299
+ function applyCellComments(comments, cells) {
300
+ if (comments.size === 0) return;
301
+ const byPosition = /* @__PURE__ */ new Map();
302
+ for (const cell of cells) byPosition.set(`${cell.row}:${cell.column}`, cell);
303
+ for (const [key, { row, column, comment }] of comments) {
304
+ const existing = byPosition.get(key);
305
+ if (existing !== void 0) {
306
+ existing.comment = comment;
307
+ continue;
308
+ }
309
+ const materialised = {
310
+ row,
311
+ column,
312
+ value: { kind: "empty" },
313
+ displayText: "",
314
+ comment
315
+ };
316
+ cells.push(materialised);
317
+ byPosition.set(key, materialised);
318
+ }
319
+ }
298
320
  function readSheet(pkg, entry, sheetIndex, sharedStrings, definedNamesBySheet, context) {
299
321
  const worksheet = rootElement(pkg.parts[entry.path]);
300
322
  if (worksheet === void 0) return {
@@ -305,9 +327,11 @@ function readSheet(pkg, entry, sheetIndex, sharedStrings, definedNamesBySheet, c
305
327
  images: [],
306
328
  printSettings: readPrintSettings(fallbackEmptyWorksheet(), sheetIndex, definedNamesBySheet)
307
329
  };
330
+ const cells = readCells(worksheet, sharedStrings, context);
331
+ applyCellComments(readSheetCellComments(pkg, entry.path), cells);
308
332
  return {
309
333
  name: entry.name,
310
- cells: readCells(worksheet, sharedStrings, context),
334
+ cells,
311
335
  columns: readColumns(worksheet),
312
336
  rows: readRows(worksheet),
313
337
  images: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ooxml.js",
3
- "version": "2.12.2",
3
+ "version": "2.13.0",
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": {