@shbernal/ts-xlsx 3.0.0 → 3.1.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 (213) hide show
  1. package/README.md +4 -1
  2. package/dist/bytes.d.ts +7 -0
  3. package/dist/bytes.js +22 -0
  4. package/dist/core/address.d.ts +60 -2
  5. package/dist/core/address.js +40 -24
  6. package/dist/core/autofilter.d.ts +4 -3
  7. package/dist/core/autofilter.js +31 -30
  8. package/dist/core/axis-handle.d.ts +2 -0
  9. package/dist/core/axis-handle.js +5 -2
  10. package/dist/core/cell.d.ts +4 -3
  11. package/dist/core/cell.js +4 -6
  12. package/dist/core/clone.d.ts +29 -0
  13. package/dist/core/clone.js +24 -0
  14. package/dist/core/color-resolution.js +2 -4
  15. package/dist/core/column.d.ts +1 -0
  16. package/dist/core/column.js +3 -0
  17. package/dist/core/comment-thread.js +2 -1
  18. package/dist/core/conditional-formatting-overlay.d.ts +2 -1
  19. package/dist/core/conditional-formatting-overlay.js +2 -2
  20. package/dist/core/conditional-formatting.d.ts +9 -1
  21. package/dist/core/conditional-formatting.js +34 -18
  22. package/dist/core/containers.d.ts +12 -0
  23. package/dist/core/containers.js +5 -0
  24. package/dist/core/data-validation-overlay.d.ts +2 -1
  25. package/dist/core/data-validation-overlay.js +4 -4
  26. package/dist/core/data-validation.d.ts +6 -0
  27. package/dist/core/data-validation.js +15 -4
  28. package/dist/core/date-format.d.ts +20 -0
  29. package/dist/core/date-format.js +160 -0
  30. package/dist/core/date.d.ts +43 -8
  31. package/dist/core/date.js +18 -2
  32. package/dist/core/formula.d.ts +26 -0
  33. package/dist/core/formula.js +20 -4
  34. package/dist/core/grid-edits.d.ts +2 -3
  35. package/dist/core/grid-edits.js +69 -70
  36. package/dist/core/grid-shift.d.ts +58 -9
  37. package/dist/core/grid-shift.js +26 -4
  38. package/dist/core/image.d.ts +13 -9
  39. package/dist/core/image.js +7 -5
  40. package/dist/core/internal.d.ts +8 -2
  41. package/dist/core/limits.d.ts +23 -0
  42. package/dist/core/limits.js +4 -0
  43. package/dist/core/merge.d.ts +5 -4
  44. package/dist/core/merge.js +18 -19
  45. package/dist/core/pivot-table.js +63 -45
  46. package/dist/core/protection.js +2 -11
  47. package/dist/core/range.js +34 -30
  48. package/dist/core/row-input.js +2 -2
  49. package/dist/core/row.d.ts +1 -0
  50. package/dist/core/row.js +3 -0
  51. package/dist/core/style.d.ts +85 -70
  52. package/dist/core/style.js +92 -28
  53. package/dist/core/table-style.d.ts +1 -1
  54. package/dist/core/table-style.js +4 -4
  55. package/dist/core/table.d.ts +27 -9
  56. package/dist/core/table.js +51 -34
  57. package/dist/core/theme.js +6 -5
  58. package/dist/core/value.d.ts +9 -0
  59. package/dist/core/value.js +36 -34
  60. package/dist/core/workbook-media.d.ts +34 -0
  61. package/dist/core/workbook-media.js +44 -0
  62. package/dist/core/workbook-styles.d.ts +67 -1
  63. package/dist/core/workbook-vba.js +3 -2
  64. package/dist/core/workbook.d.ts +23 -2
  65. package/dist/core/workbook.js +24 -40
  66. package/dist/core/worksheet-comments.d.ts +2 -1
  67. package/dist/core/worksheet-comments.js +20 -13
  68. package/dist/core/worksheet-merges.d.ts +44 -0
  69. package/dist/core/worksheet-merges.js +59 -0
  70. package/dist/core/worksheet-model.d.ts +9 -1
  71. package/dist/core/worksheet-model.js +24 -9
  72. package/dist/core/worksheet.d.ts +55 -6
  73. package/dist/core/worksheet.js +36 -44
  74. package/dist/customui/ribbon.js +2 -1
  75. package/dist/entries/core.d.ts +18 -13
  76. package/dist/entries/core.js +12 -6
  77. package/dist/entries/xlsx.d.ts +1 -1
  78. package/dist/errors.d.ts +64 -0
  79. package/dist/errors.js +22 -0
  80. package/dist/hex.d.ts +8 -0
  81. package/dist/hex.js +3 -0
  82. package/dist/io/csv/delimiter.d.ts +9 -0
  83. package/dist/io/csv/delimiter.js +6 -0
  84. package/dist/io/csv/read.js +11 -4
  85. package/dist/io/csv/write.d.ts +9 -2
  86. package/dist/io/csv/write.js +9 -38
  87. package/dist/io/opc/inflate.js +14 -0
  88. package/dist/io/opc/namespaces.d.ts +16 -0
  89. package/dist/io/opc/namespaces.js +4 -0
  90. package/dist/io/opc/part-paths.d.ts +9 -0
  91. package/dist/io/opc/part-paths.js +1 -1
  92. package/dist/io/opc/read-opc.d.ts +54 -14
  93. package/dist/io/opc/read-opc.js +42 -20
  94. package/dist/io/opc/read-options.d.ts +5 -1
  95. package/dist/io/opc/sniff-format.d.ts +3 -3
  96. package/dist/io/opc/sniff-format.js +2 -2
  97. package/dist/io/style/xf-style.d.ts +23 -1
  98. package/dist/io/style/xf-style.js +8 -0
  99. package/dist/io/xlsb/formula.d.ts +2 -2
  100. package/dist/io/xlsb/formula.js +49 -34
  101. package/dist/io/xlsb/primitives.js +8 -14
  102. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  103. package/dist/io/xlsb/ptg-functions.js +385 -596
  104. package/dist/io/xlsb/read-styles.js +45 -81
  105. package/dist/io/xlsb/read-worksheet.d.ts +20 -1
  106. package/dist/io/xlsb/read-worksheet.js +73 -89
  107. package/dist/io/xlsb/read.d.ts +6 -3
  108. package/dist/io/xlsb/read.js +44 -23
  109. package/dist/io/xlsb/record-stream.d.ts +30 -0
  110. package/dist/io/xlsb/record-stream.js +20 -0
  111. package/dist/io/xlsb/record-types.d.ts +1 -5
  112. package/dist/io/xlsb/record-types.js +1 -5
  113. package/dist/io/xlsx/cell-accumulator.d.ts +8 -1
  114. package/dist/io/xlsx/cell-accumulator.js +32 -9
  115. package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
  116. package/dist/io/xlsx/cell-style-resolution.js +29 -0
  117. package/dist/io/xlsx/cell-value.d.ts +5 -3
  118. package/dist/io/xlsx/cell-value.js +13 -15
  119. package/dist/io/xlsx/color-xml.js +3 -2
  120. package/dist/io/xlsx/column-budget.d.ts +26 -0
  121. package/dist/io/xlsx/column-budget.js +22 -0
  122. package/dist/io/xlsx/comments.d.ts +6 -2
  123. package/dist/io/xlsx/comments.js +36 -32
  124. package/dist/io/xlsx/conditional-formatting.d.ts +6 -3
  125. package/dist/io/xlsx/conditional-formatting.js +30 -26
  126. package/dist/io/xlsx/data-validation.js +29 -27
  127. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  128. package/dist/io/xlsx/edit-vba.js +14 -21
  129. package/dist/io/xlsx/font-xml.d.ts +7 -0
  130. package/dist/io/xlsx/font-xml.js +104 -0
  131. package/dist/io/xlsx/hyperlinks.d.ts +37 -5
  132. package/dist/io/xlsx/hyperlinks.js +22 -13
  133. package/dist/io/xlsx/images.js +11 -13
  134. package/dist/io/xlsx/namespaces.d.ts +16 -0
  135. package/dist/io/xlsx/namespaces.js +7 -0
  136. package/dist/io/xlsx/package-plan.d.ts +14 -2
  137. package/dist/io/xlsx/package-plan.js +39 -11
  138. package/dist/io/xlsx/pivot.js +8 -7
  139. package/dist/io/xlsx/read-parts.d.ts +59 -0
  140. package/dist/io/xlsx/read-parts.js +252 -0
  141. package/dist/io/xlsx/read-pivot.js +2 -1
  142. package/dist/io/xlsx/read-repair.d.ts +26 -0
  143. package/dist/io/xlsx/read-repair.js +39 -0
  144. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +6 -3
  145. package/dist/io/xlsx/read-rows.d.ts +18 -5
  146. package/dist/io/xlsx/read-rows.js +45 -31
  147. package/dist/io/xlsx/read-shared-strings.js +3 -2
  148. package/dist/io/xlsx/read-styles.d.ts +1 -6
  149. package/dist/io/xlsx/read-styles.js +6 -71
  150. package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
  151. package/dist/io/xlsx/read-workbook-xml.js +170 -0
  152. package/dist/io/xlsx/read-worksheet.d.ts +2 -1
  153. package/dist/io/xlsx/read-worksheet.js +35 -164
  154. package/dist/io/xlsx/read.d.ts +5 -12
  155. package/dist/io/xlsx/read.js +73 -406
  156. package/dist/io/xlsx/rich-text.js +1 -1
  157. package/dist/io/xlsx/row-position.d.ts +18 -0
  158. package/dist/io/xlsx/row-position.js +11 -0
  159. package/dist/io/xlsx/row-xml.d.ts +128 -0
  160. package/dist/io/xlsx/row-xml.js +203 -0
  161. package/dist/io/xlsx/shared-formulas.js +2 -2
  162. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  163. package/dist/io/xlsx/sheet-properties.js +145 -9
  164. package/dist/io/xlsx/style-elements.d.ts +43 -0
  165. package/dist/io/xlsx/style-elements.js +156 -0
  166. package/dist/io/xlsx/styles.d.ts +12 -3
  167. package/dist/io/xlsx/styles.js +5 -207
  168. package/dist/io/xlsx/tables.js +10 -14
  169. package/dist/io/xlsx/theme-xml.d.ts +26 -4
  170. package/dist/io/xlsx/theme-xml.js +107 -69
  171. package/dist/io/xlsx/threaded-comments.d.ts +8 -10
  172. package/dist/io/xlsx/threaded-comments.js +2 -1
  173. package/dist/io/xlsx/workbook-xml.d.ts +54 -4
  174. package/dist/io/xlsx/workbook-xml.js +60 -34
  175. package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
  176. package/dist/io/xlsx/worksheet-xml.js +48 -229
  177. package/dist/io/xlsx/write-stream.d.ts +65 -16
  178. package/dist/io/xlsx/write-stream.js +50 -20
  179. package/dist/io/xlsx/write.d.ts +1 -2
  180. package/dist/io/xlsx/write.js +114 -74
  181. package/dist/rel-type.d.ts +16 -0
  182. package/dist/rel-type.js +6 -0
  183. package/dist/token-set.d.ts +13 -0
  184. package/dist/token-set.js +4 -1
  185. package/dist/vba/bytes.d.ts +14 -5
  186. package/dist/vba/bytes.js +16 -6
  187. package/dist/vba/cfb-format.d.ts +8 -0
  188. package/dist/vba/cfb-format.js +1 -0
  189. package/dist/vba/cfb-writer.d.ts +9 -0
  190. package/dist/vba/cfb-writer.js +12 -9
  191. package/dist/vba/cfb.d.ts +13 -1
  192. package/dist/vba/cfb.js +73 -51
  193. package/dist/vba/codepage.js +2 -1
  194. package/dist/vba/errors.d.ts +7 -0
  195. package/dist/vba/ms-ovba.js +51 -16
  196. package/dist/vba/project-editor.js +35 -40
  197. package/dist/vba/project.d.ts +11 -1
  198. package/dist/vba/project.js +27 -8
  199. package/dist/vba/vba-encoding.js +4 -5
  200. package/dist/xml/xml-attrs.d.ts +37 -0
  201. package/dist/xml/xml-attrs.js +30 -0
  202. package/dist/xml/xml-chars.d.ts +40 -0
  203. package/dist/xml/xml-chars.js +12 -0
  204. package/dist/xml/xml-namespaces.d.ts +31 -0
  205. package/dist/xml/xml-namespaces.js +50 -0
  206. package/dist/xml/xml-read.d.ts +72 -10
  207. package/dist/xml/xml-read.js +94 -18
  208. package/dist/xml/xml-scan.d.ts +15 -37
  209. package/dist/xml/xml-scan.js +12 -40
  210. package/dist/xml/xml.d.ts +28 -27
  211. package/dist/xml/xml.js +24 -24
  212. package/package.json +12 -7
  213. /package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +0 -0
