@shbernal/ts-xlsx 3.1.0 → 3.2.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 (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -0,0 +1,20 @@
1
+ import type { AssertNever } from '../../core/internal.ts';
2
+ import { type SheetProtection, type SheetProtectionFlags } from '../../core/protection.ts';
3
+ import type { RecordReader } from './primitives.ts';
4
+ declare const FLAGS_IN_RECORD_ORDER: readonly ["objects", "scenarios", "formatCells", "formatColumns", "formatRows", "insertColumns", "insertRows", "insertHyperlinks", "deleteColumns", "deleteRows", "selectLockedCells", "sort", "autoFilter", "pivotTables", "selectUnlockedCells"];
5
+ /**
6
+ * The half of the proof an ordered list owes: `satisfies` covers "no invented flag", this covers "no
7
+ * omission". A flag left out here would be read at the wrong offset along with every flag after it.
8
+ */
9
+ export type EveryProtectionFlagHasARecordSlot = AssertNever<Exclude<keyof SheetProtectionFlags, (typeof FLAGS_IN_RECORD_ORDER)[number]>>;
10
+ /** What a worksheet's protection records say, gathered as they arrive. */
11
+ export declare class SheetProtectionRecords {
12
+ #private;
13
+ /** Read a `BrtSheetProtection`. */
14
+ legacy(reader: RecordReader): void;
15
+ /** Read a `BrtSheetProtectionIso`. */
16
+ iso(reader: RecordReader): void;
17
+ /** The protection the records described, or `undefined` for a sheet that is not protected. */
18
+ result(): SheetProtection | undefined;
19
+ }
20
+ export {};
@@ -0,0 +1,60 @@
1
+ import { toBase64 } from '../../bytes.js';
2
+ import { SHEET_PROTECTION_FLAGS, } from '../../core/protection.js';
3
+ import { hex } from '../../hex.js';
4
+ const FLAGS_IN_RECORD_ORDER = [
5
+ 'objects',
6
+ 'scenarios',
7
+ 'formatCells',
8
+ 'formatColumns',
9
+ 'formatRows',
10
+ 'insertColumns',
11
+ 'insertRows',
12
+ 'insertHyperlinks',
13
+ 'deleteColumns',
14
+ 'deleteRows',
15
+ 'selectLockedCells',
16
+ 'sort',
17
+ 'autoFilter',
18
+ 'pivotTables',
19
+ 'selectUnlockedCells',
20
+ ];
21
+ const ALLOWED_BY_DEFAULT = new Map(SHEET_PROTECTION_FLAGS.map(({ key, defaultForbidden }) => [key, !defaultForbidden]));
22
+ export class SheetProtectionRecords {
23
+ #locked = false;
24
+ #flags = {};
25
+ #legacyPasswordHash;
26
+ #credential;
27
+ legacy(reader) {
28
+ const protpwd = reader.u16();
29
+ this.#readFlags(reader);
30
+ this.#legacyPasswordHash = protpwd === 0 ? undefined : hex(protpwd, 4);
31
+ }
32
+ iso(reader) {
33
+ const spinCount = reader.u32();
34
+ this.#readFlags(reader);
35
+ const hashValue = toBase64(reader.bytes(reader.u32()));
36
+ const saltValue = toBase64(reader.bytes(reader.u32()));
37
+ this.#credential = { algorithmName: reader.wideString(), hashValue, saltValue, spinCount };
38
+ }
39
+ result() {
40
+ if (!this.#locked)
41
+ return undefined;
42
+ return {
43
+ flags: this.#flags,
44
+ ...(this.#credential === undefined ? {} : { credential: this.#credential }),
45
+ ...(this.#legacyPasswordHash === undefined
46
+ ? {}
47
+ : { legacyPasswordHash: this.#legacyPasswordHash }),
48
+ };
49
+ }
50
+ #readFlags(reader) {
51
+ this.#locked = reader.u32() !== 0;
52
+ const flags = {};
53
+ for (const key of FLAGS_IN_RECORD_ORDER) {
54
+ const allowed = reader.u32() !== 0;
55
+ if (allowed !== ALLOWED_BY_DEFAULT.get(key))
56
+ flags[key] = allowed;
57
+ }
58
+ this.#flags = flags;
59
+ }
60
+ }
@@ -2,13 +2,23 @@ import type { DateEpoch } from '../../core/date.ts';
2
2
  import type { CellValue } from '../../core/value.ts';
3
3
  import type { Worksheet } from '../../core/worksheet.ts';
4
4
  import { type XmlAttributes } from '../../xml/xml-scan.ts';
5
+ import type { CellMetadataIndex } from '../cell-metadata/metadata.ts';
5
6
  import { type XfStyle } from '../style/xf-style.ts';
6
7
  import { type SharedString } from './cell-value.ts';
8
+ /**
9
+ * The worksheet-body elements both readers expand from `<x/>` into an open and a close, so each
10
+ * commits from its close alone. A formatted-but-empty `<c/>` finalises once there, and a self-closing
11
+ * `<row/>` closes its row, where the streamer yields it and both readers end its style and its cells:
12
+ * a producer may write `<row r="2" hidden="1"/>`, and with no close the streamer lost the row and its
13
+ * `hidden` flag. The text-bearing `<f/>`/`<v/>`/`<t/>` stay out, so an empty one never commits text.
14
+ */
15
+ export declare const WORKSHEET_BODY_EMPTY_CLOSES: ReadonlySet<string>;
7
16
  export declare class CellAccumulator {
8
17
  #private;
9
18
  constructor(options: {
10
- readonly richRuns: boolean;
11
19
  readonly dateEpoch: DateEpoch;
20
+ readonly definedNames: ReadonlySet<string>;
21
+ readonly cellMetadata: CellMetadataIndex;
12
22
  });
13
23
  /** This cell's `<c r>` address (`"B3"`), or '' when it carried none. */
14
24
  get ref(): string;
@@ -18,20 +28,19 @@ export declare class CellAccumulator {
18
28
  get col(): number;
19
29
  /**
20
30
  * 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.
31
+ * its first column. A row past the grid opens no row, so none of its cells is placed, not even one
32
+ * whose own `r` names an in-grid cell. Both readers call this where they already tell the style
33
+ * resolver a row opened.
22
34
  */
23
- openRow(number: number): void;
24
- /**
25
- * The text gathered since the current element opened. A caller reads it for the elements it
26
- * captures itself (see {@link capture}); the machine reads it for its own.
27
- */
28
- get capturedText(): string;
29
- /** Gather the current element's text for the caller's own use, the way `<v>` and `<t>` do. */
30
- capture(): void;
35
+ openRow(number: number, inGrid: boolean): void;
36
+ /** Whether an in-grid `<row>` is open. */
37
+ get rowOpen(): boolean;
38
+ /** Close the `<row>`: a `<c>` before the next one belongs to no row and is not placed. */
39
+ closeRow(): void;
31
40
  /**
32
41
  * Drive one element open, and return whether it was one of the cell machine's own. Every open
33
42
  * resets the capture state first, which is true of both readers and of every element, not just
34
- * these; a caller that captures its own text calls {@link capture} after this returns.
43
+ * these. A caller gathering text of its own uses a `TextCapture`, not this machine's buffer.
35
44
  */
36
45
  openElement(local: string, attrs: XmlAttributes, selfClosing: boolean): boolean;
37
46
  /** Feed one run of character data. Ignored unless something is capturing. */
@@ -44,6 +53,5 @@ export declare class CellAccumulator {
44
53
  */
45
54
  closeElement(local: string): 'cell' | 'claimed' | 'other';
46
55
  finalize(sheet: Worksheet, sharedStrings: readonly SharedString[], style: XfStyle | undefined): void;
47
- private cachedResult;
48
56
  decode(sharedStrings: readonly SharedString[], style: XfStyle | undefined): CellValue;
49
57
  }
@@ -1,22 +1,28 @@
1
- import { encodeAddress, MAX_COLUMN, tryDecodeCellRef } from '../../core/address.js';
2
- import { translateFormula, unmangleFunctions } from '../../core/formula.js';
1
+ import { encodeAddress, encodeRange, MAX_COLUMN, tryDecodeAnchoredRange, tryDecodeCellRef, } from '../../core/address.js';
2
+ import { translateFormula } from '../../core/formula-references.js';
3
+ import { unmangleFunctions } from '../../core/formula.js';
3
4
  import { numInteger } from '../../xml/xml-attrs.js';
4
5
  import { boolStrict } from '../../xml/xml-scan.js';
5
6
  import { applyXfToCell } from '../style/xf-style.js';
6
7
  import { decodeCellContent, decodeFormulaResult, } from './cell-value.js';
7
8
  import { RunAccumulator } from './read-rich-runs.js';
9
+ export const WORKSHEET_BODY_EMPTY_CLOSES = new Set(['c', 'row']);
8
10
  export class CellAccumulator {
9
11
  #ref = '';
10
12
  #type = '';
11
13
  #style = -1;
12
14
  #col = -1;
13
15
  #row = -1;
16
+ #cellMetadataBlock = -1;
17
+ #valueMetadataBlock = -1;
18
+ #rowOpen = false;
14
19
  #nextCol = 1;
15
20
  #formula = '';
16
21
  #formulaShared = false;
17
22
  #formulaSi = -1;
18
23
  #sharedClone = false;
19
24
  #dataTable = null;
25
+ #arrayRef = undefined;
20
26
  #valueText = '';
21
27
  #hasFormula = false;
22
28
  #hasValue = false;
@@ -25,9 +31,13 @@ export class CellAccumulator {
25
31
  #capture = false;
26
32
  #text = '';
27
33
  #dateEpoch;
34
+ #definedNames;
35
+ #cellMetadata;
28
36
  constructor(options) {
29
- this.#runs = new RunAccumulator({ container: 'is', readRuns: options.richRuns });
37
+ this.#runs = new RunAccumulator({ container: 'is', readRuns: true });
30
38
  this.#dateEpoch = options.dateEpoch;
39
+ this.#definedNames = options.definedNames;
40
+ this.#cellMetadata = options.cellMetadata;
31
41
  }
32
42
  get ref() {
33
43
  return this.#ref;
@@ -38,13 +48,23 @@ export class CellAccumulator {
38
48
  get col() {
39
49
  return this.#col;
40
50
  }
41
- openRow(number) {
42
- this.#row = number;
51
+ openRow(number, inGrid) {
52
+ this.#rowOpen = inGrid;
53
+ this.#row = inGrid ? number : -1;
43
54
  this.#nextCol = 1;
44
55
  }
56
+ get rowOpen() {
57
+ return this.#rowOpen;
58
+ }
59
+ closeRow() {
60
+ this.#rowOpen = false;
61
+ this.#row = -1;
62
+ }
45
63
  #beginCell(attrs) {
46
64
  this.#type = attrs.t ?? '';
47
65
  this.#style = numInteger(attrs.s, 0) ?? -1;
66
+ this.#cellMetadataBlock = numInteger(attrs.cm, 1) ?? -1;
67
+ this.#valueMetadataBlock = numInteger(attrs.vm, 1) ?? -1;
48
68
  this.#placeCell(attrs.r);
49
69
  this.#formula = '';
50
70
  this.#valueText = '';
@@ -55,15 +75,16 @@ export class CellAccumulator {
55
75
  this.#formulaSi = -1;
56
76
  this.#sharedClone = false;
57
77
  this.#dataTable = null;
78
+ this.#arrayRef = undefined;
58
79
  }
59
80
  #placeCell(ref) {
60
- const decoded = ref === undefined ? undefined : tryDecodeCellRef(ref);
81
+ const decoded = this.#rowOpen && ref !== undefined ? tryDecodeCellRef(ref) : undefined;
61
82
  if (decoded !== undefined) {
62
83
  this.#ref = ref ?? '';
63
84
  this.#col = decoded.col;
64
85
  this.#row = decoded.row;
65
86
  }
66
- else if (ref === undefined && this.#row > 0 && this.#nextCol <= MAX_COLUMN) {
87
+ else if (ref === undefined && this.#rowOpen && this.#nextCol <= MAX_COLUMN) {
67
88
  this.#col = this.#nextCol;
68
89
  this.#ref = encodeAddress(this.#col, this.#row);
69
90
  }
@@ -86,8 +107,15 @@ export class CellAccumulator {
86
107
  dtr: attrs.dtr,
87
108
  r1: attrs.r1,
88
109
  r2: attrs.r2,
110
+ del1: attrs.del1,
111
+ del2: attrs.del2,
89
112
  };
90
113
  }
114
+ if (attrs.t === 'array' && attrs.ref !== undefined && this.#col > 0) {
115
+ const range = tryDecodeAnchoredRange(attrs.ref, this.#col, this.#row);
116
+ if (range !== undefined)
117
+ this.#arrayRef = encodeRange(range);
118
+ }
91
119
  }
92
120
  #setFormula(text) {
93
121
  this.#formula = text;
@@ -97,12 +125,6 @@ export class CellAccumulator {
97
125
  this.#valueText = text;
98
126
  this.#hasValue = true;
99
127
  }
100
- get capturedText() {
101
- return this.#text;
102
- }
103
- capture() {
104
- this.#capture = true;
105
- }
106
128
  openElement(local, attrs, selfClosing) {
107
129
  this.#text = '';
108
130
  this.#capture = false;
@@ -118,6 +140,8 @@ export class CellAccumulator {
118
140
  return true;
119
141
  case 'v':
120
142
  this.#capture = !selfClosing;
143
+ if (selfClosing)
144
+ this.#setValue('');
121
145
  return true;
122
146
  default:
123
147
  return false;
@@ -166,7 +190,9 @@ export class CellAccumulator {
166
190
  ...(boolStrict(this.#dataTable.dtr) ? { dataTableRow: true } : {}),
167
191
  ...(this.#dataTable.r1 !== undefined ? { r1: this.#dataTable.r1 } : {}),
168
192
  ...(this.#dataTable.r2 !== undefined ? { r2: this.#dataTable.r2 } : {}),
169
- ...this.cachedResult(style),
193
+ ...(boolStrict(this.#dataTable.del1) ? { r1Deleted: true } : {}),
194
+ ...(boolStrict(this.#dataTable.del2) ? { r2Deleted: true } : {}),
195
+ ...this.#cachedResult(style),
170
196
  };
171
197
  }
172
198
  if (this.#hasFormula && this.#formulaShared && this.#formulaSi >= 0) {
@@ -178,17 +204,17 @@ export class CellAccumulator {
178
204
  const translated = translateFormula(master.formula, this.#col - master.col, this.#row - master.row);
179
205
  return {
180
206
  sharedFormula: encodeAddress(master.col, master.row),
181
- formula: unmangleFunctions(translated),
182
- ...this.cachedResult(style),
207
+ formula: unmangleFunctions(translated, this.#definedNames),
208
+ ...this.#cachedResult(style),
183
209
  };
184
210
  }
185
211
  }
186
212
  return this.decode(sharedStrings, style);
187
213
  }
188
- cachedResult(style) {
214
+ #cachedResult(style) {
189
215
  if (!this.#hasValue)
190
216
  return {};
191
- const result = decodeFormulaResult(this.#type, this.#valueText, style?.numFmt, this.#dateEpoch);
217
+ const result = decodeFormulaResult(this.#type, this.#valueText, style?.numFmt, this.#dateEpoch, this.#cellMetadata.valueErrors.get(this.#valueMetadataBlock));
192
218
  return result === undefined ? {} : { result };
193
219
  }
194
220
  decode(sharedStrings, style) {
@@ -196,11 +222,15 @@ export class CellAccumulator {
196
222
  type: this.#type,
197
223
  hasFormula: this.#hasFormula,
198
224
  formula: this.#formula,
225
+ arrayRef: this.#arrayRef,
226
+ dynamicArray: this.#cellMetadata.dynamicArrayCells.has(this.#cellMetadataBlock),
227
+ valueError: this.#cellMetadata.valueErrors.get(this.#valueMetadataBlock),
199
228
  hasValue: this.#hasValue,
200
229
  valueText: this.#valueText,
230
+ hasInlineString: this.#runs.opened,
201
231
  inlineText: this.#runs.plainText,
202
232
  richTextRuns: this.#runs.runs,
203
233
  };
204
- return decodeCellContent(raw, sharedStrings, style?.numFmt, this.#dateEpoch);
234
+ return decodeCellContent(raw, sharedStrings, style?.numFmt, this.#dateEpoch, this.#definedNames);
205
235
  }
206
236
  }
@@ -0,0 +1,39 @@
1
+ import type { ErrorCode } from '../../core/value.ts';
2
+ import { type CellMetadataIndex, type WorkbookMetadata } from '../cell-metadata/metadata.ts';
3
+ import type { PartRelationships } from '../opc/read-opc.ts';
4
+ /**
5
+ * What every `cm` and `vm` in the workbook resolves to: the metadata part and the two rich-value parts,
6
+ * each found through the workbook's relationships by type. Both worksheet readers resolve their cells
7
+ * through this one answer.
8
+ */
9
+ export declare function readCellMetadata(workbookRels: PartRelationships): CellMetadataIndex;
10
+ /** Read `xl/metadata.xml` as far as a cell's `cm` and `vm` reach into it. */
11
+ export declare function parseMetadataPart(xml: string): WorkbookMetadata;
12
+ /**
13
+ * Records what the cells written so far point into, so the metadata part and the rich-value parts are
14
+ * emitted only for a workbook that needs them. Filled during the sheet pass, as the shared-strings table
15
+ * is, because the streaming writer renders a row long before the package around it is assembled.
16
+ */
17
+ export declare class WorkbookMetadataTable {
18
+ #private;
19
+ /** The `cm` a dynamic-array formula's cell is written with. Every such cell shares the one block. */
20
+ markDynamicArray(): number;
21
+ /**
22
+ * The `vm` a cell holding `error` is written with, beside a `<v>` of `#VALUE!`, or `undefined` for
23
+ * an error Excel reads literally, which needs no metadata.
24
+ */
25
+ markRichValueError(error: ErrorCode): number | undefined;
26
+ /** Whether no cell points into the metadata part; the writer omits it entirely when so. */
27
+ get isEmpty(): boolean;
28
+ /** Whether a cell points at a rich value, so the rich-value parts are emitted beside the metadata. */
29
+ get hasRichValues(): boolean;
30
+ /** Serialise the `xl/metadata.xml` part, in the form Excel saves it. */
31
+ toXml(): string;
32
+ /**
33
+ * Serialise `xl/richData/rdrichvalue.xml`: one rich value per error, in the order the `vm`s were handed
34
+ * out, each naming its `errorType` and the unknown sub-type, 0.
35
+ */
36
+ richValuesXml(): string;
37
+ }
38
+ /** `xl/richData/rdrichvaluestructure.xml`: the one structure every rich value the writer emits has. */
39
+ export declare const RICH_VALUE_STRUCTURES_XML: string;
@@ -0,0 +1,162 @@
1
+ import { numInteger } from '../../xml/xml-attrs.js';
2
+ import { parseXml } from '../../xml/xml-read.js';
3
+ import { boolStrict, localName } from '../../xml/xml-scan.js';
4
+ import { XML_DECLARATION } from '../../xml/xml.js';
5
+ import { DYNAMIC_ARRAY_TYPE, indexCellMetadata, RICH_VALUE_TYPE, } from '../cell-metadata/metadata.js';
6
+ import { ERROR_STRUCTURE, parseRichValueErrors, RICH_VALUE_ERROR_TYPES, } from '../cell-metadata/rich-values.js';
7
+ import { DYNAMIC_ARRAY_NS, DYNAMIC_ARRAY_PROPERTIES_EXT_URI, RICH_DATA_NS, RICH_VALUE_BLOCK_EXT_URI, SPREADSHEETML_NS, } from './namespaces.js';
8
+ export function readCellMetadata(workbookRels) {
9
+ return indexCellMetadata(parseMetadataPart(workbookRels.relatedText('sheetMetadata') ?? ''), parseRichValueErrors(workbookRels.relatedText('rdRichValueStructure') ?? '', workbookRels.relatedText('rdRichValue') ?? ''));
10
+ }
11
+ export function parseMetadataPart(xml) {
12
+ const typeNames = [];
13
+ const dynamicArrayBlocks = [];
14
+ const richValueBlocks = [];
15
+ const cellBlocks = [];
16
+ const valueBlocks = [];
17
+ let container;
18
+ const records = () => container === 'cells'
19
+ ? cellBlocks.at(-1)
20
+ : container === 'values'
21
+ ? valueBlocks.at(-1)
22
+ : undefined;
23
+ if (xml === '')
24
+ return { typeNames, dynamicArrayBlocks, richValueBlocks, cellBlocks, valueBlocks };
25
+ parseXml(xml, {
26
+ onOpen(name, attrs, selfClosing) {
27
+ switch (localName(name)) {
28
+ case 'metadataType':
29
+ typeNames.push(attrs.name ?? '');
30
+ break;
31
+ case 'futureMetadata':
32
+ container = selfClosing
33
+ ? undefined
34
+ : attrs.name === DYNAMIC_ARRAY_TYPE
35
+ ? 'dynamicArrays'
36
+ : attrs.name === RICH_VALUE_TYPE
37
+ ? 'richValues'
38
+ : undefined;
39
+ break;
40
+ case 'cellMetadata':
41
+ container = selfClosing ? undefined : 'cells';
42
+ break;
43
+ case 'valueMetadata':
44
+ container = selfClosing ? undefined : 'values';
45
+ break;
46
+ case 'bk':
47
+ if (container === 'dynamicArrays')
48
+ dynamicArrayBlocks.push(false);
49
+ else if (container === 'richValues')
50
+ richValueBlocks.push(-1);
51
+ else if (container === 'cells')
52
+ cellBlocks.push([]);
53
+ else if (container === 'values')
54
+ valueBlocks.push([]);
55
+ break;
56
+ case 'dynamicArrayProperties':
57
+ if (container === 'dynamicArrays' && dynamicArrayBlocks.length > 0) {
58
+ dynamicArrayBlocks[dynamicArrayBlocks.length - 1] = boolStrict(attrs.fDynamic);
59
+ }
60
+ break;
61
+ case 'rvb':
62
+ if (container === 'richValues' && richValueBlocks.length > 0) {
63
+ richValueBlocks[richValueBlocks.length - 1] = numInteger(attrs.i, 0) ?? -1;
64
+ }
65
+ break;
66
+ case 'rc': {
67
+ const block = records();
68
+ const type = numInteger(attrs.t, 1);
69
+ const value = numInteger(attrs.v, 0);
70
+ if (block !== undefined && type !== undefined && value !== undefined) {
71
+ block.push({ type, value });
72
+ }
73
+ break;
74
+ }
75
+ default:
76
+ break;
77
+ }
78
+ },
79
+ onClose(name) {
80
+ const local = localName(name);
81
+ if (local === 'futureMetadata' || local === 'cellMetadata' || local === 'valueMetadata') {
82
+ container = undefined;
83
+ }
84
+ },
85
+ });
86
+ return { typeNames, dynamicArrayBlocks, richValueBlocks, cellBlocks, valueBlocks };
87
+ }
88
+ const METADATA_TYPE_FLAGS = 'minSupportedVersion="120000" copy="1" pasteAll="1" pasteValues="1" merge="1" splitFirst="1" ' +
89
+ 'rowColShift="1" clearFormats="1" clearComments="1" assign="1" coerce="1"';
90
+ export class WorkbookMetadataTable {
91
+ #dynamicArrays = false;
92
+ #errors = new Map();
93
+ markDynamicArray() {
94
+ this.#dynamicArrays = true;
95
+ return 1;
96
+ }
97
+ markRichValueError(error) {
98
+ if (!RICH_VALUE_ERROR_TYPES.has(error))
99
+ return undefined;
100
+ const known = this.#errors.get(error);
101
+ if (known !== undefined)
102
+ return known;
103
+ const vm = this.#errors.size + 1;
104
+ this.#errors.set(error, vm);
105
+ return vm;
106
+ }
107
+ get isEmpty() {
108
+ return !this.#dynamicArrays && this.#errors.size === 0;
109
+ }
110
+ get hasRichValues() {
111
+ return this.#errors.size > 0;
112
+ }
113
+ toXml() {
114
+ const dynamic = this.#dynamicArrays;
115
+ const errorCount = this.#errors.size;
116
+ const types = [
117
+ ...(dynamic
118
+ ? [`<metadataType name="${DYNAMIC_ARRAY_TYPE}" ${METADATA_TYPE_FLAGS} cellMeta="1"/>`]
119
+ : []),
120
+ ...(errorCount > 0
121
+ ? [`<metadataType name="${RICH_VALUE_TYPE}" ${METADATA_TYPE_FLAGS}/>`]
122
+ : []),
123
+ ];
124
+ const richValueType = types.length;
125
+ const indices = Array.from({ length: errorCount }, (_, index) => index);
126
+ return (XML_DECLARATION +
127
+ `<metadata xmlns="${SPREADSHEETML_NS}"` +
128
+ (errorCount > 0 ? ` xmlns:xlrd="${RICH_DATA_NS}"` : '') +
129
+ (dynamic ? ` xmlns:xda="${DYNAMIC_ARRAY_NS}"` : '') +
130
+ '>' +
131
+ `<metadataTypes count="${types.length}">${types.join('')}</metadataTypes>` +
132
+ (dynamic
133
+ ? `<futureMetadata name="${DYNAMIC_ARRAY_TYPE}" count="1"><bk><extLst>` +
134
+ `<ext uri="${DYNAMIC_ARRAY_PROPERTIES_EXT_URI}">` +
135
+ '<xda:dynamicArrayProperties fDynamic="1" fCollapsed="0"/>' +
136
+ '</ext></extLst></bk></futureMetadata>'
137
+ : '') +
138
+ (errorCount > 0
139
+ ? `<futureMetadata name="${RICH_VALUE_TYPE}" count="${errorCount}">` +
140
+ indices
141
+ .map((index) => `<bk><extLst><ext uri="${RICH_VALUE_BLOCK_EXT_URI}"><xlrd:rvb i="${index}"/></ext></extLst></bk>`)
142
+ .join('') +
143
+ '</futureMetadata>'
144
+ : '') +
145
+ (dynamic ? '<cellMetadata count="1"><bk><rc t="1" v="0"/></bk></cellMetadata>' : '') +
146
+ (errorCount > 0
147
+ ? `<valueMetadata count="${errorCount}">` +
148
+ indices.map((index) => `<bk><rc t="${richValueType}" v="${index}"/></bk>`).join('') +
149
+ '</valueMetadata>'
150
+ : '') +
151
+ '</metadata>');
152
+ }
153
+ richValuesXml() {
154
+ const values = [...this.#errors.keys()].map((error) => `<rv s="0"><v>${RICH_VALUE_ERROR_TYPES.get(error)}</v><v>0</v></rv>`);
155
+ return (XML_DECLARATION +
156
+ `<rvData xmlns="${RICH_DATA_NS}" count="${values.length}">${values.join('')}</rvData>`);
157
+ }
158
+ }
159
+ export const RICH_VALUE_STRUCTURES_XML = XML_DECLARATION +
160
+ `<rvStructures xmlns="${RICH_DATA_NS}" count="1">` +
161
+ `<s t="${ERROR_STRUCTURE}"><k n="errorType" t="i"/><k n="subType" t="i"/></s>` +
162
+ '</rvStructures>';
@@ -1,5 +1,5 @@
1
1
  import { type DateEpoch } from '../../core/date.ts';
2
- import { type CellValue, type FormulaResult, type RichTextRun, type RichTextValue } from '../../core/value.ts';
2
+ import { type CellValue, type ErrorCode, type FormulaResult, type RichTextRun, type RichTextValue } from '../../core/value.ts';
3
3
  /**
4
4
  * One entry of the shared-strings pool. A `<si>` built from a bare `<t>` is a plain string; a `<si>`
5
5
  * built from `<r>` runs is rich text, so a `t="s"` cell can resolve to either kind, and rich text
@@ -12,8 +12,17 @@ export interface RawCell {
12
12
  readonly type: string;
13
13
  readonly hasFormula: boolean;
14
14
  readonly formula: string;
15
+ /** The range an `<f t="array">` fills when it starts at this cell, as Excel spells a `ref`. */
16
+ readonly arrayRef?: string | undefined;
17
+ /** Whether the cell's `cm` points at cell metadata marking a dynamic array. */
18
+ readonly dynamicArray?: boolean;
19
+ /** The error the cell's `vm` names through a rich value, which is the error a `t="e"` cell holds. */
20
+ readonly valueError?: ErrorCode | undefined;
21
+ /** Whether a `<v>` was present, `<v/>` included. */
15
22
  readonly hasValue: boolean;
16
23
  readonly valueText: string;
24
+ /** Whether an `<is>` was present, `<is/>` included. */
25
+ readonly hasInlineString: boolean;
17
26
  readonly inlineText: string;
18
27
  /** The formatted runs of a rich inline string, when the `<is>` held `<r>` elements rather than a
19
28
  * bare `<t>`. Absent (or empty) for a plain inline string, which decodes to `inlineText`. */
@@ -21,14 +30,17 @@ export interface RawCell {
21
30
  }
22
31
  /**
23
32
  * Decode a gathered cell into its model value. A formula cell becomes a `{formula, result?}`
24
- * object (the on-disk `_xlfn.`/`_xlpm.` mangling stripped back to the readable name); a plain
33
+ * object (the on-disk `_xlfn.`/`_xlpm.` mangling stripped back to the readable name), or an array
34
+ * formula over its range, dynamic when the cell's metadata says so; a plain
25
35
  * numeric cell under a date number format becomes a {@link Date}; everything else decodes by its
26
36
  * `t` type. `numFmt` is the cell's resolved number-format code, used only for date detection, and
27
37
  * `epoch` the workbook's date system, which is what a serial under such a format counts from.
38
+ * `definedNames` is the workbook's defined names as `definedNameKeys` spells them, which decide whether
39
+ * a function passed as a value sheds its `_xleta.`.
28
40
  */
29
- export declare function decodeCellContent(raw: RawCell, sharedStrings: readonly SharedString[], numFmt: string | undefined, epoch: DateEpoch): CellValue;
41
+ export declare function decodeCellContent(raw: RawCell, sharedStrings: readonly SharedString[], numFmt: string | undefined, epoch: DateEpoch, definedNames: ReadonlySet<string>): CellValue;
30
42
  /** Decode a formula's cached `<v>` result by its `t` type, coercing a numeric result under a date
31
43
  * `numFmt` to a {@link Date} exactly as a bare numeric cell is, so a date-valued formula result
32
44
  * (e.g. `TODAY()`) reads back as a Date, not a serial. Shared by the buffered reader's shared-formula
33
45
  * clone resolution, which caches a result the same way a plain formula cell does. */
34
- export declare function decodeFormulaResult(type: string, valueText: string, numFmt: string | undefined, epoch: DateEpoch): FormulaResult | undefined;
46
+ export declare function decodeFormulaResult(type: string, valueText: string, numFmt: string | undefined, epoch: DateEpoch, valueError?: ErrorCode): FormulaResult | undefined;
@@ -2,55 +2,72 @@ import { coerceDateSerial, parseDateText } from '../../core/date.js';
2
2
  import { unmangleFunctions } from '../../core/formula.js';
3
3
  import { isErrorCode, } from '../../core/value.js';
4
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) {
5
+ import { boolTristate } from '../../xml/xml-scan.js';
6
+ export function decodeCellContent(raw, sharedStrings, numFmt, epoch, definedNames) {
7
7
  if (raw.hasFormula) {
8
- const stored = unmangleFunctions(raw.formula);
8
+ const stored = unmangleFunctions(raw.formula, definedNames);
9
9
  const result = raw.hasValue
10
- ? decodeFormulaResult(raw.type, raw.valueText, numFmt, epoch)
10
+ ? decodeFormulaResult(raw.type, raw.valueText, numFmt, epoch, raw.valueError)
11
11
  : undefined;
12
- return result === undefined ? { formula: stored } : { formula: stored, result };
12
+ const cached = result === undefined ? {} : { result };
13
+ if (raw.arrayRef !== undefined) {
14
+ return {
15
+ shareType: 'array',
16
+ formula: stored,
17
+ ref: raw.arrayRef,
18
+ ...(raw.dynamicArray === true ? { dynamic: true } : {}),
19
+ ...cached,
20
+ };
21
+ }
22
+ return { formula: stored, ...cached };
13
23
  }
14
24
  if (raw.type === 'inlineStr' && raw.richTextRuns !== undefined && raw.richTextRuns.length > 0) {
15
25
  return { richText: raw.richTextRuns };
16
26
  }
17
- const value = decodeValue(raw.type, raw.valueText, raw.inlineText, raw.hasValue, sharedStrings);
27
+ const value = decodeValue(raw, sharedStrings);
18
28
  return coerceDateSerial(value, numFmt, epoch);
19
29
  }
20
- function decodeValue(type, valueText, inlineText, hasValue, sharedStrings) {
21
- switch (type) {
30
+ function decodeValue(raw, sharedStrings) {
31
+ const { valueText } = raw;
32
+ switch (raw.type) {
22
33
  case 'inlineStr':
23
- return inlineText;
34
+ return raw.hasInlineString ? raw.inlineText : null;
24
35
  case 'str':
25
- return decodeSpreadsheetText(valueText);
36
+ return raw.hasValue ? decodeSpreadsheetText(valueText) : null;
26
37
  case 'd':
27
38
  return parseDateText(valueText);
28
39
  case 's': {
29
40
  const index = numInteger(valueText, 0);
30
- return index === undefined ? '' : (sharedStrings[index] ?? '');
41
+ return index === undefined ? null : (sharedStrings[index] ?? null);
31
42
  }
32
43
  case 'b':
33
- return boolStrict(valueText);
44
+ return boolTristate(valueText) ?? null;
34
45
  case 'e':
35
- return isErrorCode(valueText) ? { error: valueText } : valueText;
36
- default: {
37
- if (!hasValue)
46
+ if (valueText === '')
38
47
  return null;
48
+ return errorOf(valueText, raw.valueError);
49
+ default:
39
50
  return numFinite(valueText) ?? null;
40
- }
41
51
  }
42
52
  }
43
- export function decodeFormulaResult(type, valueText, numFmt, epoch) {
44
- return coerceDateSerial(decodeResult(type, valueText), numFmt, epoch);
53
+ export function decodeFormulaResult(type, valueText, numFmt, epoch, valueError) {
54
+ return coerceDateSerial(decodeResult(type, valueText, valueError), numFmt, epoch);
55
+ }
56
+ function errorOf(valueText, valueError) {
57
+ if (valueError !== undefined)
58
+ return { error: valueError };
59
+ return isErrorCode(valueText) ? { error: valueText } : valueText;
45
60
  }
46
- function decodeResult(type, valueText) {
61
+ function decodeResult(type, valueText, valueError) {
47
62
  switch (type) {
48
63
  case 'str':
49
64
  return decodeSpreadsheetText(valueText);
50
65
  case 'b':
51
- return boolStrict(valueText);
66
+ return boolTristate(valueText);
52
67
  case 'e':
53
- return isErrorCode(valueText) ? { error: valueText } : valueText;
68
+ if (valueText === '')
69
+ return undefined;
70
+ return errorOf(valueText, valueError);
54
71
  default:
55
72
  return numFinite(valueText);
56
73
  }