@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
@@ -60,13 +60,18 @@ export type PageSetupFacet = {
60
60
  readonly key: 'paperSize' | 'scale' | 'fitToWidth' | 'fitToHeight';
61
61
  /** A non-negative integer: a page count, a percentage, or a paper-size id. */
62
62
  readonly kind: 'count';
63
- } | {
64
- readonly key: 'pageOrder' | 'orientation';
63
+ } | PageSetupTokenFacet<'pageOrder', PageOrder> | PageSetupTokenFacet<'orientation', PageOrientation>;
64
+ /**
65
+ * An enumerated `<pageSetup>` attribute. One entry per key, each with a guard that narrows to that
66
+ * key's own union, so a reader holding the entry can assign what the guard accepted without a cast.
67
+ */
68
+ interface PageSetupTokenFacet<K extends keyof PageSetup, T extends string> {
69
+ readonly key: K;
65
70
  readonly kind: 'token';
66
71
  /** The enumeration guard, and what to call it in the error when a value fails it. */
67
- readonly isValid: (value: string) => boolean;
72
+ readonly isValid: (value: string) => value is T;
68
73
  readonly label: string;
69
- };
74
+ }
70
75
  /**
71
76
  * The six `<pageSetup>` attributes, declared once, in CT_PageSetup order. Both directions key off
72
77
  * this list, so an attribute written but not read (it survives a re-write and vanishes on load) or
@@ -127,17 +132,26 @@ export declare const PRINT_OPTION_FLAGS: readonly ["horizontalCentered", "vertic
127
132
  /** Compile-time proof that {@link PRINT_OPTION_FLAGS} covers every {@link PrintOptions} flag. */
128
133
  export type EveryPrintOptionFlagIsDeclared = AssertNever<Exclude<keyof PrintOptions, (typeof PRINT_OPTION_FLAGS)[number]>>;
129
134
  /**
130
- * A manual page break (`<brk>`). For a row break, `id` is the row the layout splits *before*; for a
131
- * column break it is the column. `max` bounds the break's extent across the other axis (Excel writes
132
- * the last row/column index) and `man` marks it author-set rather than automatic. The model preserves
133
- * whatever the source carried so a round-trip reproduces the break's span exactly.
135
+ * A page break (`<brk>`). It falls after line `id` and before line `id + 1`, rows for a row break and
136
+ * columns for a column break, which is how Excel counts it: a break Excel shows above row 20 is saved
137
+ * as `id="19"`. `min` and `max` bound its extent across the other axis, zero-based, and Excel writes
138
+ * only the whole axis (`max` 16383 for a row break). `man` marks an author-set break rather than one
139
+ * a producer recorded as automatic. The model keeps whatever the source carried.
140
+ *
141
+ * A row or column splice moves a break with the line after it and drops the break when that line is
142
+ * deleted, which is what Excel Desktop does: driven over COM, a manual break above row 10 moved above
143
+ * row 11 when a row was inserted at row 1, and went away when row 11 was then deleted, and a column
144
+ * break moved the same way. The extent is not moved, because the only one Excel writes is the whole
145
+ * axis, and a splice should leave that whole rather than shorten it by the lines it removed.
134
146
  */
135
147
  export interface PageBreak {
136
- /** The row (or column) the break precedes. */
148
+ /** The last row (or column) before the break. */
137
149
  readonly id: number;
138
- /** The break's far extent across the other axis, if the source declared one. */
150
+ /** The break's near extent across the other axis, zero-based, if the source declared one. */
151
+ readonly min?: number;
152
+ /** The break's far extent across the other axis, zero-based, if the source declared one. */
139
153
  readonly max?: number;
140
- /** `true` when the break is manual (author-set); Excel-authored breaks always are. */
154
+ /** Whether the break is manual. A break without it is written as a manual one: Excel stores no other kind. */
141
155
  readonly man?: boolean;
142
156
  }
143
157
  /**
@@ -176,3 +190,4 @@ export interface HeaderFooter {
176
190
  export declare const HEADER_FOOTER_ELEMENTS: readonly ["oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter"];
177
191
  /** Compile-time proof that {@link HEADER_FOOTER_ELEMENTS} covers every {@link HeaderFooter} slot. */