@@ -15,7 +15,6 @@ export const BRT = {
15
15
  ColInfo: 60,
16
16
  MergeCell: 176,
17
17
  SSTItem: 19,
18
- BeginSst: 159,
19
18
  EndSst: 160,
20
19
  Fmt: 44,
21
20
  Font: 43,
@@ -37,6 +36,7 @@ export const BRT = {
37
36
  EndCellXFs: 618,
38
37
  BeginStyles: 619,
39
38
  EndStyles: 620,
39
+ WbProp: 153,
40
40
  BundleSh: 156,
41
41
  BeginBundleShs: 143,
42
42
  EndBundleShs: 144,
@@ -45,10 +45,6 @@ export const BRT = {
45
45
  EndExternals: 354,
46
46
  SupSelf: 357,
47
47
  ExternSheet: 362,
48
- WsProp: 147,
49
- WsDim: 148,
50
48
  WsFmtInfo: 485,
51
- BeginSheetData: 145,
52
- EndSheetData: 146,
53
49
  ArrFmla: 426,
54
50
  };
@@ -1,3 +1,4 @@
1
+ import type { DateEpoch } from '../../core/date.ts';
1
2
  import type { CellValue } from '../../core/value.ts';
2
3
  import type { Worksheet } from '../../core/worksheet.ts';
3
4
  import { type XmlAttributes } from '../../xml/xml-scan.ts';
@@ -7,13 +8,19 @@ export declare class CellAccumulator {
7
8
  #private;
8
9
  constructor(options: {
9
10
  readonly richRuns: boolean;
11
+ readonly dateEpoch: DateEpoch;
10
12
  });
11
13
  /** This cell's `<c r>` address (`"B3"`), or '' when it carried none. */
12
14
  get ref(): string;
13
15
  /** This cell's own `<c s>` style index, or -1 when it carries none. */
14
16
  get styleIndex(): number;
15
- /** This cell's 1-based column, or -1 when its address was absent or unparseable. */
17
+ /** This cell's 1-based column, or -1 when its address was unparseable. */
16
18
  get col(): number;
19
+ /**
20
+ * Open a `<row>`: the cells that follow belong to it, and the next one with no `r` of its own is
21
+ * its first column. Both readers call this where they already tell the style resolver a row opened.
22
+ */
23
+ openRow(number: number): void;
17
24
  /**
18
25
  * The text gathered since the current element opened. A caller reads it for the elements it
19
26
  * captures itself (see {@link capture}); the machine reads it for its own.
@@ -1,15 +1,17 @@
1
- import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
1
+ import { encodeAddress, MAX_COLUMN, tryDecodeCellRef } from '../../core/address.js';
2
2
  import { translateFormula, unmangleFunctions } from '../../core/formula.js';
3
- import { boolStrict, numInteger } from '../../xml/xml-scan.js';
3
+ import { numInteger } from '../../xml/xml-attrs.js';
4
+ import { boolStrict } from '../../xml/xml-scan.js';
4
5
  import { applyXfToCell } from '../style/xf-style.js';
5
6
  import { decodeCellContent, decodeFormulaResult, } from './cell-value.js';
6
- import { RunAccumulator } from './rich-runs.js';
7
+ import { RunAccumulator } from './read-rich-runs.js';
7
8
  export class CellAccumulator {
8
9
  #ref = '';
9
10
  #type = '';
10
11
  #style = -1;
11
12
  #col = -1;
12
13
  #row = -1;
14
+ #nextCol = 1;
13
15
  #formula = '';
14
16
  #formulaShared = false;
15
17
  #formulaSi = -1;
@@ -22,8 +24,10 @@ export class CellAccumulator {
22
24
  #masters = new Map();
23
25
  #capture = false;
24
26
  #text = '';
27
+ #dateEpoch;
25
28
  constructor(options) {
26
29
  this.#runs = new RunAccumulator({ container: 'is', readRuns: options.richRuns });
30
+ this.#dateEpoch = options.dateEpoch;
27
31
  }
28
32
  get ref() {
29
33
  return this.#ref;
@@ -34,13 +38,14 @@ export class CellAccumulator {
34
38
  get col() {
35
39
  return this.#col;
36
40
  }
41
+ openRow(number) {
42
+ this.#row = number;
43
+ this.#nextCol = 1;
44
+ }
37
45
  #beginCell(attrs) {
38
46
  this.#type = attrs.t ?? '';
39
47
  this.#style = numInteger(attrs.s, 0) ?? -1;
40
- const decoded = tryDecodeCellRef(attrs.r ?? '');
41
- this.#ref = decoded === undefined ? '' : (attrs.r ?? '');
42
- this.#col = decoded?.col ?? -1;
43
- this.#row = decoded?.row ?? -1;
48
+ this.#placeCell(attrs.r);
44
49
  this.#formula = '';
45
50
  this.#valueText = '';
46
51
  this.#runs.beginContainer();
@@ -51,6 +56,24 @@ export class CellAccumulator {
51
56
  this.#sharedClone = false;
52
57
  this.#dataTable = null;
53
58
  }
59
+ #placeCell(ref) {
60
+ const decoded = ref === undefined ? undefined : tryDecodeCellRef(ref);
61
+ if (decoded !== undefined) {
62
+ this.#ref = ref ?? '';
63
+ this.#col = decoded.col;
64
+ this.#row = decoded.row;
65
+ }
66
+ else if (ref === undefined && this.#row > 0 && this.#nextCol <= MAX_COLUMN) {
67
+ this.#col = this.#nextCol;
68
+ this.#ref = encodeAddress(this.#col, this.#row);
69
+ }
70
+ else {
71
+ this.#ref = '';
72
+ this.#col = -1;
73
+ }
74
+ if (this.#col > 0)
75
+ this.#nextCol = this.#col + 1;
76
+ }
54
77
  #beginFormula(attrs, selfClosing) {
55
78
  this.#formulaShared = attrs.t === 'shared';
56
79
  this.#formulaSi = numInteger(attrs.si, 0) ?? -1;
@@ -165,7 +188,7 @@ export class CellAccumulator {
165
188
  cachedResult(style) {
166
189
  if (!this.#hasValue)
167
190
  return {};
168
- const result = decodeFormulaResult(this.#type, this.#valueText, style?.numFmt);
191
+ const result = decodeFormulaResult(this.#type, this.#valueText, style?.numFmt, this.#dateEpoch);
169
192
  return result === undefined ? {} : { result };
170
193
  }
171
194
  decode(sharedStrings, style) {
@@ -178,6 +201,6 @@ export class CellAccumulator {
178
201
  inlineText: this.#runs.plainText,
179
202
  richTextRuns: this.#runs.runs,
180
203
  };
181
- return decodeCellContent(raw, sharedStrings, style?.numFmt);
204
+ return decodeCellContent(raw, sharedStrings, style?.numFmt, this.#dateEpoch);
182
205
  }
183
206
  }
@@ -0,0 +1,25 @@
1
+ /** The attributes this resolver reads, the subset both readers' scanners already hand it. */
2
+ interface StyleAttributes {
3
+ readonly [key: string]: string | undefined;
4
+ }
5
+ /**
6
+ * The cell → row → column style resolution for one worksheet, fed by the same elements in the same
7
+ * order both readers already visit them: `<col>` before any cell references it, `<row>` before its
8
+ * own cells.
9
+ */
10
+ export declare class CellStyleResolver {
11
+ #private;
12
+ /** Record a `<col min max style>` span's default for the columns it covers. */
13
+ noteColumnSpan(first: number, last: number, attrs: StyleAttributes): void;
14
+ /** Open a `<row>`, taking the format it declares for its own bare cells. */
15
+ openRow(attrs: StyleAttributes): void;
16
+ /** Close the row, so a cell outside one never inherits the last row's format. */
17
+ closeRow(): void;
18
+ /**
19
+ * The `xfStyles` index a cell's format resolves to, or `-1` when nothing declares one. A row's
20
+ * format applies only when the row is marked `customFormat`; without that flag the `s` on a `<row>`
21
+ * describes the row itself and is not inherited by its cells.
22
+ */
23
+ indexFor(col: number, cellStyleIndex: number): number;
24
+ }
25
+ export {};
@@ -0,0 +1,29 @@
1
+ import { numInteger } from '../../xml/xml-attrs.js';
2
+ import { boolStrict } from '../../xml/xml-scan.js';
3
+ export class CellStyleResolver {
4
+ #columnStyle = new Map();
5
+ #rowStyle = -1;
6
+ #rowCustomFormat = false;
7
+ noteColumnSpan(first, last, attrs) {
8
+ const styleIndex = numInteger(attrs.style, 0) ?? -1;
9
+ if (styleIndex < 0)
10
+ return;
11
+ for (let index = first; index <= last; index++)
12
+ this.#columnStyle.set(index, styleIndex);
13
+ }
14
+ openRow(attrs) {
15
+ this.#rowStyle = numInteger(attrs.s, 0) ?? -1;
16
+ this.#rowCustomFormat = boolStrict(attrs.customFormat);
17
+ }
18
+ closeRow() {
19
+ this.#rowStyle = -1;
20
+ this.#rowCustomFormat = false;
21
+ }
22
+ indexFor(col, cellStyleIndex) {
23
+ if (cellStyleIndex >= 0)
24
+ return cellStyleIndex;
25
+ if (this.#rowCustomFormat && this.#rowStyle >= 0)
26
+ return this.#rowStyle;
27
+ return this.#columnStyle.get(col) ?? -1;
28
+ }
29
+ }
@@ -1,3 +1,4 @@
1
+ import { type DateEpoch } from '../../core/date.ts';
1
2
  import { type CellValue, type FormulaResult, type RichTextRun, type RichTextValue } from '../../core/value.ts';
2
3
  /**
3
4
  * One entry of the shared-strings pool. A `<si>` built from a bare `<t>` is a plain string; a `<si>`
@@ -22,11 +23,12 @@ export interface RawCell {
22
23
  * Decode a gathered cell into its model value. A formula cell becomes a `{formula, result?}`
23
24
  * object (the on-disk `_xlfn.`/`_xlpm.` mangling stripped back to the readable name); a plain
24
25
  * 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
+ * `t` type. `numFmt` is the cell's resolved number-format code, used only for date detection, and
27
+ * `epoch` the workbook's date system, which is what a serial under such a format counts from.
26
28
  */
27
- export declare function decodeCellContent(raw: RawCell, sharedStrings: readonly SharedString[], numFmt: string | undefined): CellValue;
29
+ export declare function decodeCellContent(raw: RawCell, sharedStrings: readonly SharedString[], numFmt: string | undefined, epoch: DateEpoch): CellValue;
28
30
  /** Decode a formula's cached `<v>` result by its `t` type, coercing a numeric result under a date
29
31
  * `numFmt` to a {@link Date} exactly as a bare numeric cell is, so a date-valued formula result
30
32
  * (e.g. `TODAY()`) reads back as a Date, not a serial. Shared by the buffered reader's shared-formula
31
33
  * 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 | undefined;
34
+ export declare function decodeFormulaResult(type: string, valueText: string, numFmt: string | undefined, epoch: DateEpoch): FormulaResult | undefined;
@@ -1,20 +1,21 @@
1
- import { isDateFormat, serialToDate } from '../../core/date.js';
1
+ import { coerceDateSerial, parseDateText } from '../../core/date.js';
2
2
  import { unmangleFunctions } from '../../core/formula.js';
3
3
  import { isErrorCode, } from '../../core/value.js';
4
- import { boolStrict, decodeSpreadsheetText, numFinite } from '../../xml/xml-scan.js';
5
- export function decodeCellContent(raw, sharedStrings, numFmt) {
4
+ import { decodeSpreadsheetText, numFinite, numInteger } from '../../xml/xml-attrs.js';
5
+ import { boolStrict } from '../../xml/xml-scan.js';
6
+ export function decodeCellContent(raw, sharedStrings, numFmt, epoch) {
6
7
  if (raw.hasFormula) {
7
8
  const stored = unmangleFunctions(raw.formula);
8
- const result = raw.hasValue ? decodeFormulaResult(raw.type, raw.valueText, numFmt) : undefined;
9
+ const result = raw.hasValue
10
+ ? decodeFormulaResult(raw.type, raw.valueText, numFmt, epoch)
11
+ : undefined;
9
12
  return result === undefined ? { formula: stored } : { formula: stored, result };
10
13
  }
11
14
  if (raw.type === 'inlineStr' && raw.richTextRuns !== undefined && raw.richTextRuns.length > 0) {
12
15
  return { richText: raw.richTextRuns };
13
16
  }
14
17
  const value = decodeValue(raw.type, raw.valueText, raw.inlineText, raw.hasValue, sharedStrings);
15
- return typeof value === 'number' && numFmt !== undefined && isDateFormat(numFmt)
16
- ? serialToDate(value)
17
- : value;
18
+ return coerceDateSerial(value, numFmt, epoch);
18
19
  }
19
20
  function decodeValue(type, valueText, inlineText, hasValue, sharedStrings) {
20
21
  switch (type) {
@@ -23,10 +24,10 @@ function decodeValue(type, valueText, inlineText, hasValue, sharedStrings) {
23
24
  case 'str':
24
25
  return decodeSpreadsheetText(valueText);
25
26
  case 'd':
26
- return valueText === '' ? null : new Date(valueText);
27
+ return parseDateText(valueText);
27
28
  case 's': {
28
- const index = Number(valueText);
29
- return Number.isInteger(index) ? (sharedStrings[index] ?? '') : '';
29
+ const index = numInteger(valueText, 0);
30
+ return index === undefined ? '' : (sharedStrings[index] ?? '');
30
31
  }
31
32
  case 'b':
32
33
  return boolStrict(valueText);
@@ -39,11 +40,8 @@ function decodeValue(type, valueText, inlineText, hasValue, sharedStrings) {
39
40
  }
40
41
  }
41
42
  }
42
- export function decodeFormulaResult(type, valueText, numFmt) {
43
- const result = decodeResult(type, valueText);
44
- return typeof result === 'number' && numFmt !== undefined && isDateFormat(numFmt)
45
- ? serialToDate(result)
46
- : result;
43
+ export function decodeFormulaResult(type, valueText, numFmt, epoch) {
44
+ return coerceDateSerial(decodeResult(type, valueText), numFmt, epoch);
47
45
  }
48
46
  function decodeResult(type, valueText) {
49
47
  switch (type) {
@@ -1,10 +1,11 @@
1
1
  import { parseArgb } from '../../core/style.js';
2
- import { numFinite, numInteger } from '../../xml/xml-scan.js';
2
+ import { quoted } from '../../errors.js';
3
+ import { numFinite, numInteger } from '../../xml/xml-attrs.js';
3
4
  import { numberText } from '../../xml/xml.js';
4
5
  function normalizeArgb(argb) {
5
6
  const rgb = parseArgb(argb);
6
7
  if (rgb === undefined) {
7
- throw new SyntaxError(`Invalid ARGB colour ${JSON.stringify(argb)}: expected 6 or 8 hexadecimal digits`);
8
+ throw new SyntaxError(`Invalid ARGB colour ${quoted(argb)}: expected 6 or 8 hexadecimal digits`);
8
9
  }
9
10
  return rgb;
10
11
  }
@@ -0,0 +1,26 @@
1
+ import { type XmlAttributes } from '../../xml/xml-scan.ts';
2
+ /**
3
+ * A per-sheet allowance of column touches, spent by each `<col>` span the reader applies.
4
+ *
5
+ * A span past the allowance is truncated, and one starting past it is dropped, rather than the read
6
+ * being refused: that is the stance both readers already take on an out-of-grid span, and it means a
7
+ * hostile file loses formatting it could not have meant while a real one is untouched.
8
+ */
9
+ export declare class ColumnRecordBudget {
10
+ #private;
11
+ /** The last column of `[first, last]` this sheet can still afford, or `undefined` when none is. */
12
+ take(first: number, last: number): number | undefined;
13
+ }
14
+ /**
15
+ * The columns a `<col min max>` element actually applies to, or `undefined` for one that applies to
16
+ * none: unreadable bounds, a span starting past the grid, or a budget already spent.
17
+ *
18
+ * Beside the budget because the two decisions are one decision. Both readers had their own copy, and
19
+ * the copies had already drifted: one tested `min > MAX_COLUMN` and the other did not, which happened
20
+ * not to matter only because `budget.take` returns `undefined` when `first > last` - an accident of
21
+ * this class's contract rather than an agreement between the readers.
22
+ */
23
+ export declare function takeColumnSpan(attrs: XmlAttributes, budget: ColumnRecordBudget): {
24
+ first: number;
25
+ last: number;
26
+ } | undefined;
@@ -0,0 +1,22 @@
1
+ import { MAX_COLUMN } from '../../core/address.js';
2
+ import { numInteger } from '../../xml/xml-attrs.js';
3
+ import {} from '../../xml/xml-scan.js';
4
+ const MAX_COLUMN_RECORD_TOUCHES = MAX_COLUMN * 4;
5
+ export class ColumnRecordBudget {
6
+ #remaining = MAX_COLUMN_RECORD_TOUCHES;
7
+ take(first, last) {
8
+ if (this.#remaining <= 0 || first > last)
9
+ return undefined;
10
+ const affordable = Math.min(last, first + this.#remaining - 1);
11
+ this.#remaining -= affordable - first + 1;
12
+ return affordable;
13
+ }
14
+ }
15
+ export function takeColumnSpan(attrs, budget) {
16
+ const first = numInteger(attrs.min, 1);
17
+ const declared = numInteger(attrs.max, 1);
18
+ if (first === undefined || declared === undefined || first > MAX_COLUMN)
19
+ return undefined;
20
+ const last = budget.take(first, Math.min(declared, MAX_COLUMN));
21
+ return last === undefined ? undefined : { first, last };
22
+ }
@@ -1,6 +1,8 @@
1
+ import type { Cell } from '../../core/cell.ts';
1
2
  import type { CommentThread } from '../../core/comment-thread.ts';
2
3
  import type { Worksheet } from '../../core/worksheet.ts';
3
- /** A comment bound for `comments{n}.xml`, paired with the coordinates the VML anchor needs. */
4
+ /** A comment bound for `comments{n}.xml`, paired with the coordinates the VML anchor needs.
5
+ */
4
6
  export interface CommentCell {
5
7
  readonly ref: string;
6
8
  /** 1-based row of the commented cell. */
@@ -28,7 +30,9 @@ export interface CommentCell {
28
30
  * Ordered by cell, row-major, the way Excel writes the list, so a fallback lands interleaved among the
29
31
  * notes rather than appended after them, and the VML shapes follow the same order.
30
32
  */
31
- export declare function collectComments(sheet: Worksheet, threads: readonly CommentThread[]): CommentCell[];
33
+ export declare function collectComments(cells: Iterable<Cell>, threads: readonly CommentThread[], alreadyCollected?: readonly CommentCell[]): CommentCell[];
34
+ /** The notes among a run of cells, for a writer that must ask before the cells are evicted. */
35
+ export declare function collectNotes(cells: Iterable<Cell>): CommentCell[];
32
36
  /**
33
37
  * The `xl/comments{n}.xml` part.
34
38
  *
@@ -1,20 +1,32 @@
1
1
  import { tryDecodeCellRef } from '../../core/address.js';
2
- import { parseXml } from '../../xml/xml-read.js';
3
- import { decodeSpreadsheetText, localName, numInteger } from '../../xml/xml-scan.js';
2
+ import { decodeSpreadsheetText, numInteger } from '../../xml/xml-attrs.js';
3
+ import { parseXml, TextCapture } from '../../xml/xml-read.js';
4
+ import { localName } from '../../xml/xml-scan.js';
4
5
  import { escapeText, textAttr, textElement, XML_DECLARATION } from '../../xml/xml.js';
5
6
  import { MARKUP_COMPATIBILITY_NS, REVISION_NS, SPREADSHEETML_NS } from './namespaces.js';
6
- export function collectComments(sheet, threads) {
7
+ export function collectComments(cells, threads, alreadyCollected = []) {
7
8
  const fallbacks = threadFallbacks(threads);
8
9
  const anchored = new Set(fallbacks.map((fallback) => fallback.ref));
9
- const comments = [...fallbacks];
10
- for (const { cells } of sheet.rows()) {
11
- for (const cell of cells) {
12
- if (cell.note === undefined || anchored.has(cell.address))
13
- continue;
14
- comments.push({ ref: cell.address, row: cell.row, col: cell.col, text: cell.note });
15
- }
10
+ const comments = [...fallbacks, ...alreadyCollected];
11
+ for (const cell of cells) {
12
+ if (cell.note === undefined || anchored.has(cell.address))
13
+ continue;
14
+ comments.push(noteOn(cell, cell.note));
15
+ }
16
+ return comments
17
+ .filter((comment) => comment.threadId !== undefined || !anchored.has(comment.ref))
18
+ .sort((a, b) => a.row - b.row || a.col - b.col);
19
+ }
20
+ export function collectNotes(cells) {
21
+ const notes = [];
22
+ for (const cell of cells) {
23
+ if (cell.note !== undefined)
24
+ notes.push(noteOn(cell, cell.note));
16
25
  }
17
- return comments.sort((a, b) => a.row - b.row || a.col - b.col);
26
+ return notes;
27
+ }
28
+ function noteOn(cell, text) {
29
+ return { ref: cell.address, row: cell.row, col: cell.col, text };
18
30
  }
19
31
  function threadFallbacks(threads) {
20
32
  const fallbacks = [];
@@ -76,13 +88,14 @@ const VML_HEADER = '<xml xmlns:v="urn:schemas-microsoft-com:vml" ' +
76
88
  '<v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" ' +
77
89
  'path="m,l,21600r21600,l21600,xe"><v:stroke joinstyle="miter"/>' +
78
90
  '<v:path gradientshapeok="t" o:connecttype="rect"/></v:shapetype>';
91
+ const FIRST_SHAPE_ID = 1025;
79
92
  export function vmlDrawingXml(comments) {
80
93
  const shapes = comments
81
94
  .map((comment, i) => {
82
95
  const row0 = comment.row - 1;
83
96
  const col0 = comment.col - 1;
84
97
  const anchor = `${col0 + 1}, 15, ${row0}, 2, ${col0 + 3}, 15, ${row0 + 4}, 4`;
85
- return (`<v:shape id="_x0000_s${1025 + i}" type="#_x0000_t202" ` +
98
+ return (`<v:shape id="_x0000_s${FIRST_SHAPE_ID + i}" type="#_x0000_t202" ` +
86
99
  'style="position:absolute;margin-left:59.25pt;margin-top:1.5pt;width:108pt;height:59.25pt;' +
87
100
  `z-index:${i + 1};visibility:hidden" fillcolor="#ffffe1" o:insetmode="auto">` +
88
101
  '<v:fill color2="#ffffe1"/><v:shadow on="t" color="black" obscured="t"/>' +
@@ -95,50 +108,41 @@ export function vmlDrawingXml(comments) {
95
108
  .join('');
96
109
  return `${VML_HEADER}${shapes}</xml>`;
97
110
  }
98
- const COMMENT_EMPTY_CLOSES = new Set(['author']);
111
+ const COMMENT_EMPTY_CLOSES = new Set(['author', 'text']);
99
112
  const THREAD_AUTHOR_PREFIX = 'tc=';
100
113
  export function parseComments(xml) {
101
114
  const comments = new Map();
102
115
  const authors = [];
103
116
  let currentRef;
104
117
  let currentAuthorId;
105
- let capture;
106
- let buffer = '';
118
+ const capture = new TextCapture(['author', 'text']);
119
+ let body = '';
107
120
  parseXml(xml, {
108
- onOpen(name, attrs) {
121
+ onOpen(name, attrs, selfClosing) {
109
122
  const local = localName(name);
110
123
  if (local === 'comment') {
111
124
  currentRef = attrs.ref;
112
125
  currentAuthorId = attrs.authorId;
113
- buffer = '';
114
- }
115
- else if (local === 'author') {
116
- capture = 'author';
117
- buffer = '';
118
- }
119
- else if (local === 'text') {
120
- capture = 'text';
126
+ body = '';
121
127
  }
128
+ capture.open(local, selfClosing);
122
129
  },
123
130
  onText(text) {
124
- if (capture !== undefined)
125
- buffer += text;
131
+ capture.text(text);
126
132
  },
127
133
  onClose(name) {
128
134
  const local = localName(name);
135
+ const text = capture.close(local);
129
136
  if (local === 'author') {
130
- authors.push(buffer);
131
- capture = undefined;
132
- buffer = '';
137
+ authors.push(text ?? '');
133
138
  }
134
139
  else if (local === 'text') {
135
- buffer = decodeSpreadsheetText(buffer);
136
- capture = undefined;
140
+ body = decodeSpreadsheetText(text ?? '');
137
141
  }
138
142
  else if (local === 'comment' && currentRef !== undefined) {
139
143
  const threadId = threadIdOf(authors[numInteger(currentAuthorId, 0) ?? -1]);
140
144
  comments.set(currentRef, {
141
- text: buffer,
145
+ text: body,
142
146
  ...(threadId !== undefined ? { threadId } : {}),
143
147
  });
144
148
  currentRef = undefined;
@@ -1,12 +1,15 @@
1
- import { type ConditionalFormatting } from '../../core/conditional-formatting.ts';
1
+ import { type ConditionalFormatting, type ConditionalFormattingRule } from '../../core/conditional-formatting.ts';
2
2
  import { type CollectingPass } from '../../xml/xml-read.ts';
3
3
  import type { StyleRegistry } from './styles.ts';
4
+ /** Each extended data bar's link id, keyed by rule identity: the one map both passes are handed. */
5
+ export type DataBarExtLinks = ReadonlyMap<ConditionalFormattingRule, string>;
6
+ export declare function dataBarExtLinks(formattings: readonly ConditionalFormatting[]): DataBarExtLinks;
4
7
  /**
5
8
  * Serialise every conditional formatting on a sheet into its `<conditionalFormatting>` blocks, in
6
9
  * insertion order. Returns '' when the sheet has none. A rule missing a `priority` is assigned the
7
10
  * next free one, so the output always satisfies Excel's requirement that every cfRule carry one.
8
11
  */
9
- export declare function conditionalFormattingsXml(formattings: readonly ConditionalFormatting[], styles: StyleRegistry): string;
12
+ export declare function conditionalFormattingsXml(formattings: readonly ConditionalFormatting[], styles: StyleRegistry, extLinks: DataBarExtLinks): string;
10
13
  /**
11
14
  * The worksheet `<extLst>` `<ext>` carrying the x14 data-bar extensions, or '' when no data bar needs
12
15
  * one. Each extension's `<x14:cfRule id>` is read from the same {@link dataBarExtLinks} map the classic
@@ -14,7 +17,7 @@ export declare function conditionalFormattingsXml(formattings: readonly Conditio
14
17
  * (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single `<extLst>` beside the
15
18
  * data-validation extension.
16
19
  */
17
- export declare function conditionalFormattingsExtXml(formattings: readonly ConditionalFormatting[]): string;
20
+ export declare function conditionalFormattingsExtXml(formattings: readonly ConditionalFormatting[], extLinks: DataBarExtLinks): string;
18
21
  /**
19
22
  * A pass reading a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
20
23
  * blocks supply every rule; the x14 extension (`<x14:conditionalFormatting>` inside `<extLst>`) is
@@ -1,9 +1,11 @@
1
1
  import { isCfValueObjectType, isConditionalFormattingOperator, isConditionalFormattingType, isIconSetType, isCfTimePeriod, } from '../../core/conditional-formatting.js';
2
+ import { stripFormulaEquals } from '../../core/formula.js';
3
+ import { coerceNumericLiteral, enumToken, numFinite, numInteger } from '../../xml/xml-attrs.js';
2
4
  import { elementSubtrees, TextCapture, } from '../../xml/xml-read.js';
3
- import { boolPresent, boolStrict, coerceNumericLiteral, enumToken, localName, numFinite, numInteger, } from '../../xml/xml-scan.js';
4
- import { boolAttr, checkedToken, escapeAttr, escapeText, numberText, stripFormulaEquals, textAttr, } from '../../xml/xml.js';
5
+ import { boolPresent, boolStrict, localName } from '../../xml/xml-scan.js';
6
+ import { boolAttr, checkedToken, escapeAttr, escapeText, numberText, textAttr, } from '../../xml/xml.js';
5
7
  import { colorAttrs, parseColor } from './color-xml.js';
6
- import { CF_EXT_URI, DATABAR_LINK_EXT_URI, XM_NS } from './namespaces.js';
8
+ import { CF_EXT_URI, DATABAR_LINK_EXT_URI, isExtensionElement, XM_NS } from './namespaces.js';
7
9
  import { x14Ext } from './x14-ext.js';
8
10
  const DEFAULT_DATABAR_CFVO = [{ type: 'min' }, { type: 'max' }];
9
11
  const DEFAULT_DATABAR_COLOR = { argb: 'FF638EC6' };
@@ -15,7 +17,7 @@ function needsDataBarExt(rule) {
15
17
  function dataBarExtGuid(index) {
16
18
  return `{00000000-0000-0000-0000-${String(index + 1).padStart(12, '0')}}`;
17
19
  }
18
- function dataBarExtLinks(formattings) {
20
+ export function dataBarExtLinks(formattings) {
19
21
  const links = new Map();
20
22
  let index = 0;
21
23
  for (const cf of formattings) {
@@ -28,15 +30,13 @@ function dataBarExtLinks(formattings) {
28
30
  }
29
31
  const SCALE_KINDS = ['dataBar', 'colorScale', 'iconSet'];
30
32
  const SCALE_TYPES = new Set(SCALE_KINDS);
31
- export function conditionalFormattingsXml(formattings, styles) {
33
+ export function conditionalFormattingsXml(formattings, styles, extLinks) {
32
34
  if (formattings.length === 0)
33
35
  return '';
34
36
  const priority = { next: 1 };
35
- const extLinks = dataBarExtLinks(formattings);
36
37
  return formattings.map((cf) => blockXml(cf, styles, priority, extLinks)).join('');
37
38
  }
38
- export function conditionalFormattingsExtXml(formattings) {
39
- const extLinks = dataBarExtLinks(formattings);
39
+ export function conditionalFormattingsExtXml(formattings, extLinks) {
40
40
  const items = [];
41
41
  for (const cf of formattings) {
42
42
  for (const rule of cf.rules) {
@@ -50,8 +50,7 @@ export function conditionalFormattingsExtXml(formattings) {
50
50
  return x14Ext(CF_EXT_URI, `<x14:conditionalFormattings>${items.join('')}</x14:conditionalFormattings>`);
51
51
  }
52
52
  function x14DataBarXml(ref, rule, guid) {
53
- const cfvo = rule.cfvo && rule.cfvo.length > 0 ? rule.cfvo : DEFAULT_DATABAR_CFVO;
54
- const anchors = cfvo.map(x14CfvoXml).join('');
53
+ const anchors = dataBarAnchors(rule).map(x14Cfvo).join('');
55
54
  const gradient = boolAttr('gradient', rule.gradient);
56
55
  const negative = rule.negativeFillColor !== undefined
57
56
  ? `<x14:negativeFillColor ${colorAttrs(rule.negativeFillColor)}/>`
@@ -62,12 +61,23 @@ function x14DataBarXml(ref, rule, guid) {
62
61
  `<x14:dataBar${gradient}>${anchors}${negative}${axis}</x14:dataBar>` +
63
62
  `</x14:cfRule><xm:sqref>${escapeText(ref)}</xm:sqref></x14:conditionalFormatting>`);
64
63
  }
65
- function x14CfvoXml(cfvo) {
66
- const type = checkedToken(cfvo.type, isCfValueObjectType, 'conditional format value type');
67
- if (cfvo.value === undefined)
68
- return `<x14:cfvo type="${type}"/>`;
69
- return `<x14:cfvo type="${type}"><xm:f>${escapeText(String(cfvo.value))}</xm:f></x14:cfvo>`;
64
+ function dataBarAnchors(rule) {
65
+ return rule.cfvo && rule.cfvo.length > 0 ? rule.cfvo : DEFAULT_DATABAR_CFVO;
70
66
  }
67
+ function cfvoWriter(form) {
68
+ return (cfvo) => {
69
+ const type = checkedToken(cfvo.type, isCfValueObjectType, 'conditional format value type');
70
+ const tag = form === 'classic' ? 'cfvo' : 'x14:cfvo';
71
+ if (cfvo.value === undefined)
72
+ return `<${tag} type="${type}"/>`;
73
+ const value = String(cfvo.value);
74
+ return form === 'classic'
75
+ ? `<${tag} type="${type}"${textAttr('val', value)}/>`
76
+ : `<${tag} type="${type}"><xm:f>${escapeText(value)}</xm:f></${tag}>`;
77
+ };
78
+ }
79
+ const cfvoXml = cfvoWriter('classic');
80
+ const x14Cfvo = cfvoWriter('x14');
71
81
  function cfRuleExtLinkXml(guid) {
72
82
  return `<extLst>${x14Ext(DATABAR_LINK_EXT_URI, `<x14:id>${guid}</x14:id>`)}</extLst>`;
73
83
  }
@@ -140,9 +150,8 @@ function scaleXml(rule) {
140
150
  return iconSetXml(rule);
141
151
  }
142
152
  function dataBarXml(rule) {
143
- const cfvo = rule.cfvo && rule.cfvo.length > 0 ? rule.cfvo : DEFAULT_DATABAR_CFVO;
144
153
  const color = rule.color ?? DEFAULT_DATABAR_COLOR;
145
- const anchors = cfvo.map(cfvoXml).join('');
154
+ const anchors = dataBarAnchors(rule).map(cfvoXml).join('');
146
155
  return `<dataBar>${anchors}<color ${colorAttrs(color)}/></dataBar>`;
147
156
  }
148
157
  function colorScaleXml(rule) {
@@ -157,11 +166,6 @@ function iconSetXml(rule) {
157
166
  const anchors = (rule.cfvo ?? []).map(cfvoXml).join('');
158
167
  return `<iconSet${name}>${anchors}</iconSet>`;
159
168
  }
160
- function cfvoXml(cfvo) {
161
- const type = checkedToken(cfvo.type, isCfValueObjectType, 'conditional format value type');
162
- const val = cfvo.value === undefined ? '' : textAttr('val', String(cfvo.value));
163
- return `<cfvo type="${type}"${val}/>`;
164
- }
165
169
  export function conditionalFormattingPass() {
166
170
  const blocks = [];
167
171
  let block;
@@ -174,9 +178,9 @@ export function conditionalFormattingPass() {
174
178
  let x14ExtId;
175
179
  const x14IdCapture = new TextCapture('id');
176
180
  const handlers = {
177
- onOpen(name, attrs, selfClosing) {
181
+ onOpen(name, attrs, selfClosing, scope) {
178
182
  const ln = localName(name);
179
- if (name.includes(':')) {
183
+ if (isExtensionElement(scope, name)) {
180
184
  if (ln === 'id' && draft !== undefined) {
181
185
  x14IdCapture.open(ln, selfClosing);
182
186
  }
@@ -233,9 +237,9 @@ export function conditionalFormattingPass() {
233
237
  formulaCapture.text(chunk);
234
238
  x14IdCapture.text(chunk);
235
239
  },
236
- onClose(name) {
240
+ onClose(name, scope) {
237
241
  const ln = localName(name);
238
- if (name.includes(':')) {
242
+ if (isExtensionElement(scope, name)) {
239
243
  const id = x14IdCapture.close(ln);
240
244
  if (id !== undefined) {
241
245
  if (draft !== undefined)