@shbernal/ts-xlsx 1.0.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.
Files changed (223) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +230 -0
  3. package/dist/core/address.d.ts +49 -0
  4. package/dist/core/address.js +125 -0
  5. package/dist/core/autofilter.d.ts +55 -0
  6. package/dist/core/autofilter.js +48 -0
  7. package/dist/core/cell.d.ts +132 -0
  8. package/dist/core/cell.js +249 -0
  9. package/dist/core/color-resolution.d.ts +58 -0
  10. package/dist/core/color-resolution.js +170 -0
  11. package/dist/core/column.d.ts +87 -0
  12. package/dist/core/column.js +167 -0
  13. package/dist/core/comment-thread.d.ts +116 -0
  14. package/dist/core/comment-thread.js +62 -0
  15. package/dist/core/conditional-formatting-overlay.d.ts +15 -0
  16. package/dist/core/conditional-formatting-overlay.js +24 -0
  17. package/dist/core/conditional-formatting.d.ts +70 -0
  18. package/dist/core/conditional-formatting.js +35 -0
  19. package/dist/core/containers.d.ts +2 -0
  20. package/dist/core/containers.js +25 -0
  21. package/dist/core/data-validation-overlay.d.ts +28 -0
  22. package/dist/core/data-validation-overlay.js +56 -0
  23. package/dist/core/data-validation.d.ts +38 -0
  24. package/dist/core/data-validation.js +13 -0
  25. package/dist/core/date.d.ts +25 -0
  26. package/dist/core/date.js +58 -0
  27. package/dist/core/formula.d.ts +49 -0
  28. package/dist/core/formula.js +319 -0
  29. package/dist/core/grid-edits.d.ts +22 -0
  30. package/dist/core/grid-edits.js +206 -0
  31. package/dist/core/image.d.ts +76 -0
  32. package/dist/core/image.js +65 -0
  33. package/dist/core/internal.d.ts +11 -0
  34. package/dist/core/internal.js +29 -0
  35. package/dist/core/merge.d.ts +13 -0
  36. package/dist/core/merge.js +26 -0
  37. package/dist/core/modern-functions.d.ts +1 -0
  38. package/dist/core/modern-functions.js +165 -0
  39. package/dist/core/page-setup.d.ts +94 -0
  40. package/dist/core/page-setup.js +6 -0
  41. package/dist/core/pivot-table.d.ts +113 -0
  42. package/dist/core/pivot-table.js +249 -0
  43. package/dist/core/preserved.d.ts +58 -0
  44. package/dist/core/preserved.js +5 -0
  45. package/dist/core/protection.d.ts +67 -0
  46. package/dist/core/protection.js +68 -0
  47. package/dist/core/range.d.ts +112 -0
  48. package/dist/core/range.js +283 -0
  49. package/dist/core/row.d.ts +73 -0
  50. package/dist/core/row.js +135 -0
  51. package/dist/core/style.d.ts +246 -0
  52. package/dist/core/style.js +125 -0
  53. package/dist/core/table-style.d.ts +67 -0
  54. package/dist/core/table-style.js +90 -0
  55. package/dist/core/table.d.ts +194 -0
  56. package/dist/core/table.js +291 -0
  57. package/dist/core/theme.d.ts +90 -0
  58. package/dist/core/theme.js +272 -0
  59. package/dist/core/value.d.ts +115 -0
  60. package/dist/core/value.js +144 -0
  61. package/dist/core/workbook-protection.d.ts +27 -0
  62. package/dist/core/workbook-protection.js +25 -0
  63. package/dist/core/workbook.d.ts +528 -0
  64. package/dist/core/workbook.js +732 -0
  65. package/dist/core/worksheet-model.d.ts +34 -0
  66. package/dist/core/worksheet-model.js +100 -0
  67. package/dist/core/worksheet.d.ts +647 -0
  68. package/dist/core/worksheet.js +1025 -0
  69. package/dist/customui/errors.d.ts +15 -0
  70. package/dist/customui/errors.js +15 -0
  71. package/dist/customui/index.d.ts +2 -0
  72. package/dist/customui/index.js +7 -0
  73. package/dist/customui/ribbon.d.ts +86 -0
  74. package/dist/customui/ribbon.js +180 -0
  75. package/dist/entries/core.d.ts +23 -0
  76. package/dist/entries/core.js +20 -0
  77. package/dist/entries/csv.d.ts +2 -0
  78. package/dist/entries/csv.js +6 -0
  79. package/dist/entries/customui.d.ts +1 -0
  80. package/dist/entries/customui.js +3 -0
  81. package/dist/entries/errors.d.ts +7 -0
  82. package/dist/entries/errors.js +20 -0
  83. package/dist/entries/vba.d.ts +2 -0
  84. package/dist/entries/vba.js +7 -0
  85. package/dist/entries/xlsb.d.ts +1 -0
  86. package/dist/entries/xlsb.js +7 -0
  87. package/dist/entries/xlsx.d.ts +5 -0
  88. package/dist/entries/xlsx.js +18 -0
  89. package/dist/errors.d.ts +65 -0
  90. package/dist/errors.js +66 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.js +19 -0
  93. package/dist/io/csv/read.d.ts +15 -0
  94. package/dist/io/csv/read.js +113 -0
  95. package/dist/io/csv/write.d.ts +28 -0
  96. package/dist/io/csv/write.js +124 -0
  97. package/dist/io/opc/errors.d.ts +46 -0
  98. package/dist/io/opc/errors.js +49 -0
  99. package/dist/io/opc/inflate.d.ts +15 -0
  100. package/dist/io/opc/inflate.js +91 -0
  101. package/dist/io/opc/namespaces.d.ts +18 -0
  102. package/dist/io/opc/namespaces.js +18 -0
  103. package/dist/io/opc/part-paths.d.ts +4 -0
  104. package/dist/io/opc/part-paths.js +35 -0
  105. package/dist/io/opc/read-opc.d.ts +24 -0
  106. package/dist/io/opc/read-opc.js +153 -0
  107. package/dist/io/opc/read-options.d.ts +10 -0
  108. package/dist/io/opc/read-options.js +7 -0
  109. package/dist/io/opc/rels.d.ts +15 -0
  110. package/dist/io/opc/rels.js +28 -0
  111. package/dist/io/opc/sniff-format.d.ts +27 -0
  112. package/dist/io/opc/sniff-format.js +92 -0
  113. package/dist/io/style/xf-style.d.ts +47 -0
  114. package/dist/io/style/xf-style.js +95 -0
  115. package/dist/io/xlsb/errors.d.ts +15 -0
  116. package/dist/io/xlsb/errors.js +15 -0
  117. package/dist/io/xlsb/formula.d.ts +48 -0
  118. package/dist/io/xlsb/formula.js +398 -0
  119. package/dist/io/xlsb/primitives.d.ts +86 -0
  120. package/dist/io/xlsb/primitives.js +230 -0
  121. package/dist/io/xlsb/ptg-functions.d.ts +16 -0
  122. package/dist/io/xlsb/ptg-functions.js +659 -0
  123. package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
  124. package/dist/io/xlsb/read-shared-strings.js +22 -0
  125. package/dist/io/xlsb/read-styles.d.ts +4 -0
  126. package/dist/io/xlsb/read-styles.js +386 -0
  127. package/dist/io/xlsb/read-worksheet.d.ts +9 -0
  128. package/dist/io/xlsb/read-worksheet.js +301 -0
  129. package/dist/io/xlsb/read.d.ts +20 -0
  130. package/dist/io/xlsb/read.js +199 -0
  131. package/dist/io/xlsb/record-stream.d.ts +14 -0
  132. package/dist/io/xlsb/record-stream.js +56 -0
  133. package/dist/io/xlsb/record-types.d.ts +55 -0
  134. package/dist/io/xlsb/record-types.js +73 -0
  135. package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
  136. package/dist/io/xlsx/cell-accumulator.js +172 -0
  137. package/dist/io/xlsx/cell-value.d.ts +32 -0
  138. package/dist/io/xlsx/cell-value.js +85 -0
  139. package/dist/io/xlsx/comments.d.ts +74 -0
  140. package/dist/io/xlsx/comments.js +243 -0
  141. package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
  142. package/dist/io/xlsx/conditional-formatting.js +464 -0
  143. package/dist/io/xlsx/data-validation.d.ts +20 -0
  144. package/dist/io/xlsx/data-validation.js +258 -0
  145. package/dist/io/xlsx/edit-vba.d.ts +23 -0
  146. package/dist/io/xlsx/edit-vba.js +139 -0
  147. package/dist/io/xlsx/errors.d.ts +17 -0
  148. package/dist/io/xlsx/errors.js +17 -0
  149. package/dist/io/xlsx/hyperlinks.d.ts +42 -0
  150. package/dist/io/xlsx/hyperlinks.js +139 -0
  151. package/dist/io/xlsx/images.d.ts +34 -0
  152. package/dist/io/xlsx/images.js +218 -0
  153. package/dist/io/xlsx/namespaces.d.ts +55 -0
  154. package/dist/io/xlsx/namespaces.js +55 -0
  155. package/dist/io/xlsx/package-plan.d.ts +103 -0
  156. package/dist/io/xlsx/package-plan.js +168 -0
  157. package/dist/io/xlsx/pivot-read.d.ts +5 -0
  158. package/dist/io/xlsx/pivot-read.js +134 -0
  159. package/dist/io/xlsx/pivot.d.ts +12 -0
  160. package/dist/io/xlsx/pivot.js +141 -0
  161. package/dist/io/xlsx/read-rows.d.ts +85 -0
  162. package/dist/io/xlsx/read-rows.js +286 -0
  163. package/dist/io/xlsx/read-styles.d.ts +8 -0
  164. package/dist/io/xlsx/read-styles.js +551 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +4 -0
  166. package/dist/io/xlsx/read-worksheet.js +528 -0
  167. package/dist/io/xlsx/read.d.ts +32 -0
  168. package/dist/io/xlsx/read.js +680 -0
  169. package/dist/io/xlsx/relationships.d.ts +33 -0
  170. package/dist/io/xlsx/relationships.js +47 -0
  171. package/dist/io/xlsx/rich-runs.d.ts +13 -0
  172. package/dist/io/xlsx/rich-runs.js +59 -0
  173. package/dist/io/xlsx/rich-text.d.ts +7 -0
  174. package/dist/io/xlsx/rich-text.js +23 -0
  175. package/dist/io/xlsx/shared-formulas.d.ts +6 -0
  176. package/dist/io/xlsx/shared-formulas.js +52 -0
  177. package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
  178. package/dist/io/xlsx/shared-strings-read.js +70 -0
  179. package/dist/io/xlsx/shared-strings.d.ts +17 -0
  180. package/dist/io/xlsx/shared-strings.js +50 -0
  181. package/dist/io/xlsx/sheet-properties.d.ts +13 -0
  182. package/dist/io/xlsx/sheet-properties.js +211 -0
  183. package/dist/io/xlsx/styles.d.ts +138 -0
  184. package/dist/io/xlsx/styles.js +821 -0
  185. package/dist/io/xlsx/tables.d.ts +9 -0
  186. package/dist/io/xlsx/tables.js +208 -0
  187. package/dist/io/xlsx/threaded-comments.d.ts +102 -0
  188. package/dist/io/xlsx/threaded-comments.js +308 -0
  189. package/dist/io/xlsx/workbook-xml.d.ts +12 -0
  190. package/dist/io/xlsx/workbook-xml.js +353 -0
  191. package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
  192. package/dist/io/xlsx/worksheet-xml.js +643 -0
  193. package/dist/io/xlsx/write-stream.d.ts +171 -0
  194. package/dist/io/xlsx/write-stream.js +399 -0
  195. package/dist/io/xlsx/write.d.ts +81 -0
  196. package/dist/io/xlsx/write.js +396 -0
  197. package/dist/io/xlsx/x14-ext.d.ts +9 -0
  198. package/dist/io/xlsx/x14-ext.js +12 -0
  199. package/dist/vba/cfb-writer.d.ts +19 -0
  200. package/dist/vba/cfb-writer.js +277 -0
  201. package/dist/vba/cfb.d.ts +16 -0
  202. package/dist/vba/cfb.js +324 -0
  203. package/dist/vba/codepage.d.ts +18 -0
  204. package/dist/vba/codepage.js +87 -0
  205. package/dist/vba/errors.d.ts +24 -0
  206. package/dist/vba/errors.js +24 -0
  207. package/dist/vba/index.d.ts +5 -0
  208. package/dist/vba/index.js +15 -0
  209. package/dist/vba/ms-ovba.d.ts +12 -0
  210. package/dist/vba/ms-ovba.js +176 -0
  211. package/dist/vba/project-editor.d.ts +62 -0
  212. package/dist/vba/project-editor.js +377 -0
  213. package/dist/vba/project.d.ts +45 -0
  214. package/dist/vba/project.js +160 -0
  215. package/dist/vba/vba-encoding.d.ts +14 -0
  216. package/dist/vba/vba-encoding.js +39 -0
  217. package/dist/xml/errors.d.ts +15 -0
  218. package/dist/xml/errors.js +15 -0
  219. package/dist/xml/xml-read.d.ts +107 -0
  220. package/dist/xml/xml-read.js +283 -0
  221. package/dist/xml/xml.d.ts +41 -0
  222. package/dist/xml/xml.js +86 -0
  223. package/package.json +117 -0