178
192
  export type EveryHeaderFooterElementIsDeclared = AssertNever<Exclude<keyof HeaderFooter, (typeof HEADER_FOOTER_ELEMENTS)[number]>>;
193
+ export {};
@@ -1,3 +1,5 @@
1
+ import { type SheetSplice } from './formula-references.ts';
2
+ import { INTERNAL } from './internal.ts';
1
3
  import type { Worksheet } from './worksheet.ts';
2
4
  /** The aggregation a pivot's value field applies. These are OOXML's `ST_DataConsolidateFunction`
3
5
  * names verbatim, so a metric doubles as its `<dataField subtotal="…">` value. Excel performs the
@@ -27,13 +29,20 @@ export interface ParsedPivotSource {
27
29
  readonly kind: PivotSourceKind;
28
30
  readonly sheet: string;
29
31
  readonly ref: string;
32
+ /**
33
+ * Whether {@link sheet} and {@link ref} name a range in another workbook, which the cache reaches
34
+ * through a relationship. A row or column splice of this workbook moves the source of a pivot drawing
35
+ * from one of its own sheets, as Excel does, and never one of these.
36
+ */
37
+ readonly inAnotherWorkbook: boolean;
30
38
  }
31
39
  /** The semantic model reconstructed from a loaded pivot's `pivotTableDefinition` and its
32
40
  * `pivotCacheDefinition` (see `io/xlsx/read-pivot.ts`). Field roles are indices into {@link fields};
33
41
  * {@link metric} is the aggregation the value field applies. This mirrors the authoring model's shape
34
42
  * without requiring the source sheet it was built from, so a pivot loaded from a package is
35
43
  * inspectable data rather than an opaque preserved blob. It is a read-only view: the writer emits a
36
- * loaded pivot from its preserved parts, not from this model, so exposing it never double-emits. */
44
+ * loaded pivot from its preserved parts, not from this model, so exposing it never double-emits. A
45
+ * splice of the source sheet moves the source range in both, by {@link splicePivotSource}. */
37
46
  export interface ParsedPivotTable {
38
47
  readonly name: string;
39
48
  readonly cacheId: string;
@@ -49,7 +58,8 @@ export interface ParsedPivotTable {
49
58
  readonly metric: PivotMetric;
50
59
  }
51
60
  /** How a pivot table is authored: a source sheet and the header names that drive each axis.
52
- * `rows`/`columns`/`values` name columns by their header text in the source's first row. */
61
+ * `rows`/`columns`/`values` name columns by their header text in the source's first row, ignoring
62
+ * case, as the headers themselves must be unique ignoring case. */
53
63
  export interface PivotTableOptions {
54
64
  readonly source: Worksheet;
55
65
  readonly rows: readonly string[];
@@ -68,20 +78,25 @@ export type PivotItem = {
68
78
  } | {
69
79
  readonly kind: 'blank';
70
80
  };
71
- /** The numeric summary Excel expects on a non-shared field whose every present value is a number. */
81
+ /** The range and integrality of the numbers a cache field holds, which Excel records beside them. */
72
82
  export interface PivotNumericSummary {
73
83
  readonly allInteger: boolean;
74
84
  readonly min: number;
75
85
  readonly max: number;
76
86
  }
77
87
  /** One field of the pivot cache. An axis field (row or column) carries a `sharedItems` catalogue its
78
- * records reference by index; any other field stores its values inline in the records and, when they
79
- * are all numeric, describes them with a `numeric` summary. */
88
+ * records reference by index; any other field stores its values inline in the records. Either way the
89
+ * cache describes which kinds of value the field holds, because Excel reads the catalogue against that
90
+ * description: a catalogue of numbers that does not say it holds numbers opens with the repair prompt. */
80
91
  export interface PivotCacheField {
81
92
  readonly name: string;
82
93
  readonly sharedItems: readonly PivotItem[] | null;
83
- readonly numeric: PivotNumericSummary | null;
94
+ /** Whether any value is a string. */
95
+ readonly containsString: boolean;
96
+ /** Whether any value is missing. */
84
97
  readonly containsBlank: boolean;
98
+ /** The field's numbers, summarised, or `null` when it holds none. */
99
+ readonly numeric: PivotNumericSummary | null;
85
100
  }
86
101
  /** One cell of a cache record: an index into a shared-items catalogue, or an inline value. */
87
102
  export type PivotRecordCell = {
@@ -93,14 +108,15 @@ export type PivotRecordCell = {
93
108
  * the full cache (fields + records) and the axis-field wiring the renderer needs; nothing here
94
109
  * touches XML.
95
110
  *
96
- * Supported shape: exactly one value field aggregated by `sum`, at least one row field and one
97
- * column field. An unsupported request throws at authoring time rather than emitting a corrupt file.
111
+ * Supported shape: at least one row field and one column field, each source field on at most one
112
+ * axis and at most once, over a header row whose names are unique ignoring case; and exactly one value
113
+ * field, aggregated by any {@link PivotMetric} (`sum` by default), which may also be an axis field. An
114
+ * unsupported request throws at authoring time rather than emitting a corrupt file.
98
115
  */
99
116
  export declare class PivotTable {
117
+ #private;
100
118
  readonly metric: PivotMetric;
101
119
  readonly sourceSheetName: string;
102
- /** The `A1:C4` source range: the header row through the last data row, across the field columns. */
103
- readonly sourceRef: string;
104
120
  readonly cacheFields: readonly PivotCacheField[];
105
121
  readonly records: readonly (readonly PivotRecordCell[])[];
106
122
  /** Indices into {@link cacheFields} of the row-axis, column-axis, and value fields. */
@@ -108,6 +124,29 @@ export declare class PivotTable {
108
124
  readonly columnFields: readonly number[];
109
125
  readonly valueField: number;
110
126
  constructor(options: PivotTableOptions);
127
+ /**
128
+ * The `A1:C4` source range: the header row through the last data row, across the field columns.
129
+ *
130
+ * It moves with a row or column splice of the source sheet, as Excel moves a pivot's source: an
131
+ * insert inside it grows it and a delete shrinks it, while a delete that takes the whole range leaves
132
+ * it as it was. The cache captured at construction does not change; Excel rebuilds it from this range
133
+ * when it opens the file.
134
+ */
135
+ get sourceRef(): string;
136
+ /** What a splice does to the pivot's source; see `core/internal.ts`. */
137
+ readonly [INTERNAL]: PivotTableInternals;
111
138
  /** The value field's header name, used to label the aggregated data column ("Sum of Amount"). */
112
139
  get valueFieldName(): string;
113
140
  }
141
+ /**
142
+ * A pivot's worksheet source range on `sheet`, moved through a splice as Excel moves it: grown by an
143
+ * insert inside it, shrunk by a delete, and left as it was by a delete that takes every row or every
144
+ * column of it, which Excel does not turn into `#REF!`. A splice of another sheet leaves it too. The one
145
+ * rule for an authored pivot, a loaded pivot's view, and the preserved cache the writer edits.
146
+ */
147
+ export declare function splicePivotSource(sheet: string, ref: string, edit: SheetSplice): string;
148
+ /** What the library's own machinery may do to a {@link PivotTable}; reached as `pivot[INTERNAL]`. */
149
+ export interface PivotTableInternals {
150
+ /** Move the source range through a row or column splice, when the splice is of the source sheet. */
151
+ spliceSource(edit: SheetSplice): void;
152
+ }
@@ -1,8 +1,9 @@
1
1
  import { AuthoringError, InternalError, quoted } from '../errors.js';
2
2
  import { tokenSet } from '../token-set.js';
3
3
  import { encodeRect } from './address.js';
4
+ import { spliceFormula } from './formula-references.js';
4
5
  import { INTERNAL } from './internal.js';
5
- import { isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, richTextToPlain, } from './value.js';
6
+ import { isArrayFormulaValue, isErrorValue, isFormulaValue, isRichTextValue, isSharedFormulaValue, REF_ERROR, richTextToPlain, } from './value.js';
6
7
  const PIVOT_METRICS = {
7
8
  sum: true,
8
9
  count: true,
@@ -32,7 +33,7 @@ const BLANK = { kind: 'blank' };
32
33
  export class PivotTable {
33
34
  metric;
34
35
  sourceSheetName;
35
- sourceRef;
36
+ #sourceRef;
36
37
  cacheFields;
37
38
  records;
38
39
  rowFields;
@@ -64,7 +65,7 @@ export class PivotTable {
64
65
  throw new InternalError('pivot field list is empty after discoverFields guaranteed it is not');
65
66
  }
66
67
  this.sourceSheetName = source.name;
67
- this.sourceRef = encodeRect({
68
+ this.#sourceRef = encodeRect({
68
69
  top: 1,
69
70
  left: firstField.col,
70
71
  bottom: lastRow,
@@ -80,7 +81,11 @@ export class PivotTable {
80
81
  const catalogues = fields.map(() => null);
81
82
  this.cacheFields = fields.map((field, fieldIndex) => {
82
83
  const scalars = columnScalars[fieldIndex] ?? missingColumn(fieldIndex);
83
- const containsBlank = scalars.some((scalar) => scalar.kind === 'blank');
84
+ const kinds = {
85
+ containsString: scalars.some((scalar) => scalar.kind === 'string'),
86
+ containsBlank: scalars.some((scalar) => scalar.kind === 'blank'),
87
+ numeric: numericSummary(scalars),
88
+ };
84
89
  if (axisFields.has(fieldIndex)) {
85
90
  const items = [];
86
91
  const catalogue = new Map();
@@ -92,9 +97,9 @@ export class PivotTable {
92
97
  }
93
98
  }
94
99
  catalogues[fieldIndex] = catalogue;
95
- return { name: field.name, sharedItems: items, numeric: null, containsBlank };
100
+ return { name: field.name, sharedItems: items, ...kinds };
96
101
  }
97
- return { name: field.name, sharedItems: null, numeric: numericSummary(scalars), containsBlank };
102
+ return { name: field.name, sharedItems: null, ...kinds };
98
103
  });
99
104
  const byField = fields.map((_field, fieldIndex) => ({
100
105
  scalars: columnScalars[fieldIndex] ?? missingColumn(fieldIndex),
@@ -118,6 +123,14 @@ export class PivotTable {
118
123
  }
119
124
  this.records = records;
120
125
  }
126
+ get sourceRef() {
127
+ return this.#sourceRef;
128
+ }
129
+ [INTERNAL] = {
130
+ spliceSource: (edit) => {
131
+ this.#sourceRef = splicePivotSource(this.sourceSheetName, this.#sourceRef, edit);
132
+ },
133
+ };
121
134
  get valueFieldName() {
122
135
  const field = this.cacheFields[this.valueField];
123
136
  if (field === undefined) {
@@ -129,10 +142,18 @@ export class PivotTable {
129
142
  }
130
143
  function discoverFields(valueAt, columnCount) {
131
144
  const fields = [];
145
+ const seen = new Map();
132
146
  for (let col = 1; col <= columnCount; col++) {
133
147
  const name = textOf(scalarOf(valueAt(1, col)));
134
- if (name !== '')
135
- fields.push({ name, col });
148
+ if (name === '')
149
+ continue;
150
+ const clash = seen.get(name.toLowerCase());
151
+ if (clash !== undefined) {
152
+ throw new AuthoringError(`the pivot source header ${quoted(name)} repeats ${quoted(clash)}: pivot field names must ` +
153
+ 'be unique, ignoring case');
154
+ }
155
+ seen.set(name.toLowerCase(), name);
156
+ fields.push({ name, col });
136
157
  }
137
158
  if (fields.length === 0)
138
159
  throw new AuthoringError('the pivot source header row is empty');
@@ -140,7 +161,8 @@ function discoverFields(valueAt, columnCount) {
140
161
  }
141
162
  function resolveRoles(options, fields) {
142
163
  const resolve = (role, name) => {
143
- const index = fields.findIndex((field) => field.name === name);
164
+ const key = name.toLowerCase();
165
+ const index = fields.findIndex((field) => field.name.toLowerCase() === key);
144
166
  if (index < 0) {
145
167
  throw new AuthoringError(`pivot ${role} field ${quoted(name)} is not a column header in the source sheet`);
146
168
  }
@@ -154,9 +176,23 @@ function resolveRoles(options, fields) {
154
176
  if (valueName === undefined || extraValues.length > 0) {
155
177
  throw new AuthoringError('a pivot table needs exactly one value field');
156
178
  }
179
+ const axisRoles = new Map();
180
+ const resolveAxis = (role, names) => names.map((name) => {
181
+ const index = resolve(role, name);
182
+ const earlier = axisRoles.get(index);
183
+ if (earlier === role) {
184
+ throw new AuthoringError(`pivot ${role} field ${quoted(name)} is named more than once`);
185
+ }
186
+ if (earlier !== undefined) {
187
+ throw new AuthoringError(`pivot field ${quoted(name)} is named as both a ${earlier} and a ${role} field: a field ` +
188
+ 'sits on one axis');
189
+ }
190
+ axisRoles.set(index, role);
191
+ return index;
192
+ });
157
193
  return {
158
- rowFields: options.rows.map((name) => resolve('row', name)),
159
- columnFields: options.columns.map((name) => resolve('column', name)),
194
+ rowFields: resolveAxis('row', options.rows),
195
+ columnFields: resolveAxis('column', options.columns),
160
196
  valueField: resolve('value', valueName),
161
197
  };
162
198
  }
@@ -179,10 +215,8 @@ function numericSummary(scalars) {
179
215
  let allInteger = true;
180
216
  let sawNumber = false;
181
217
  for (const scalar of scalars) {
182
- if (scalar.kind === 'blank')
183
- continue;
184
218
  if (scalar.kind !== 'number')
185
- return null;
219
+ continue;
186
220
  sawNumber = true;
187
221
  if (scalar.value < min)
188
222
  min = scalar.value;
@@ -213,16 +247,16 @@ function scalarOf(value) {
213
247
  return { kind: 'string', value: value.toISOString() };
214
248
  if (isRichTextValue(value))
215
249
  return { kind: 'string', value: richTextToPlain(value) };
216
- if (isHyperlinkValue(value)) {
217
- return {
218
- kind: 'string',
219
- value: typeof value.text === 'string' ? value.text : richTextToPlain(value.text),
220
- };
221
- }
222
250
  if (isErrorValue(value))
223
251
  return { kind: 'string', value: value.error };
224
- if (isFormulaValue(value) || isSharedFormulaValue(value)) {
252
+ if (isFormulaValue(value) || isSharedFormulaValue(value) || isArrayFormulaValue(value)) {
225
253
  return value.result === undefined ? BLANK : scalarOf(value.result);
226
254
  }
227
255
  return BLANK;
228
256
  }
257
+ export function splicePivotSource(sheet, ref, edit) {
258
+ if (sheet.toLowerCase() !== edit.sheet.toLowerCase())
259
+ return ref;
260
+ const moved = spliceFormula(ref, sheet, edit);
261
+ return moved === REF_ERROR ? ref : moved;
262
+ }
@@ -1,3 +1,4 @@
1
+ import type { AssertNever } from './internal.ts';
1
2
  /**
2
3
  * Whether each protected-sheet operation stays available to a user. Every flag is an
3
4
  * *allow* flag: `true` keeps the operation permitted, `false` forbids it, and an absent
@@ -46,7 +47,20 @@ export interface SheetProtectionCredential {
46
47
  export interface SheetProtection {
47
48
  readonly flags: SheetProtectionFlags;
48
49
  readonly credential?: SheetProtectionCredential;
50
+ /**
51
+ * The legacy 16-bit password hash (`password="CC3D"`) a file protected the sheet with, kept verbatim
52
+ * so the sheet stays guarded after a save. Pre-2010 Excel, XlsxWriter, openpyxl and LibreOffice write
53
+ * it instead of the agile credential. It is never derived from a password: `protect` writes only the
54
+ * agile form, and this hash is weak enough that nobody should want it authored.
55
+ */
56
+ readonly legacyPasswordHash?: string;
49
57
  }
58
+ /**
59
+ * Whether a string is a legacy sheet password hash as its schema type (`ST_UnsignedShortHex`) spells
60
+ * one: exactly four hexadecimal digits. The reader keeps only such a value and the writer refuses
61
+ * anything else, since the hash is interpolated into an attribute.
62
+ */
63
+ export declare function isLegacyPasswordHash(value: string): boolean;
50
64
  /**
51
65
  * The OOXML encoding table for the protection flags: each `<sheetProtection>` attribute paired
52
66
  * with whether that operation is *forbidden by default* once a sheet is protected. Both directions
@@ -55,10 +69,58 @@ export interface SheetProtection {
55
69
  * and deserialization can never fall out of step. Most editing operations default to forbidden
56
70
  * under protection; selecting cells and the object/scenario operations default to permitted.
57
71
  */
58
- export declare const SHEET_PROTECTION_FLAGS: readonly {
59
- readonly key: keyof SheetProtectionFlags;
60
- readonly defaultForbidden: boolean;
61
- }[];
72
+ export declare const SHEET_PROTECTION_FLAGS: readonly [{
73
+ readonly key: 'formatCells';
74
+ readonly defaultForbidden: true;
75
+ }, {
76
+ readonly key: 'formatColumns';
77
+ readonly defaultForbidden: true;
78
+ }, {
79
+ readonly key: 'formatRows';
80
+ readonly defaultForbidden: true;
81
+ }, {
82
+ readonly key: 'insertColumns';
83
+ readonly defaultForbidden: true;
84
+ }, {
85
+ readonly key: 'insertRows';
86
+ readonly defaultForbidden: true;
87
+ }, {
88
+ readonly key: 'insertHyperlinks';
89
+ readonly defaultForbidden: true;
90
+ }, {
91
+ readonly key: 'deleteColumns';
92
+ readonly defaultForbidden: true;
93
+ }, {
94
+ readonly key: 'deleteRows';
95
+ readonly defaultForbidden: true;
96
+ }, {
97
+ readonly key: 'sort';
98
+ readonly defaultForbidden: true;
99
+ }, {
100
+ readonly key: 'autoFilter';
101
+ readonly defaultForbidden: true;
102
+ }, {
103
+ readonly key: 'pivotTables';
104
+ readonly defaultForbidden: true;
105
+ }, {
106
+ readonly key: 'objects';
107
+ readonly defaultForbidden: false;
108
+ }, {
109
+ readonly key: 'scenarios';
110
+ readonly defaultForbidden: false;
111
+ }, {
112
+ readonly key: 'selectLockedCells';
113
+ readonly defaultForbidden: false;
114
+ }, {
115
+ readonly key: 'selectUnlockedCells';
116
+ readonly defaultForbidden: false;
117
+ }];
118
+ /**
119
+ * Compile-time proof that {@link SHEET_PROTECTION_FLAGS} covers every {@link SheetProtectionFlags}
120
+ * operation. The table's doc says the writer and reader can never fall out of step; that holds only
121
+ * while every flag is in it, and a flag added to the interface alone would be neither written nor read.
122
+ */
123
+ export type EverySheetProtectionFlagIsDeclared = AssertNever<Exclude<keyof SheetProtectionFlags, (typeof SHEET_PROTECTION_FLAGS)[number]['key']>>;
62
124
  /**
63
125
  * Derive a fresh {@link SheetProtectionCredential} for a password. Each call generates a new
64
126
  * random salt, so protecting two sheets with the same password yields different credentials:
@@ -1,5 +1,9 @@
1
1
  import { concat, toBase64, utf16leBytes } from '../bytes.js';
2
2
  import { sha512 } from '../sha512.js';
3
+ const LEGACY_PASSWORD_HASH = /^[0-9A-Fa-f]{4}$/;
4
+ export function isLegacyPasswordHash(value) {
5
+ return LEGACY_PASSWORD_HASH.test(value);
6
+ }
3
7
  export const SHEET_PROTECTION_FLAGS = [
4
8
  { key: 'formatCells', defaultForbidden: true },
5
9
  { key: 'formatColumns', defaultForbidden: true },
package/dist/core/row.js CHANGED
@@ -1,4 +1,4 @@
1
- import { assertRowInBounds, columnToNumber, encodeAddress } from './address.js';
1
+ import { assertColumnInBounds, assertRowInBounds, columnToNumber } from './address.js';
2
2
  import { AxisHandle } from './axis-handle.js';
3
3
  import { INTERNAL } from './internal.js';
4
4
  export class Row extends AxisHandle {
@@ -54,7 +54,8 @@ export class Row extends AxisHandle {
54
54
  }
55
55
  getCell(column) {
56
56
  const index = typeof column === 'number' ? column : columnToNumber(column);
57
- return this.#sheet.getCell(encodeAddress(index, this.number));
57
+ assertColumnInBounds(index);
58
+ return this.#sheet[INTERNAL].masterAt(this.number, index);
58
59
  }
59
60
  get cells() {
60
61
  return this.#sheet[INTERNAL].rowCells(this.number);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A structured reference to one column of a table, spelled as Excel spells it: `Table[Column]` for a
3
+ * plain name, and `Table[[Column]]` for a name holding a character from Microsoft's special list, with
4
+ * `[`, `]`, `#`, `'` and `@` escaped by a leading `'`. Interpolating the name bare wrote
5
+ * `SUBTOTAL(109,T[Price [USD]])`, a formula whose brackets close in the middle of the name.
6
+ */
7
+ export declare function structuredColumnReference(tableName: string, columnName: string): string;
@@ -0,0 +1,7 @@
1
+ const STRUCTURED_REFERENCE_SPECIAL = /[\t\n\r,:.[\]#'"{}$^&*+=></@\\!()%?`;~_-]/;
2
+ const STRUCTURED_REFERENCE_ESCAPED = /[[\]#'@]/g;
3
+ export function structuredColumnReference(tableName, columnName) {
4
+ if (!STRUCTURED_REFERENCE_SPECIAL.test(columnName))
5
+ return `${tableName}[${columnName}]`;
6
+ return `${tableName}[[${columnName.replace(STRUCTURED_REFERENCE_ESCAPED, "'$&")}]]`;
7
+ }
@@ -1,7 +1,7 @@
1
1
  import { type GridRect } from './address.ts';
2
2
  import { type ClonePlan } from './clone.ts';
3
3
  import { type AxisSplice } from './grid-shift.ts';
4
- import type { AssertNever } from './internal.ts';
4
+ import { type AssertNever, INTERNAL } from './internal.ts';
5
5
  import type { CellStyle } from './style.ts';
6
6
  import type { CellValue } from './value.ts';
7
7
  /** A per-column cell format applied to a table's body cells: the facets Excel's table-column style
@@ -70,6 +70,12 @@ export interface TableStyleInfo {
70
70
  declare const STYLE_INFO_CLONE: ClonePlan<TableStyleInfo>;
71
71
  /** The proof that {@link STYLE_INFO_CLONE} names every field of the style. */
72
72
  export type EveryTableStyleInfoFieldIsCloned = AssertNever<Exclude<keyof Required<TableStyleInfo>, keyof typeof STYLE_INFO_CLONE>>;
73
+ /** The `<tableStyleInfo>` banding flags, in CT_TableStyleInfo attribute order. Each is an optional
74
+ * OOXML boolean, so the list is the whole of what the reader and the writer need to know about them. */
75
+ export declare const TABLE_STYLE_FLAGS: readonly ["showFirstColumn", "showLastColumn", "showRowStripes", "showColumnStripes"];
76
+ /** Compile-time proof that `name` and {@link TABLE_STYLE_FLAGS} cover every {@link TableStyleInfo}
77
+ * field, so a flag added to the type cannot be read or written by one direction only. */
78
+ export type EveryTableStyleFlagIsDeclared = AssertNever<Exclude<keyof TableStyleInfo, 'name' | (typeof TABLE_STYLE_FLAGS)[number]>>;
73
79
  /**
74
80
  * OOXML's totals-row function names (`ST_TotalsRowFunction`) to the `SUBTOTAL` first-argument code
75
81
  * Excel writes into a materialised totals cell. The `10x` band ignores manually hidden rows, the
@@ -105,18 +111,31 @@ export interface TableColumn {
105
111
  * `<totalsRowFormula>` child. Round-tripped verbatim and written into the totals cell as the
106
112
  * cell's formula. Meaningful only alongside `totalsRowFunction: "custom"`; ignored otherwise. */
107
113
  readonly totalsRowFormula?: string;
114
+ /**
115
+ * The formula (no leading `=`) that makes this a calculated column: OOXML's
116
+ * `<calculatedColumnFormula>`, spelled as it stands in the column's first data row, with a structured
117
+ * reference to the row written `Table[[#This Row],[Column]]`. Excel fills it into each row added to
118
+ * the table.
119
+ *
120
+ * It describes the column and writes no cell: the body cells hold their own formulas.
121
+ */
122
+ readonly calculatedColumnFormula?: string;
108
123
  /** A format applied to this column's body cells as they are written (see {@link TableColumnStyle}).
109
124
  * Excel bakes a table-column style into the cells rather than storing it as table metadata, so this
110
125
  * is an authoring convenience: it round-trips as the affected cells' own styles, not as the table. */
111
126
  readonly style?: TableColumnStyle;
112
127
  }
113
128
  export interface TableOptions {
114
- /** Table name: a valid Excel identifier, unique across the workbook. This is the name used in
115
- * structured formula references (`Table1[Column]`). */
129
+ /**
130
+ * Table name: a valid Excel identifier, unique across the workbook. It is the one name a table has:
131
+ * the name structured references use (`Table1[Column]`) and the one Excel shows, written as both the
132
+ * table part's `name` and its `displayName`.
133
+ *
134
+ * There is no separate display label. OOXML's `displayName` is the identifier Excel resolves a
135
+ * structured reference against and shows in the UI, and a package whose `displayName` carries a space
136
+ * opens with Excel's repair prompt.
137
+ */
116
138
  name: string;
117
- /** Human-facing display name shown in the UI. A free-form label (spaces allowed) that need not
118
- * be a valid identifier. Defaults to {@link name} when omitted. */
119
- displayName?: string;
120
139
  /** A1 reference of the table's top-left cell (an anchor, e.g. `"A1"`, not the full range). */
121
140
  ref: string;
122
141
  /** The table's columns, left to right. At least one is required. */
@@ -147,14 +166,16 @@ export type TableRegion = GridRect;
147
166
  export declare class Table {
148
167
  #private;
149
168
  readonly name: string;
150
- readonly displayName: string;
151
- readonly columns: readonly TableColumn[];
152
169
  readonly headerRow: boolean;
153
170
  readonly totalsRow: boolean;
154
171
  readonly totalsRowShown: boolean | undefined;
155
172
  readonly autoFilter: boolean;
156
173
  readonly style: TableStyleInfo | undefined;
174
+ /** What a splice does to the table's formulas; see `core/internal.ts`. */
175
+ readonly [INTERNAL]: TableInternals;
157
176
  constructor(options: TableOptions, grid?: TableGrid);
177
+ /** The table's columns, left to right. */
178
+ get columns(): readonly TableColumn[];
158
179
  get columnCount(): number;
159
180
  /** The number of data rows (excludes the header and totals rows). Always defined: a table loaded
160
181
  * from a file derives it from the stored range, so reading the height never throws. */
@@ -211,4 +232,13 @@ export declare class Table {
211
232
  /** The occupied rectangle, for conflict checks such as overlapping merges. */
212
233
  get region(): TableRegion;
213
234
  }
235
+ /** What the library's own machinery may do to a {@link Table}; reached as `table[INTERNAL]`. */
236
+ export interface TableInternals {
237
+ /**
238
+ * Replace each column's calculated column formula and totals row formula with what `rewrite` makes
239
+ * of it, which is how a row or column splice moves the references they make. A column whose formulas
240
+ * come back unchanged stays the same object.
241
+ */
242
+ rewriteFormulas(rewrite: (formula: string) => string): void;
243
+ }
214
244
  export {};