@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
@@ -1,4 +1,7 @@
1
- import { tokenSet } from '../token-set.js';
1
+ import { tokenSet, tokenSetOf } from '../token-set.js';
2
+ import { cloneWith } from './clone.js';
3
+ import { copyKeyIfPresent } from './containers.js';
4
+ import { NAMED_STYLE_ID } from './internal.js';
2
5
  export const isNamedUnderlineStyle = tokenSet({
3
6
  none: true,
4
7
  single: true,
@@ -11,27 +14,50 @@ export function parseArgb(value) {
11
14
  const argb = hex.length === 6 ? `FF${hex}` : hex;
12
15
  return /^[0-9a-fA-F]{8}$/.test(argb) ? argb : undefined;
13
16
  }
14
- export const isFillPatternType = tokenSet({
15
- none: true,
16
- solid: true,
17
- gray125: true,
18
- darkGray: true,
19
- mediumGray: true,
20
- lightGray: true,
21
- gray0625: true,
22
- darkHorizontal: true,
23
- darkVertical: true,
24
- darkDown: true,
25
- darkUp: true,
26
- darkGrid: true,
27
- darkTrellis: true,
28
- lightHorizontal: true,
29
- lightVertical: true,
30
- lightDown: true,
31
- lightUp: true,
32
- lightGrid: true,
33
- lightTrellis: true,
34
- });
17
+ export const FILL_PATTERNS_IN_SCHEMA_ORDER = [
18
+ 'none',
19
+ 'solid',
20
+ 'mediumGray',
21
+ 'darkGray',
22
+ 'lightGray',
23
+ 'darkHorizontal',
24
+ 'darkVertical',
25
+ 'darkDown',
26
+ 'darkUp',
27
+ 'darkGrid',
28
+ 'darkTrellis',
29
+ 'lightHorizontal',
30
+ 'lightVertical',
31
+ 'lightDown',
32
+ 'lightUp',
33
+ 'lightGrid',
34
+ 'lightTrellis',
35
+ 'gray125',
36
+ 'gray0625',
37
+ ];
38
+ export const isFillPatternType = tokenSetOf(FILL_PATTERNS_IN_SCHEMA_ORDER);
39
+ const GRADIENT_STOP_CLONE = { position: 'value', color: 'record' };
40
+ const PATTERN_FILL_CLONE = {
41
+ type: 'value',
42
+ pattern: 'value',
43
+ fgColor: 'record',
44
+ bgColor: 'record',
45
+ };
46
+ const GRADIENT_FILL_CLONE = {
47
+ type: 'value',
48
+ gradient: 'value',
49
+ degree: 'value',
50
+ left: 'value',
51
+ right: 'value',
52
+ top: 'value',
53
+ bottom: 'value',
54
+ stops: (stops) => stops.map((stop) => cloneWith(stop, GRADIENT_STOP_CLONE)),
55
+ };
56
+ export function cloneFill(fill) {
57
+ return fill.type === 'gradient'
58
+ ? cloneWith(fill, GRADIENT_FILL_CLONE)
59
+ : cloneWith(fill, PATTERN_FILL_CLONE);
60
+ }
35
61
  export const isBorderStyle = tokenSet({
36
62
  thin: true,
37
63
  medium: true,
@@ -47,11 +73,42 @@ export const isBorderStyle = tokenSet({
47
73
  mediumDashDotDot: true,
48
74
  slantDashDot: true,
49
75
  });
76
+ const BORDER_EDGE_CLONE = { style: 'value', color: 'record' };
77
+ const cloneBorderEdge = (edge) => cloneWith(edge, BORDER_EDGE_CLONE);
78
+ const BORDER_CLONE = {
79
+ left: cloneBorderEdge,
80
+ right: cloneBorderEdge,
81
+ top: cloneBorderEdge,
82
+ bottom: cloneBorderEdge,
83
+ diagonal: cloneBorderEdge,
84
+ diagonalUp: 'value',
85
+ diagonalDown: 'value',
86
+ };
87
+ export function cloneBorder(border) {
88
+ return cloneWith(border, BORDER_CLONE);
89
+ }
50
90
  export const isFontVerticalAlignment = tokenSet({
51
91
  superscript: true,
52
92
  subscript: true,
53
93
  });
54
94
  export const isFontScheme = tokenSet({ minor: true, major: true, none: true });
95
+ const FONT_CLONE = {
96
+ name: 'value',
97
+ size: 'value',
98
+ family: 'value',
99
+ scheme: 'value',
100
+ charset: 'value',
101
+ color: 'record',
102
+ bold: 'value',
103
+ italic: 'value',
104
+ underline: 'value',
105
+ strike: 'value',
106
+ outline: 'value',
107
+ vertAlign: 'value',
108
+ };
109
+ export function cloneFont(font) {
110
+ return cloneWith(font, FONT_CLONE);
111
+ }
55
112
  export const isHorizontalAlignment = tokenSet({
56
113
  general: true,
57
114
  left: true,
@@ -93,17 +150,24 @@ const CELL_STYLE_FACET_KEYS = {
93
150
  protection: true,
94
151
  };
95
152
  export const CELL_STYLE_FACETS = Object.keys(CELL_STYLE_FACET_KEYS);
153
+ const CELL_CONTENT_ONLY_KEYS = {
154
+ quotePrefix: true,
155
+ [NAMED_STYLE_ID]: true,
156
+ };
157
+ export const CELL_CONTENT_FACETS = [
158
+ ...CELL_STYLE_FACETS,
159
+ ...Reflect.ownKeys(CELL_CONTENT_ONLY_KEYS),
160
+ ];
161
+ export function assignContentFacets(target, source) {
162
+ for (const facet of CELL_CONTENT_FACETS)
163
+ copyKeyIfPresent(target, source, facet);
164
+ }
96
165
  export function assignStyleFacets(target, source) {
97
166
  for (const facet of CELL_STYLE_FACETS)
98
- copyFacet(target, source, facet);
167
+ copyKeyIfPresent(target, source, facet);
99
168
  }
100
169
  export function pickStyleFacets(source) {
101
170
  const facets = {};
102
171
  assignStyleFacets(facets, source);
103
172
  return facets;
104
173
  }
105
- function copyFacet(target, source, key) {
106
- const value = source[key];
107
- if (value !== undefined)
108
- target[key] = value;
109
- }
@@ -1,4 +1,4 @@
1
- import type { DifferentialStyle } from './style.ts';
1
+ import type { DifferentialStyle } from './workbook-styles.ts';
2
2
  /**
3
3
  * The regions a table style can format (`ST_TableStyleType`).
4
4
  *
@@ -1,4 +1,4 @@
1
- import { AuthoringError } from '../errors.js';
1
+ import { AuthoringError, invalidToken, quoted } from '../errors.js';
2
2
  export const TABLE_STYLE_ELEMENT_TYPES = [
3
3
  'wholeTable',
4
4
  'headerRow',
@@ -46,17 +46,17 @@ export function checkTableStyle(style) {
46
46
  if (element === undefined)
47
47
  continue;
48
48
  if (!isTableStyleElementType(type)) {
49
- throw new AuthoringError(`Invalid table style element ${JSON.stringify(type)}: not a value the OOXML enumeration allows`);
49
+ throw invalidToken('table style element', type);
50
50
  }
51
51
  const { size } = element;
52
52
  if (size === undefined)
53
53
  continue;
54
54
  if (!STRIPE_ELEMENT_TYPES.has(type)) {
55
- throw new AuthoringError(`table style element "${type}" cannot carry a size: band width applies only to ` +
55
+ throw new AuthoringError(`table style element ${quoted(type)} cannot carry a size: band width applies only to ` +
56
56
  `${[...STRIPE_ELEMENT_TYPES].join(', ')}`);
57
57
  }
58
58
  if (!Number.isInteger(size) || size < 1) {
59
- throw new RangeError(`Invalid table style band size ${size}: expected a positive integer`);
59
+ throw new RangeError(`invalid table style band size ${size}: expected a positive integer`);
60
60
  }
61
61
  }
62
62
  }
@@ -1,4 +1,7 @@
1
1
  import { type GridRect } from './address.ts';
2
+ import { type ClonePlan } from './clone.ts';
3
+ import { type AxisSplice } from './grid-shift.ts';
4
+ import type { AssertNever } from './internal.ts';
2
5
  import type { CellStyle } from './style.ts';
3
6
  import type { CellValue } from './value.ts';
4
7
  /** A per-column cell format applied to a table's body cells: the facets Excel's table-column style
@@ -57,6 +60,16 @@ export interface TableStyleInfo {
57
60
  /** Band the columns (alternating fill). */
58
61
  readonly showColumnStripes?: boolean;
59
62
  }
63
+ /** Copy a style, keeping only its defined fields off the literal so `exactOptionalPropertyTypes`
64
+ * never sees a fabricated `key: undefined`: an absent attribute must stay absent across a copy.
65
+ *
66
+ * The sentinel name `"None"` (Excel's table-style gallery entry for *no* style) is normalised to an
67
+ * absent name: OOXML expresses "unstyled" as a `<tableStyleInfo>` with no `name` attribute, so a
68
+ * literal `name="None"` would reference a style that does not exist and make the file suspect. The
69
+ * banding flags set alongside it are untouched. */
70
+ declare const STYLE_INFO_CLONE: ClonePlan<TableStyleInfo>;
71
+ /** The proof that {@link STYLE_INFO_CLONE} names every field of the style. */
72
+ export type EveryTableStyleInfoFieldIsCloned = AssertNever<Exclude<keyof Required<TableStyleInfo>, keyof typeof STYLE_INFO_CLONE>>;
60
73
  /**
61
74
  * OOXML's totals-row function names (`ST_TotalsRowFunction`) to the `SUBTOTAL` first-argument code
62
75
  * Excel writes into a materialised totals cell. The `10x` band ignores manually hidden rows, the
@@ -158,20 +171,24 @@ export declare class Table {
158
171
  */
159
172
  addRow(values?: readonly CellValue[]): void;
160
173
  /**
161
- * Re-pin the table through a row splice: `count` rows removed at the 1-based `start`, then rows
162
- * inserted so surviving rows below shift by `delta`. A splice entirely above the table moves its
163
- * whole range by `delta`; one landing inside grows or shrinks the data rows to absorb the change;
174
+ * Re-pin the table through a row splice. A splice entirely above the table moves its whole range
175
+ * by the splice's `delta`; one landing inside grows or shrinks the data rows to absorb the change;
164
176
  * one that deletes the table's every row removes it. Returns `false` when the table no longer has
165
177
  * a row to occupy (the caller drops it), `true` when it survives.
166
178
  */
167
- shiftRows(start: number, count: number, delta: number): boolean;
179
+ shiftRows(splice: AxisSplice): boolean;
168
180
  /**
169
- * Re-pin the table through a column splice. A splice entirely to the table's left moves its anchor
170
- * by `delta`; one to its right leaves it untouched. A splice landing inside the table's columns is
171
- * structural surgery on named columns with no unambiguous answer, so the table's columns are left
172
- * as-is (anchor unchanged) rather than fabricated or dropped. Always returns `true`.
181
+ * Re-pin the table through a column splice, the mirror of {@link shiftRows}. A splice entirely to
182
+ * the table's left moves its anchor by the splice's `delta`; one to its right leaves it untouched;
183
+ * one that deletes the table's every column removes it. Returns `false` when the table no longer
184
+ * has a column to occupy (the caller drops it), `true` when it survives.
185
+ *
186
+ * A splice landing *inside* the table's columns is structural surgery on named columns with no
187
+ * unambiguous answer, so those columns are left as-is rather than fabricated or dropped. Whole-table
188
+ * deletion is not that case: a table left declared over whatever slid into its place, carrying the
189
+ * names of columns that no longer exist, is content the writer then emits.
173
190
  */
174
- shiftColumns(start: number, count: number, delta: number): boolean;
191
+ shiftColumns(splice: AxisSplice): boolean;
175
192
  /**
176
193
  * The options that reconstruct this table: the anchor as a single-cell ref (not the derived
177
194
  * full range), the columns, and the data-row count with the header/totals flags. Feeding this
@@ -194,3 +211,4 @@ export declare class Table {
194
211
  /** The occupied rectangle, for conflict checks such as overlapping merges. */
195
212
  get region(): TableRegion;
196
213
  }
214
+ export {};
@@ -1,19 +1,20 @@
1
- import { AuthoringError } from '../errors.js';
1
+ import { AuthoringError, quoted } from '../errors.js';
2
2
  import { tokenSet } from '../token-set.js';
3
- import { decodeCellRef, encodeAddress } from './address.js';
3
+ import { decodeCellRef, encodeAddress, encodeRect, MAX_COLUMN, MAX_ROW, numberToColumn, } from './address.js';
4
+ import { cloneWith } from './clone.js';
4
5
  import { isDeletedSpan, shiftIndex } from './grid-shift.js';
6
+ import { MAX_TABLE_NAME_LENGTH, TABLE_NAME_PATTERN } from './limits.js';
7
+ const STYLE_INFO_CLONE = {
8
+ name: (name) => (name === 'None' ? undefined : name),
9
+ showFirstColumn: 'value',
10
+ showLastColumn: 'value',
11
+ showRowStripes: 'value',
12
+ showColumnStripes: 'value',
13
+ };
5
14
  function cloneStyleInfo(style) {
6
- const clone = {};
7
- if (style.name !== undefined && style.name !== 'None')
8
- clone.name = style.name;
9
- if (style.showFirstColumn !== undefined)
10
- clone.showFirstColumn = style.showFirstColumn;
11
- if (style.showLastColumn !== undefined)
12
- clone.showLastColumn = style.showLastColumn;
13
- if (style.showRowStripes !== undefined)
14
- clone.showRowStripes = style.showRowStripes;
15
- if (style.showColumnStripes !== undefined)
16
- clone.showColumnStripes = style.showColumnStripes;
15
+ const clone = cloneWith(style, STYLE_INFO_CLONE);
16
+ if (clone.name === undefined)
17
+ delete clone.name;
17
18
  return clone;
18
19
  }
19
20
  export const TOTALS_ROW_SUBTOTAL_CODE = {
@@ -38,7 +39,6 @@ export const isTotalsRowFunction = tokenSet({
38
39
  custom: true,
39
40
  none: true,
40
41
  });
41
- const IDENTIFIER = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
42
42
  function disambiguateColumnNames(columns) {
43
43
  const seen = new Set();
44
44
  return columns.map((column) => {
@@ -50,11 +50,11 @@ function disambiguateColumnNames(columns) {
50
50
  });
51
51
  }
52
52
  function validateTableName(name) {
53
- if (name.length === 0 || name.length > 255) {
54
- throw new RangeError(`table name ${JSON.stringify(name)} must be between 1 and 255 characters`);
53
+ if (name.length === 0 || name.length > MAX_TABLE_NAME_LENGTH) {
54
+ throw new RangeError(`table name ${quoted(name)} must be between 1 and ${MAX_TABLE_NAME_LENGTH} characters`);
55
55
  }
56
- if (!IDENTIFIER.test(name)) {
57
- throw new SyntaxError(`table name ${JSON.stringify(name)} is not a valid Excel identifier: it must start with a letter, ` +
56
+ if (!TABLE_NAME_PATTERN.test(name)) {
57
+ throw new SyntaxError(`table name ${quoted(name)} is not a valid Excel identifier: it must start with a letter, ` +
58
58
  'underscore, or backslash and contain only letters, digits, periods, and underscores');
59
59
  }
60
60
  }
@@ -74,17 +74,17 @@ export class Table {
74
74
  constructor(options, grid) {
75
75
  validateTableName(options.name);
76
76
  if (options.columns.length === 0) {
77
- throw new AuthoringError(`table "${options.name}" must declare at least one column`);
77
+ throw new AuthoringError(`table ${quoted(options.name)} must declare at least one column`);
78
78
  }
79
79
  if (!Number.isInteger(options.rowCount) || options.rowCount < 0) {
80
- throw new RangeError(`table "${options.name}" has an invalid data-row count (${options.rowCount})`);
80
+ throw new RangeError(`table ${quoted(options.name)} has an invalid data-row count (${options.rowCount})`);
81
81
  }
82
82
  let anchor;
83
83
  try {
84
84
  anchor = decodeCellRef(options.ref);
85
85
  }
86
86
  catch (cause) {
87
- throw new SyntaxError(`table ref "${options.ref}" must anchor at a single cell (e.g. "A1")`, {
87
+ throw new SyntaxError(`table ref ${quoted(options.ref)} must anchor at a single cell (e.g. "A1")`, {
88
88
  cause,
89
89
  });
90
90
  }
@@ -102,7 +102,15 @@ export class Table {
102
102
  this.#dataRowCount = options.rowCount;
103
103
  this.#grid = grid;
104
104
  if (this.#rowSpan < 1) {
105
- throw new AuthoringError(`table "${this.name}" has no rows: it needs a header row or at least one data row`);
105
+ throw new AuthoringError(`table ${quoted(this.name)} has no rows: it needs a header row or at least one data row`);
106
+ }
107
+ if (this.#right > MAX_COLUMN) {
108
+ throw new RangeError(`table ${quoted(this.name)} spans ${this.columns.length} columns from ` +
109
+ `${numberToColumn(this.#anchorCol)}, past the last column (${numberToColumn(MAX_COLUMN)})`);
110
+ }
111
+ if (this.#bottom > MAX_ROW) {
112
+ throw new RangeError(`table ${quoted(this.name)} spans ${this.#rowSpan} rows from ${this.#anchorRow}, ` +
113
+ `past the last row (${MAX_ROW})`);
106
114
  }
107
115
  if (grid !== undefined)
108
116
  this.#materializeFrame(grid);
@@ -115,13 +123,13 @@ export class Table {
115
123
  }
116
124
  addRow(values = []) {
117
125
  if (values.length > this.columnCount) {
118
- throw new RangeError(`row has ${values.length} values but table "${this.name}" has ${this.columnCount} columns`);
126
+ throw new RangeError(`row has ${values.length} values but table ${quoted(this.name)} has ${this.columnCount} columns`);
119
127
  }
120
128
  const target = this.#anchorRow + (this.headerRow ? 1 : 0) + this.#dataRowCount;
121
129
  const grid = this.#grid;
122
130
  if (this.totalsRow) {
123
131
  if (grid === undefined) {
124
- throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot relocate its totals row to append a data row`);
132
+ throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot relocate its totals row to append a data row`);
125
133
  }
126
134
  grid.insertRow(target);
127
135
  this.#writeRow(grid, target, values);
@@ -129,7 +137,7 @@ export class Table {
129
137
  }
130
138
  if (values.length > 0) {
131
139
  if (grid === undefined) {
132
- throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot write appended row values`);
140
+ throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot write appended row values`);
133
141
  }
134
142
  this.#writeRow(grid, target, values);
135
143
  }
@@ -174,11 +182,11 @@ export class Table {
174
182
  });
175
183
  }
176
184
  }
177
- shiftRows(start, count, delta) {
178
- if (isDeletedSpan(this.#anchorRow, this.#bottom, start, count))
185
+ shiftRows(splice) {
186
+ if (isDeletedSpan(this.#anchorRow, this.#bottom, splice))
179
187
  return false;
180
- const top = shiftIndex(this.#anchorRow, start, count, delta, 'row');
181
- const bottom = shiftIndex(this.#bottom, start, count, delta, 'row');
188
+ const top = shiftIndex(this.#anchorRow, splice);
189
+ const bottom = shiftIndex(this.#bottom, splice);
182
190
  const span = bottom - top + 1;
183
191
  const fixedRows = (this.headerRow ? 1 : 0) + (this.totalsRow ? 1 : 0);
184
192
  const dataRows = span - fixedRows;
@@ -188,9 +196,13 @@ export class Table {
188
196
  this.#dataRowCount = dataRows;
189
197
  return true;
190
198
  }
191
- shiftColumns(start, count, delta) {
192
- if (this.#anchorCol >= start + count)
193
- this.#anchorCol += delta;
199
+ shiftColumns(splice) {
200
+ if (isDeletedSpan(this.#anchorCol, this.#right, splice))
201
+ return false;
202
+ const anchor = shiftIndex(this.#anchorCol, splice);
203
+ if (anchor + this.columns.length - 1 > MAX_COLUMN)
204
+ return false;
205
+ this.#anchorCol = anchor;
194
206
  return true;
195
207
  }
196
208
  get options() {
@@ -211,13 +223,18 @@ export class Table {
211
223
  return options;
212
224
  }
213
225
  get range() {
214
- return `${encodeAddress(this.#anchorCol, this.#anchorRow)}:${encodeAddress(this.#right, this.#bottom)}`;
226
+ return encodeRect({
227
+ top: this.#anchorRow,
228
+ left: this.#anchorCol,
229
+ bottom: this.#bottom,
230
+ right: this.#right,
231
+ });
215
232
  }
216
233
  get autoFilterRef() {
217
234
  if (!this.autoFilter)
218
235
  return undefined;
219
236
  const bottom = this.#anchorRow + this.#dataRowCount;
220
- return `${encodeAddress(this.#anchorCol, this.#anchorRow)}:${encodeAddress(this.#right, bottom)}`;
237
+ return encodeRect({ top: this.#anchorRow, left: this.#anchorCol, bottom, right: this.#right });
221
238
  }
222
239
  get region() {
223
240
  return { top: this.#anchorRow, left: this.#anchorCol, bottom: this.#bottom, right: this.#right };
@@ -1,3 +1,5 @@
1
+ import { quoted } from '../errors.js';
2
+ import { parseArgb } from './style.js';
1
3
  export const THEME_COLOR_SLOTS = [
2
4
  'lt1',
3
5
  'dk1',
@@ -36,10 +38,9 @@ export const DEFAULT_THEME_FONTS = {
36
38
  minor: OFFICE_BODY_FACE,
37
39
  };
38
40
  export function normalizeThemeColor(value) {
39
- const hex = value.startsWith('#') ? value.slice(1) : value;
40
- const rgb = hex.length === 8 ? hex.slice(2) : hex;
41
- if (!/^[0-9a-fA-F]{6}$/.test(rgb)) {
42
- throw new SyntaxError(`Invalid theme colour ${JSON.stringify(value)}: expected 6 hexadecimal digits (RRGGBB)`);
41
+ const argb = parseArgb(value);
42
+ if (argb === undefined) {
43
+ throw new SyntaxError(`invalid theme colour ${quoted(value)}: expected 6 hexadecimal digits (RRGGBB)`);
43
44
  }
44
- return rgb.toUpperCase();
45
+ return argb.slice(2).toUpperCase();
45
46
  }
@@ -15,6 +15,15 @@ export type ValueType = (typeof ValueType)[keyof typeof ValueType];
15
15
  /** The canonical Excel error literals a cell (or formula result) can carry. */
16
16
  export declare const ERROR_CODES: readonly ['#N/A', '#REF!', '#NAME?', '#DIV/0!', '#NULL!', '#VALUE!', '#NUM!', '#SPILL!', '#CALC!', '#GETTING_DATA'];
17
17
  export type ErrorCode = (typeof ERROR_CODES)[number];
18
+ /**
19
+ * The error a spreadsheet puts in place of a reference that has nowhere left to point.
20
+ *
21
+ * Named here rather than in the two formula codecs that produce it. Both of them -- the A1 renderer
22
+ * in `core/formula.ts` and the BIFF12 one in `io/xlsb/formula.ts` -- had a private literal of their
23
+ * own, spelled the same and named differently, for a value {@link ERROR_CODES} already publishes and
24
+ * `isErrorCode` already recognises.
25
+ */
26
+ export declare const REF_ERROR: ErrorCode;
18
27
  /** An in-cell error, e.g. `{error: '#REF!'}`. */
19
28
  export interface ErrorValue {
20
29
  readonly error: ErrorCode;
@@ -21,6 +21,7 @@ export const ERROR_CODES = [
21
21
  '#CALC!',
22
22
  '#GETTING_DATA',
23
23
  ];
24
+ export const REF_ERROR = '#REF!';
24
25
  const ERROR_SET = new Set(ERROR_CODES);
25
26
  function hasKey(value, key) {
26
27
  return typeof value === 'object' && value !== null && key in value;
@@ -47,58 +48,59 @@ export function richTextToPlain(value) {
47
48
  return value.richText.map((run) => run.text).join('');
48
49
  }
49
50
  export function cellValueToText(value) {
50
- if (value === null)
51
- return '';
52
- switch (typeof value) {
53
- case 'number':
54
- return String(value);
55
- case 'string':
56
- return value;
57
- case 'boolean':
58
- return value ? 'TRUE' : 'FALSE';
59
- default:
60
- break;
61
- }
62
- if (value instanceof Date)
63
- return Number.isNaN(value.getTime()) ? '' : value.toISOString();
64
- if (isHyperlinkValue(value)) {
65
- return typeof value.text === 'string' ? value.text : richTextToPlain(value.text);
66
- }
67
- if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
68
- return value.result === undefined ? '' : cellValueToText(value.result);
69
- }
70
- if (isRichTextValue(value))
71
- return richTextToPlain(value);
72
- if (isErrorValue(value))
73
- return value.error;
74
- return unsupportedValue(value);
51
+ return classify(value, TO_TEXT);
75
52
  }
76
53
  export function detectValueType(value) {
54
+ return classify(value, TO_TYPE);
55
+ }
56
+ function classify(value, visit) {
77
57
  if (value === null)
78
- return ValueType.Null;
58
+ return visit.null(value);
79
59
  switch (typeof value) {
80
60
  case 'number':
81
- return ValueType.Number;
61
+ return visit.number(value);
82
62
  case 'string':
83
- return ValueType.String;
63
+ return visit.string(value);
84
64
  case 'boolean':
85
- return ValueType.Boolean;
65
+ return visit.boolean(value);
86
66
  default:
87
67
  break;
88
68
  }
89
69
  if (value instanceof Date)
90
- return ValueType.Date;
70
+ return visit.date(value);
91
71
  if (isHyperlinkValue(value))
92
- return ValueType.Hyperlink;
72
+ return visit.hyperlink(value);
93
73
  if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
94
- return ValueType.Formula;
74
+ return visit.formula(value);
95
75
  }
96
76
  if (isRichTextValue(value))
97
- return ValueType.RichText;
77
+ return visit.richText(value);
98
78
  if (isErrorValue(value))
99
- return ValueType.Error;
79
+ return visit.error(value);
100
80
  return unsupportedValue(value);
101
81
  }
82
+ const TO_TYPE = {
83
+ null: () => ValueType.Null,
84
+ number: () => ValueType.Number,
85
+ string: () => ValueType.String,
86
+ boolean: () => ValueType.Boolean,
87
+ date: () => ValueType.Date,
88
+ error: () => ValueType.Error,
89
+ formula: () => ValueType.Formula,
90
+ richText: () => ValueType.RichText,
91
+ hyperlink: () => ValueType.Hyperlink,
92
+ };
93
+ const TO_TEXT = {
94
+ null: () => '',
95
+ number: (value) => String(value),
96
+ string: (value) => value,
97
+ boolean: (value) => (value ? 'TRUE' : 'FALSE'),
98
+ date: (value) => (Number.isNaN(value.getTime()) ? '' : value.toISOString()),
99
+ error: (value) => value.error,
100
+ formula: (value) => (value.result === undefined ? '' : cellValueToText(value.result)),
101
+ richText: richTextToPlain,
102
+ hyperlink: (value) => (typeof value.text === 'string' ? value.text : richTextToPlain(value.text)),
103
+ };
102
104
  function unsupportedValue(value) {
103
105
  throw new TypeError(`unsupported cell value: ${describe(value)}`);
104
106
  }
@@ -0,0 +1,34 @@
1
+ import { type WorkbookImage } from './image.ts';
2
+ export declare class WorkbookMedia {
3
+ #private;
4
+ /** The registered images, indexed by the id {@link register} returned. Live, not a copy. */
5
+ get all(): readonly WorkbookImage[];
6
+ /** Look up a registered image by its id, or `undefined` if no image carries that id. */
7
+ get(id: number): WorkbookImage | undefined;
8
+ /**
9
+ * {@link get} for a caller that has no answer for an absent id.
10
+ *
11
+ * An unregistered id is what a sheet belonging to *another* workbook looks like from here, which
12
+ * is why the message names the sheet rather than the id alone. Emitting a package with a drawing
13
+ * pointing at media nobody registered is the silently-broken-image failure this refuses to start.
14
+ *
15
+ * @throws {AuthoringError} if no image carries that id.
16
+ */
17
+ require(id: number, forSheetName: string): WorkbookImage;
18
+ /** Store a picture's bytes and return the id that names them. */
19
+ register(extension: string | undefined, buffer: Uint8Array): number;
20
+ /**
21
+ * Register a picture arriving from elsewhere, re-using an identical one already held.
22
+ *
23
+ * The extension is re-normalised rather than trusted: a hand-built {@link WorkbookImage} may carry
24
+ * `".PNG"` where the registry holds `"png"`, and two spellings of one kind must not read as two
25
+ * pictures.
26
+ *
27
+ * Through the content index rather than a scan. Comparing byte-by-byte against every held picture
28
+ * made importing n distinct images cost n squared byte comparisons: fifty 1 MB pictures carried
29
+ * between workbooks compared about 2.5 GB. The *rule* is unchanged, and `imageContentKey` states
30
+ * why identity here is content and never object identity. The index is built in reverse so the
31
+ * FIRST id registered under a key wins, which is the answer the scan gave.
32
+ */
33
+ registerExisting(image: WorkbookImage): number;
34
+ }
@@ -0,0 +1,44 @@
1
+ import { AuthoringError, quoted } from '../errors.js';
2
+ import { imageContentKey, normalizeImageExtension } from './image.js';
3
+ export class WorkbookMedia {
4
+ #images = [];
5
+ #byContent;
6
+ get all() {
7
+ return this.#images;
8
+ }
9
+ get(id) {
10
+ return this.#images[id];
11
+ }
12
+ require(id, forSheetName) {
13
+ const image = this.#images[id];
14
+ if (image === undefined) {
15
+ throw new AuthoringError(`worksheet ${quoted(forSheetName)} shows image id ${id}, which is not registered on this ` +
16
+ "workbook: a sheet's images can only be exported by the workbook that holds them");
17
+ }
18
+ return image;
19
+ }
20
+ register(extension, buffer) {
21
+ const image = {
22
+ extension: normalizeImageExtension(extension, buffer),
23
+ data: buffer,
24
+ };
25
+ this.#images.push(image);
26
+ const id = this.#images.length - 1;
27
+ const index = this.#byContent;
28
+ if (index !== undefined) {
29
+ const key = imageContentKey(image);
30
+ if (!index.has(key))
31
+ index.set(key, id);
32
+ }
33
+ return id;
34
+ }
35
+ registerExisting(image) {
36
+ const candidate = {
37
+ extension: normalizeImageExtension(image.extension, image.data),
38
+ data: image.data,
39
+ };
40
+ this.#byContent ??= new Map(this.#images.map((held, id) => [imageContentKey(held), id]).reverse());
41
+ return (this.#byContent.get(imageContentKey(candidate)) ??
42
+ this.register(candidate.extension, candidate.data));
43
+ }
44
+ }