@@ -0,0 +1,73 @@
1
+ // The BIFF12 record types this reader consumes, by their [MS-XLSB] "Record Enumeration" (section 2.3)
2
+ // numbers. Named constants rather than magic numbers at the switch sites: a record number carries no
3
+ // hint of what it is, so `BRT.CellRk` is the difference between a readable parser and a table of
4
+ // unexplained integers.
5
+ //
6
+ // Deliberately partial. [MS-XLSB] enumerates ~760 record types; listing the ones we do not decode
7
+ // would claim coverage we do not have. A record whose type is absent here is skipped by the parsers,
8
+ // which is the correct behaviour for a forward-compatible reader — the format grows, and an unknown
9
+ // record is always safely framed (its size is in the stream) even when its meaning is not known.
10
+ /** BIFF12 record numbers, grouped by the part whose stream they appear in. */
11
+ export const BRT = {
12
+ // Cell table (worksheet part). The single-byte record space — these are the hot path.
13
+ RowHdr: 0,
14
+ CellBlank: 1,
15
+ CellRk: 2,
16
+ CellError: 3,
17
+ CellBool: 4,
18
+ CellReal: 5,
19
+ CellSt: 6,
20
+ CellIsst: 7,
21
+ FmlaString: 8,
22
+ FmlaNum: 9,
23
+ FmlaBool: 10,
24
+ FmlaError: 11,
25
+ CellRString: 62,
26
+ ColInfo: 60,
27
+ MergeCell: 176,
28
+ // Shared-string table.
29
+ SSTItem: 19,
30
+ BeginSst: 159,
31
+ EndSst: 160,
32
+ // Style sheet: each collection is a Begin/End pair around its entries, and `XF` appears inside two
33
+ // of them (the named-style layer and the direct-format layer), so the style parser tracks which
34
+ // collection it is in rather than keying on the record number alone.
35
+ Fmt: 44,
36
+ Font: 43,
37
+ Fill: 45,
38
+ Border: 46,
39
+ XF: 47,
40
+ Style: 48,
41
+ BeginFmts: 615,
42
+ EndFmts: 616,
43
+ BeginFonts: 611,
44
+ EndFonts: 612,
45
+ BeginFills: 603,
46
+ EndFills: 604,
47
+ BeginBorders: 613,
48
+ EndBorders: 614,
49
+ BeginCellStyleXFs: 626,
50
+ EndCellStyleXFs: 627,
51
+ BeginCellXFs: 617,
52
+ EndCellXFs: 618,
53
+ BeginStyles: 619,
54
+ EndStyles: 620,
55
+ // Workbook part.
56
+ BundleSh: 156,
57
+ BeginBundleShs: 143,
58
+ EndBundleShs: 144,
59
+ Name: 39,
60
+ // The externals block: which workbooks a formula can reach, and which sheets of them each `ixti` a
61
+ // 3-D reference carries names. `SupSelf` declares a supporting book that is *this* workbook.
62
+ BeginExternals: 353,
63
+ EndExternals: 354,
64
+ SupSelf: 357,
65
+ ExternSheet: 362,
66
+ // Worksheet structure.
67
+ WsProp: 147,
68
+ WsDim: 148,
69
+ WsFmtInfo: 485,
70
+ BeginSheetData: 145,
71
+ EndSheetData: 146,
72
+ ArrFmla: 426,
73
+ };
@@ -0,0 +1,25 @@
1
+ import type { CellValue } from '../../core/value.ts';
2
+ import type { Worksheet } from '../../core/worksheet.ts';
3
+ import { type XmlAttributes } from '../../xml/xml-read.ts';
4
+ import { type XfStyle } from '../style/xf-style.ts';
5
+ import { type SharedString } from './cell-value.ts';
6
+ import { RunAccumulator } from './rich-runs.ts';
7
+ export declare class CellAccumulator {
8
+ #private;
9
+ /** This cell's `<c r>` address (`"B3"`), or '' when it carried none. */
10
+ get ref(): string;
11
+ /** This cell's own `<c s>` style index, or -1 when it carries none. */
12
+ get styleIndex(): number;
13
+ /** This cell's 1-based column, or -1 when its address was absent or unparseable. */
14
+ get col(): number;
15
+ /** The rich-text run accumulator, driven by the surrounding parser's `<r>`/`<rPr>` handling. */
16
+ get runs(): RunAccumulator;
17
+ beginCell(attrs: XmlAttributes): void;
18
+ beginFormula(attrs: XmlAttributes, selfClosing: boolean): void;
19
+ setFormula(text: string): void;
20
+ setValue(text: string): void;
21
+ beginInlineString(): void;
22
+ appendText(text: string, inInlineString: boolean): void;
23
+ finalize(sheet: Worksheet, sharedStrings: readonly SharedString[], style: XfStyle | undefined): void;
24
+ decode(sharedStrings: readonly SharedString[], style: XfStyle | undefined): CellValue;
25
+ }
@@ -0,0 +1,172 @@
1
+ // Cell-gathering state machine for the worksheet-body reader. It owns the still-textual pieces of the
2
+ // one `<c>` currently being read — its address/type/style, formula, cached value, inline text, and
3
+ // rich-text runs — plus the shared-formula master map that spans the whole sheet. Each `<c>` resets
4
+ // the per-cell state on {@link beginCell} and commits it on {@link finalize}, so this class is the
5
+ // single owner of "what has this cell gathered so far" — to a cell what {@link RunAccumulator} is to a
6
+ // rich string. Value *decoding* stays in `cell-value.ts`; this class only gathers the raw pieces.
7
+ import { decodeAddress, encodeAddress } from "../../core/address.js";
8
+ import { translateFormula, unmangleFunctions } from "../../core/formula.js";
9
+ import { boolPresent } from "../../xml/xml-read.js";
10
+ import { applyXfToCell } from "../style/xf-style.js";
11
+ import { decodeCellContent, decodeFormulaResult, } from "./cell-value.js";
12
+ import { RunAccumulator } from "./rich-runs.js";
13
+ export class CellAccumulator {
14
+ #ref = '';
15
+ #type = '';
16
+ #style = -1;
17
+ #col = -1;
18
+ #row = -1;
19
+ #formula = '';
20
+ // Shared-formula bookkeeping. A master `<f t="shared" ref si>TEXT</f>` seeds the group; every clone
21
+ // `<f t="shared" si/>` in the sheet references it by `si` and carries no text of its own.
22
+ #formulaShared = false;
23
+ #formulaSi = -1;
24
+ #sharedClone = false;
25
+ #dataTable = null;
26
+ #valueText = '';
27
+ #inlineText = '';
28
+ #hasFormula = false;
29
+ #hasValue = false;
30
+ #runs = new RunAccumulator();
31
+ // Masters always precede their clones (Excel keeps the master top-left), so a clone resolves against
32
+ // a map filled as the sheet streams: the master's formula translated to the clone's position.
33
+ #masters = new Map();
34
+ /** This cell's `<c r>` address (`"B3"`), or '' when it carried none. */
35
+ get ref() {
36
+ return this.#ref;
37
+ }
38
+ /** This cell's own `<c s>` style index, or -1 when it carries none. */
39
+ get styleIndex() {
40
+ return this.#style;
41
+ }
42
+ /** This cell's 1-based column, or -1 when its address was absent or unparseable. */
43
+ get col() {
44
+ return this.#col;
45
+ }
46
+ /** The rich-text run accumulator, driven by the surrounding parser's `<r>`/`<rPr>` handling. */
47
+ get runs() {
48
+ return this.#runs;
49
+ }
50
+ // Begin a new `<c>`: record its address/type/style and clear every per-cell gathered field so the
51
+ // last cell's formula, value, runs, or shared/data-table declaration cannot bleed into this one.
52
+ beginCell(attrs) {
53
+ this.#ref = attrs.r ?? '';
54
+ this.#type = attrs.t ?? '';
55
+ this.#style = attrs.s !== undefined ? Number(attrs.s) : -1;
56
+ this.#col = this.#ref === '' ? -1 : (decodeAddress(this.#ref).col ?? -1);
57
+ this.#row = this.#ref === '' ? -1 : (decodeAddress(this.#ref).row ?? -1);
58
+ this.#formula = '';
59
+ this.#valueText = '';
60
+ this.#inlineText = '';
61
+ this.#runs.reset();
62
+ this.#hasFormula = false;
63
+ this.#hasValue = false;
64
+ this.#formulaShared = false;
65
+ this.#formulaSi = -1;
66
+ this.#sharedClone = false;
67
+ this.#dataTable = null;
68
+ }
69
+ // Begin an `<f>`: record its shared-formula grouping and any data-table declaration. A self-closing
70
+ // `<f t="shared" si/>` is a clone — it fires no close and carries no text — so mark it here to
71
+ // resolve against its master when the cell finalises.
72
+ beginFormula(attrs, selfClosing) {
73
+ this.#formulaShared = attrs.t === 'shared';
74
+ this.#formulaSi = attrs.si !== undefined ? Number(attrs.si) : -1;
75
+ if (selfClosing && this.#formulaShared)
76
+ this.#sharedClone = true;
77
+ if (attrs.t === 'dataTable' && attrs.ref !== undefined) {
78
+ this.#dataTable = {
79
+ ref: attrs.ref,
80
+ dt2D: attrs.dt2D,
81
+ dtr: attrs.dtr,
82
+ r1: attrs.r1,
83
+ r2: attrs.r2,
84
+ };
85
+ }
86
+ }
87
+ setFormula(text) {
88
+ this.#formula = text;
89
+ this.#hasFormula = true;
90
+ }
91
+ setValue(text) {
92
+ this.#valueText = text;
93
+ this.#hasValue = true;
94
+ }
95
+ // Begin an `<is>`: clear the inline string and its runs so a rich value built from a previous cell's
96
+ // runs keeps its own array.
97
+ beginInlineString() {
98
+ this.#inlineText = '';
99
+ this.#runs.reset();
100
+ }
101
+ // Route a `<t>`'s text: to the open run when one is active, otherwise to the inline string when the
102
+ // parser is inside an `<is>`. A run takes precedence — a run is also inside the inline string.
103
+ appendText(text, inInlineString) {
104
+ if (!this.#runs.appendText(text) && inInlineString)
105
+ this.#inlineText += text;
106
+ }
107
+ // Commit the gathered cell to the sheet with its already-resolved style (the caller applies the
108
+ // cell → row → column inheritance order). A data-table cell surfaces its declaration; a shared-formula
109
+ // master seeds the group before finalising, and a clone resolves to the master translated to its own
110
+ // position. Everything else decodes as an ordinary `<c>` payload. An address-less cell is a no-op.
111
+ finalize(sheet, sharedStrings, style) {
112
+ if (this.#ref === '')
113
+ return;
114
+ if (this.#dataTable !== null) {
115
+ const value = {
116
+ shareType: 'dataTable',
117
+ ref: this.#dataTable.ref,
118
+ ...(boolPresent(this.#dataTable.dt2D ?? '0') ? { dataTable2D: true } : {}),
119
+ ...(boolPresent(this.#dataTable.dtr ?? '0') ? { dataTableRow: true } : {}),
120
+ ...(this.#dataTable.r1 !== undefined ? { r1: this.#dataTable.r1 } : {}),
121
+ ...(this.#dataTable.r2 !== undefined ? { r2: this.#dataTable.r2 } : {}),
122
+ ...(this.#hasValue
123
+ ? { result: decodeFormulaResult(this.#type, this.#valueText, style?.numFmt) }
124
+ : {}),
125
+ };
126
+ const cell = sheet.getCell(this.#ref);
127
+ applyXfToCell(cell, style);
128
+ cell.value = value;
129
+ return;
130
+ }
131
+ if (this.#hasFormula && this.#formulaShared && this.#formulaSi >= 0) {
132
+ this.#masters.set(this.#formulaSi, { formula: this.#formula, col: this.#col, row: this.#row });
133
+ }
134
+ else if (this.#sharedClone && this.#formulaSi >= 0) {
135
+ const master = this.#masters.get(this.#formulaSi);
136
+ if (master !== undefined) {
137
+ const translated = translateFormula(master.formula, this.#col - master.col, this.#row - master.row);
138
+ const value = {
139
+ sharedFormula: encodeAddress(master.col, master.row),
140
+ formula: unmangleFunctions(translated),
141
+ // A clone's cached result honours the cell's date format the same way a plain formula's does.
142
+ ...(this.#hasValue
143
+ ? { result: decodeFormulaResult(this.#type, this.#valueText, style?.numFmt) }
144
+ : {}),
145
+ };
146
+ const cell = sheet.getCell(this.#ref);
147
+ applyXfToCell(cell, style);
148
+ cell.value = value;
149
+ return;
150
+ }
151
+ }
152
+ const cell = sheet.getCell(this.#ref);
153
+ applyXfToCell(cell, style);
154
+ cell.value = this.decode(sharedStrings, style);
155
+ }
156
+ // Decode the gathered pieces into a plain cell value, resolving the shared pool and date formats but
157
+ // NOT the shared-formula / data-table declarations {@link finalize} handles. This is what a data
158
+ // read (the streaming reader) wants: the cell's own value, with a shared-formula clone surfacing its
159
+ // cached result rather than a translated formula it will not evaluate.
160
+ decode(sharedStrings, style) {
161
+ const raw = {
162
+ type: this.#type,
163
+ hasFormula: this.#hasFormula,
164
+ formula: this.#formula,
165
+ hasValue: this.#hasValue,
166
+ valueText: this.#valueText,
167
+ inlineText: this.#inlineText,
168
+ richTextRuns: this.#runs.runs,
169
+ };
170
+ return decodeCellContent(raw, sharedStrings, style?.numFmt);
171
+ }
172
+ }
@@ -0,0 +1,32 @@
1
+ import { type CellValue, type FormulaResult, type RichTextRun, type RichTextValue } from '../../core/value.ts';
2
+ /**
3
+ * One entry of the shared-strings pool. A `<si>` built from a bare `<t>` is a plain string; a `<si>`
4
+ * built from `<r>` runs is rich text — so a `t="s"` cell can resolve to either kind, and rich text
5
+ * that Excel pooled reads back with its per-run formatting intact rather than flattened to text.
6
+ */
7
+ export type SharedString = string | RichTextValue;
8
+ /** The raw, still-textual pieces of a `<c>` element the SAX pass has gathered. */
9
+ export interface RawCell {
10
+ /** The `t` attribute (`s`, `str`, `inlineStr`, `b`, `e`, `d`, or '' for a number). */
11
+ readonly type: string;
12
+ readonly hasFormula: boolean;
13
+ readonly formula: string;
14
+ readonly hasValue: boolean;
15
+ readonly valueText: string;
16
+ readonly inlineText: string;
17
+ /** The formatted runs of a rich inline string, when the `<is>` held `<r>` elements rather than a
18
+ * bare `<t>`. Absent (or empty) for a plain inline string, which decodes to `inlineText`. */
19
+ readonly richTextRuns?: readonly RichTextRun[];
20
+ }
21
+ /**
22
+ * Decode a gathered cell into its model value. A formula cell becomes a `{formula, result?}`
23
+ * object (the on-disk `_xlfn.`/`_xlpm.` mangling stripped back to the readable name); a plain
24
+ * numeric cell under a date number format becomes a {@link Date}; everything else decodes by its
25
+ * `t` type. `numFmt` is the cell's resolved number-format code, used only for date detection.
26
+ */
27
+ export declare function decodeCellContent(raw: RawCell, sharedStrings: readonly SharedString[], numFmt: string | undefined): CellValue;
28
+ /** Decode a formula's cached `<v>` result by its `t` type, coercing a numeric result under a date
29
+ * `numFmt` to a {@link Date} exactly as a bare numeric cell is — so a date-valued formula result
30
+ * (e.g. `TODAY()`) reads back as a Date, not a serial. Shared by the buffered reader's shared-formula
31
+ * clone resolution, which caches a result the same way a plain formula cell does. */
32
+ export declare function decodeFormulaResult(type: string, valueText: string, numFmt?: string): FormulaResult;
@@ -0,0 +1,85 @@
1
+ // Decoding a worksheet cell's on-disk `<c>` payload into a model {@link CellValue}.
2
+ //
3
+ // This is the single value-decoding surface both readers share: the buffered reader
4
+ // (`./read.ts`) and the streaming row reader (`./read-rows.ts`). Keeping it in one place is
5
+ // what guarantees a cell read one row at a time decodes identically to the same cell read as
6
+ // part of a whole workbook — a divergence here would be a silent data bug in exactly one path.
7
+ import { isDateFormat, serialToDate } from "../../core/date.js";
8
+ import { unmangleFunctions } from "../../core/formula.js";
9
+ import { isErrorCode, } from "../../core/value.js";
10
+ import { boolStrict } from "../../xml/xml-read.js";
11
+ /**
12
+ * Decode a gathered cell into its model value. A formula cell becomes a `{formula, result?}`
13
+ * object (the on-disk `_xlfn.`/`_xlpm.` mangling stripped back to the readable name); a plain
14
+ * numeric cell under a date number format becomes a {@link Date}; everything else decodes by its
15
+ * `t` type. `numFmt` is the cell's resolved number-format code, used only for date detection.
16
+ */
17
+ export function decodeCellContent(raw, sharedStrings, numFmt) {
18
+ if (raw.hasFormula) {
19
+ const stored = unmangleFunctions(raw.formula);
20
+ const result = raw.hasValue ? decodeFormulaResult(raw.type, raw.valueText, numFmt) : undefined;
21
+ return result === undefined ? { formula: stored } : { formula: stored, result };
22
+ }
23
+ // An inline string built from `<r>` runs is rich text — surface its runs rather than flattening
24
+ // them to the concatenated `inlineText` a plain string would decode to.
25
+ if (raw.type === 'inlineStr' && raw.richTextRuns !== undefined && raw.richTextRuns.length > 0) {
26
+ return { richText: raw.richTextRuns };
27
+ }
28
+ const value = decodeValue(raw.type, raw.valueText, raw.inlineText, raw.hasValue, sharedStrings);
29
+ // A number stored under a date format is a date serial — surface it as a Date so a written
30
+ // date round-trips as a date, not a bare number. Only plain numeric cells qualify; a string,
31
+ // boolean, or formula result under a date format keeps its own kind.
32
+ return typeof value === 'number' && numFmt !== undefined && isDateFormat(numFmt)
33
+ ? serialToDate(value)
34
+ : value;
35
+ }
36
+ function decodeValue(type, valueText, inlineText, hasValue, sharedStrings) {
37
+ switch (type) {
38
+ case 'inlineStr':
39
+ return inlineText;
40
+ case 'str':
41
+ return valueText;
42
+ case 'd':
43
+ // A Strict-mode (ISO/IEC 29500 Strict) date cell stores an ISO 8601 value directly, not a
44
+ // serial. Parse it literally — an ISO date is UTC — so it reads as the date it states rather
45
+ // than a 1900-epoch serial the transitional decoder would fabricate from the text.
46
+ return valueText === '' ? null : new Date(valueText);
47
+ case 's': {
48
+ // A `t="s"` cell indexes the shared pool; the entry is a plain string or, when Excel pooled a
49
+ // rich value, a {@link RichTextValue} whose runs surface here rather than being flattened.
50
+ const index = Number(valueText);
51
+ return Number.isInteger(index) ? (sharedStrings[index] ?? '') : '';
52
+ }
53
+ case 'b':
54
+ return boolStrict(valueText);
55
+ case 'e':
56
+ return isErrorCode(valueText) ? { error: valueText } : valueText;
57
+ default:
58
+ return hasValue ? Number(valueText) : null;
59
+ }
60
+ }
61
+ /** Decode a formula's cached `<v>` result by its `t` type, coercing a numeric result under a date
62
+ * `numFmt` to a {@link Date} exactly as a bare numeric cell is — so a date-valued formula result
63
+ * (e.g. `TODAY()`) reads back as a Date, not a serial. Shared by the buffered reader's shared-formula
64
+ * clone resolution, which caches a result the same way a plain formula cell does. */
65
+ export function decodeFormulaResult(type, valueText, numFmt) {
66
+ const result = decodeResult(type, valueText);
67
+ return typeof result === 'number' && numFmt !== undefined && isDateFormat(numFmt)
68
+ ? serialToDate(result)
69
+ : result;
70
+ }
71
+ // The formula-result subset of `decodeValue`: a cached result is only ever a string, boolean,
72
+ // error, or number — never a shared-string index, inline string, or Strict-mode date — so this
73
+ // handles just those cases rather than the full cell-value grammar.
74
+ function decodeResult(type, valueText) {
75
+ switch (type) {
76
+ case 'str':
77
+ return valueText;
78
+ case 'b':
79
+ return boolStrict(valueText);
80
+ case 'e':
81
+ return isErrorCode(valueText) ? { error: valueText } : valueText;
82
+ default:
83
+ return Number(valueText);
84
+ }
85
+ }
@@ -0,0 +1,74 @@
1
+ import type { CommentThread } from '../../core/comment-thread.ts';
2
+ import type { Worksheet } from '../../core/worksheet.ts';
3
+ /** A comment bound for `comments{n}.xml`, paired with the coordinates the VML anchor needs. */
4
+ export interface CommentCell {
5
+ readonly ref: string;
6
+ /** 1-based row of the commented cell. */
7
+ readonly row: number;
8
+ /** 1-based column of the commented cell. */
9
+ readonly col: number;
10
+ readonly text: string;
11
+ /**
12
+ * The {@link CommentThread} head id this comment is the legacy fallback for, absent for a user's own
13
+ * note. Present means the comment is emitted with a synthetic `tc={id}` author and an `xr:uid`, the
14
+ * pair Excel resolves the cell's thread through.
15
+ */
16
+ readonly threadId?: string;
17
+ }
18
+ /**
19
+ * Gather every comment a sheet must write: its cells' notes, plus one legacy fallback per conversation
20
+ * in `threads`. A comment anchors to its cell regardless of the cell's value, so a note (or a thread) on
21
+ * an otherwise-empty cell is collected too.
22
+ *
23
+ * `threads` is the conversations the *package* will carry, not simply the ones the sheet holds — the
24
+ * caller decides, because a fallback beside a thread whose `threadedComment` part is missing is worse
25
+ * than no fallback at all: verified against desktop Excel, such a comment shows as neither a thread nor
26
+ * a note, so the text disappears entirely.
27
+ *
28
+ * Ordered by cell, row-major, the way Excel writes the list — so a fallback lands interleaved among the
29
+ * notes rather than appended after them, and the VML shapes follow the same order.
30
+ */
31
+ export declare function collectComments(sheet: Worksheet, threads: readonly CommentThread[]): CommentCell[];
32
+ /**
33
+ * The `xl/comments{n}.xml` part.
34
+ *
35
+ * Authors are laid out the way Excel lays them out: one synthetic `tc={headId}` entry per threaded
36
+ * conversation first, then a single anonymous author shared by every note (the model carries no note
37
+ * author). Each comment points at its own author by index, and a fallback additionally carries the
38
+ * `xr:uid` naming its thread — the pair that keeps Excel treating the cell as threaded.
39
+ */
40
+ export declare function commentsXml(comments: readonly CommentCell[]): string;
41
+ /** The `xl/drawings/vmlDrawing{n}.vml` companion: one hidden text-box shape per comment, in the same
42
+ * order as the comments part. Anchor coordinates place the box a couple of cells down-and-right of its
43
+ * owner; Excel refines them on open, so the values are a sensible starting geometry rather than a
44
+ * pixel-exact layout. A thread's fallback shape is `ObjectType="Note"` like any other — Excel draws the
45
+ * threaded-comment card itself and only needs the shape to exist. */
46
+ export declare function vmlDrawingXml(comments: readonly CommentCell[]): string;
47
+ /** One `<comment>` read back from a comments part. */
48
+ export interface ParsedComment {
49
+ readonly text: string;
50
+ /**
51
+ * The thread head id this comment is the legacy fallback for, read off its synthetic `tc={headId}`
52
+ * author; absent for a user's own note.
53
+ */
54
+ readonly threadId?: string;
55
+ }
56
+ /**
57
+ * Parse a `comments{n}.xml` part into a map of A1 reference → comment. Text runs within one comment are
58
+ * concatenated; an author-name run is Excel's own convention and is not stripped, so a note reads back
59
+ * as exactly the text that was written.
60
+ */
61
+ export declare function parseComments(xml: string): Map<string, ParsedComment>;
62
+ /**
63
+ * Apply a parsed comments part onto a sheet's cells as notes, addressing each by its A1 reference.
64
+ *
65
+ * A thread's legacy fallback is not a note and does not become one: its text is boilerplate wrapping a
66
+ * copy of the conversation, so surfacing it as `cell.note` hands the caller garbage — and on write it
67
+ * would be re-emitted as a plain note, destroying the `tc=`/`xr:uid` binding and leaving Excel unable to
68
+ * see the thread at all.
69
+ *
70
+ * Suppressed only for a conversation the reader actually holds: a file whose thread part is missing or
71
+ * damaged has nothing else left, so there the boilerplate is kept rather than the content lost. Call
72
+ * after the sheet's threads are restored, since that is what this reads to decide.
73
+ */
74
+ export declare function applyNotes(sheet: Worksheet, comments: ReadonlyMap<string, ParsedComment>): void;