@shbernal/ts-xlsx 3.0.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 (277) hide show
  1. package/README.md +34 -24
  2. package/dist/bytes.d.ts +10 -2
  3. package/dist/bytes.js +22 -0
  4. package/dist/core/address.d.ts +96 -2
  5. package/dist/core/address.js +87 -33
  6. package/dist/core/array-formula-ranges.d.ts +33 -0
  7. package/dist/core/array-formula-ranges.js +62 -0
  8. package/dist/core/autofilter.d.ts +4 -3
  9. package/dist/core/autofilter.js +31 -30
  10. package/dist/core/axis-handle.d.ts +2 -0
  11. package/dist/core/axis-handle.js +5 -2
  12. package/dist/core/cell.d.ts +4 -3
  13. package/dist/core/cell.js +4 -6
  14. package/dist/core/clone.d.ts +29 -0
  15. package/dist/core/clone.js +24 -0
  16. package/dist/core/color-resolution.js +4 -6
  17. package/dist/core/column.d.ts +1 -0
  18. package/dist/core/column.js +6 -2
  19. package/dist/core/comment-thread.d.ts +7 -3
  20. package/dist/core/comment-thread.js +2 -1
  21. package/dist/core/conditional-formatting-overlay.d.ts +11 -1
  22. package/dist/core/conditional-formatting-overlay.js +30 -3
  23. package/dist/core/conditional-formatting.d.ts +107 -11
  24. package/dist/core/conditional-formatting.js +76 -19
  25. package/dist/core/containers.d.ts +12 -0
  26. package/dist/core/containers.js +5 -0
  27. package/dist/core/data-validation-overlay.d.ts +7 -1
  28. package/dist/core/data-validation-overlay.js +19 -4
  29. package/dist/core/data-validation.d.ts +22 -0
  30. package/dist/core/data-validation.js +30 -4
  31. package/dist/core/date-format.d.ts +20 -0
  32. package/dist/core/date-format.js +170 -0
  33. package/dist/core/date.d.ts +43 -8
  34. package/dist/core/date.js +19 -3
  35. package/dist/core/formula-references.d.ts +26 -0
  36. package/dist/core/formula-references.js +173 -0
  37. package/dist/core/formula-scan.d.ts +2 -0
  38. package/dist/core/formula-scan.js +58 -0
  39. package/dist/core/formula.d.ts +76 -17
  40. package/dist/core/formula.js +87 -80
  41. package/dist/core/function-values.d.ts +4 -0
  42. package/dist/core/function-values.js +58 -0
  43. package/dist/core/future-functions.d.ts +3 -0
  44. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  45. package/dist/core/grid-edits.d.ts +30 -3
  46. package/dist/core/grid-edits.js +248 -81
  47. package/dist/core/grid-shift.d.ts +58 -9
  48. package/dist/core/grid-shift.js +26 -4
  49. package/dist/core/hyperlink.d.ts +39 -0
  50. package/dist/core/hyperlink.js +88 -0
  51. package/dist/core/image.d.ts +49 -14
  52. package/dist/core/image.js +16 -5
  53. package/dist/core/internal.d.ts +10 -2
  54. package/dist/core/limits.d.ts +25 -0
  55. package/dist/core/limits.js +4 -0
  56. package/dist/core/merge.d.ts +18 -4
  57. package/dist/core/merge.js +33 -20
  58. package/dist/core/page-setup.d.ts +26 -11
  59. package/dist/core/pivot-table.d.ts +49 -10
  60. package/dist/core/pivot-table.js +112 -60
  61. package/dist/core/protection.d.ts +66 -4
  62. package/dist/core/protection.js +6 -11
  63. package/dist/core/range.js +34 -30
  64. package/dist/core/row-input.js +2 -2
  65. package/dist/core/row.d.ts +1 -0
  66. package/dist/core/row.js +6 -2
  67. package/dist/core/structured-reference.d.ts +7 -0
  68. package/dist/core/structured-reference.js +7 -0
  69. package/dist/core/style.d.ts +85 -70
  70. package/dist/core/style.js +92 -28
  71. package/dist/core/table-style.d.ts +1 -1
  72. package/dist/core/table-style.js +4 -4
  73. package/dist/core/table.d.ts +64 -16
  74. package/dist/core/table.js +87 -40
  75. package/dist/core/theme.d.ts +2 -0
  76. package/dist/core/theme.js +9 -5
  77. package/dist/core/value.d.ts +60 -28
  78. package/dist/core/value.js +48 -43
  79. package/dist/core/workbook-media.d.ts +34 -0
  80. package/dist/core/workbook-media.js +44 -0
  81. package/dist/core/workbook-styles.d.ts +83 -1
  82. package/dist/core/workbook-styles.js +4 -0
  83. package/dist/core/workbook-theme.js +5 -3
  84. package/dist/core/workbook-vba.js +3 -2
  85. package/dist/core/workbook.d.ts +41 -3
  86. package/dist/core/workbook.js +46 -41
  87. package/dist/core/worksheet-comments.d.ts +2 -1
  88. package/dist/core/worksheet-comments.js +20 -13
  89. package/dist/core/worksheet-merges.d.ts +49 -0
  90. package/dist/core/worksheet-merges.js +65 -0
  91. package/dist/core/worksheet-model.d.ts +10 -2
  92. package/dist/core/worksheet-model.js +28 -9
  93. package/dist/core/worksheet-pictures.d.ts +3 -3
  94. package/dist/core/worksheet-pictures.js +7 -6
  95. package/dist/core/worksheet.d.ts +165 -27
  96. package/dist/core/worksheet.js +206 -89
  97. package/dist/customui/ribbon.js +2 -1
  98. package/dist/entries/core.d.ts +21 -14
  99. package/dist/entries/core.js +14 -7
  100. package/dist/entries/errors.d.ts +1 -0
  101. package/dist/entries/errors.js +1 -0
  102. package/dist/entries/xlsx.d.ts +1 -1
  103. package/dist/errors.d.ts +64 -0
  104. package/dist/errors.js +22 -0
  105. package/dist/hex.d.ts +8 -0
  106. package/dist/hex.js +3 -0
  107. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  108. package/dist/io/cell-metadata/metadata.js +35 -0
  109. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  110. package/dist/io/cell-metadata/rich-values.js +80 -0
  111. package/dist/io/csv/delimiter.d.ts +21 -0
  112. package/dist/io/csv/delimiter.js +18 -0
  113. package/dist/io/csv/errors.d.ts +14 -0
  114. package/dist/io/csv/errors.js +5 -0
  115. package/dist/io/csv/read.d.ts +6 -1
  116. package/dist/io/csv/read.js +51 -22
  117. package/dist/io/csv/write.d.ts +19 -5
  118. package/dist/io/csv/write.js +24 -44
  119. package/dist/io/opc/inflate.js +14 -0
  120. package/dist/io/opc/namespaces.d.ts +16 -0
  121. package/dist/io/opc/namespaces.js +4 -0
  122. package/dist/io/opc/part-paths.d.ts +9 -0
  123. package/dist/io/opc/part-paths.js +5 -5
  124. package/dist/io/opc/read-opc.d.ts +58 -14
  125. package/dist/io/opc/read-opc.js +49 -21
  126. package/dist/io/opc/read-options.d.ts +5 -1
  127. package/dist/io/opc/sniff-format.d.ts +3 -3
  128. package/dist/io/opc/sniff-format.js +2 -2
  129. package/dist/io/opc/strict-relationships.d.ts +2 -0
  130. package/dist/io/opc/strict-relationships.js +12 -0
  131. package/dist/io/read-policy/column-budget.d.ts +26 -0
  132. package/dist/io/read-policy/column-budget.js +18 -0
  133. package/dist/io/read-policy/read-repair.d.ts +74 -0
  134. package/dist/io/read-policy/read-repair.js +119 -0
  135. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  136. package/dist/io/style/cell-style-resolution.js +30 -0
  137. package/dist/io/style/xf-style.d.ts +23 -1
  138. package/dist/io/style/xf-style.js +8 -0
  139. package/dist/io/xlsb/formula.d.ts +2 -2
  140. package/dist/io/xlsb/formula.js +52 -35
  141. package/dist/io/xlsb/primitives.js +8 -14
  142. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  143. package/dist/io/xlsb/ptg-functions.js +495 -596
  144. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  145. package/dist/io/xlsb/read-metadata.js +69 -0
  146. package/dist/io/xlsb/read-styles.js +45 -81
  147. package/dist/io/xlsb/read-worksheet.d.ts +26 -1
  148. package/dist/io/xlsb/read-worksheet.js +148 -106
  149. package/dist/io/xlsb/read.d.ts +6 -3
  150. package/dist/io/xlsb/read.js +70 -31
  151. package/dist/io/xlsb/record-stream.d.ts +30 -0
  152. package/dist/io/xlsb/record-stream.js +20 -0
  153. package/dist/io/xlsb/record-types.d.ts +14 -5
  154. package/dist/io/xlsb/record-types.js +14 -5
  155. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  156. package/dist/io/xlsb/sheet-protection.js +60 -0
  157. package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
  158. package/dist/io/xlsx/cell-accumulator.js +74 -21
  159. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  160. package/dist/io/xlsx/cell-metadata.js +162 -0
  161. package/dist/io/xlsx/cell-value.d.ts +19 -5
  162. package/dist/io/xlsx/cell-value.js +45 -30
  163. package/dist/io/xlsx/color-xml.js +3 -2
  164. package/dist/io/xlsx/column-span.d.ts +11 -0
  165. package/dist/io/xlsx/column-span.js +9 -0
  166. package/dist/io/xlsx/comments.d.ts +9 -30
  167. package/dist/io/xlsx/comments.js +25 -83
  168. package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
  169. package/dist/io/xlsx/conditional-formatting.js +437 -138
  170. package/dist/io/xlsx/data-validation.d.ts +12 -7
  171. package/dist/io/xlsx/data-validation.js +67 -53
  172. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  173. package/dist/io/xlsx/edit-vba.js +52 -52
  174. package/dist/io/xlsx/font-xml.d.ts +7 -0
  175. package/dist/io/xlsx/font-xml.js +104 -0
  176. package/dist/io/xlsx/hyperlinks.d.ts +19 -21
  177. package/dist/io/xlsx/hyperlinks.js +12 -32
  178. package/dist/io/xlsx/images.d.ts +49 -16
  179. package/dist/io/xlsx/images.js +149 -50
  180. package/dist/io/xlsx/namespaces.d.ts +24 -0
  181. package/dist/io/xlsx/namespaces.js +11 -0
  182. package/dist/io/xlsx/package-plan.d.ts +60 -15
  183. package/dist/io/xlsx/package-plan.js +83 -18
  184. package/dist/io/xlsx/part-names.d.ts +6 -8
  185. package/dist/io/xlsx/part-names.js +3 -7
  186. package/dist/io/xlsx/pivot.d.ts +5 -5
  187. package/dist/io/xlsx/pivot.js +26 -24
  188. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  189. package/dist/io/xlsx/preserved-splices.js +74 -0
  190. package/dist/io/xlsx/read-comments.d.ts +29 -0
  191. package/dist/io/xlsx/read-comments.js +75 -0
  192. package/dist/io/xlsx/read-parts.d.ts +59 -0
  193. package/dist/io/xlsx/read-parts.js +259 -0
  194. package/dist/io/xlsx/read-pivot.js +11 -4
  195. package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
  196. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
  197. package/dist/io/xlsx/read-rows.d.ts +35 -11
  198. package/dist/io/xlsx/read-rows.js +73 -49
  199. package/dist/io/xlsx/read-shared-strings.js +3 -2
  200. package/dist/io/xlsx/read-styles.d.ts +0 -30
  201. package/dist/io/xlsx/read-styles.js +45 -105
  202. package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
  203. package/dist/io/xlsx/read-workbook-xml.js +175 -0
  204. package/dist/io/xlsx/read-worksheet.d.ts +9 -3
  205. package/dist/io/xlsx/read-worksheet.js +70 -177
  206. package/dist/io/xlsx/read.d.ts +3 -14
  207. package/dist/io/xlsx/read.js +74 -411
  208. package/dist/io/xlsx/relationships.d.ts +3 -0
  209. package/dist/io/xlsx/relationships.js +4 -0
  210. package/dist/io/xlsx/rich-text.js +1 -1
  211. package/dist/io/xlsx/row-position.d.ts +20 -0
  212. package/dist/io/xlsx/row-position.js +11 -0
  213. package/dist/io/xlsx/row-xml.d.ts +139 -0
  214. package/dist/io/xlsx/row-xml.js +222 -0
  215. package/dist/io/xlsx/shared-formulas.js +2 -2
  216. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  217. package/dist/io/xlsx/sheet-properties.js +182 -20
  218. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  219. package/dist/io/xlsx/strict-parts.js +171 -0
  220. package/dist/io/xlsx/style-elements.d.ts +41 -0
  221. package/dist/io/xlsx/style-elements.js +136 -0
  222. package/dist/io/xlsx/styles.d.ts +17 -3
  223. package/dist/io/xlsx/styles.js +13 -209
  224. package/dist/io/xlsx/tables.d.ts +10 -2
  225. package/dist/io/xlsx/tables.js +55 -52
  226. package/dist/io/xlsx/theme-xml.d.ts +32 -4
  227. package/dist/io/xlsx/theme-xml.js +106 -71
  228. package/dist/io/xlsx/threaded-comments.d.ts +14 -11
  229. package/dist/io/xlsx/threaded-comments.js +16 -8
  230. package/dist/io/xlsx/workbook-xml.d.ts +45 -6
  231. package/dist/io/xlsx/workbook-xml.js +62 -54
  232. package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
  233. package/dist/io/xlsx/worksheet-xml.js +69 -282
  234. package/dist/io/xlsx/write-stream.d.ts +90 -19
  235. package/dist/io/xlsx/write-stream.js +80 -25
  236. package/dist/io/xlsx/write.d.ts +12 -6
  237. package/dist/io/xlsx/write.js +226 -123
  238. package/dist/rel-type.d.ts +25 -0
  239. package/dist/rel-type.js +9 -0
  240. package/dist/token-set.d.ts +13 -0
  241. package/dist/token-set.js +4 -1
  242. package/dist/vba/bytes.d.ts +16 -5
  243. package/dist/vba/bytes.js +27 -6
  244. package/dist/vba/cfb-format.d.ts +55 -0
  245. package/dist/vba/cfb-format.js +35 -0
  246. package/dist/vba/cfb-writer.d.ts +8 -0
  247. package/dist/vba/cfb-writer.js +40 -40
  248. package/dist/vba/cfb.d.ts +21 -4
  249. package/dist/vba/cfb.js +124 -90
  250. package/dist/vba/codepage.js +2 -1
  251. package/dist/vba/dir-records.d.ts +6 -0
  252. package/dist/vba/dir-records.js +10 -2
  253. package/dist/vba/errors.d.ts +11 -4
  254. package/dist/vba/index.d.ts +0 -2
  255. package/dist/vba/index.js +0 -2
  256. package/dist/vba/ms-ovba.d.ts +6 -2
  257. package/dist/vba/ms-ovba.js +66 -22
  258. package/dist/vba/project-editor.js +129 -119
  259. package/dist/vba/project.d.ts +15 -1
  260. package/dist/vba/project.js +38 -13
  261. package/dist/vba/vba-encoding.d.ts +4 -10
  262. package/dist/vba/vba-encoding.js +3 -17
  263. package/dist/xml/xml-attrs.d.ts +37 -0
  264. package/dist/xml/xml-attrs.js +30 -0
  265. package/dist/xml/xml-chars.d.ts +40 -0
  266. package/dist/xml/xml-chars.js +12 -0
  267. package/dist/xml/xml-namespaces.d.ts +31 -0
  268. package/dist/xml/xml-namespaces.js +50 -0
  269. package/dist/xml/xml-read.d.ts +101 -11
  270. package/dist/xml/xml-read.js +150 -18
  271. package/dist/xml/xml-scan.d.ts +25 -34
  272. package/dist/xml/xml-scan.js +69 -46
  273. package/dist/xml/xml.d.ts +59 -26
  274. package/dist/xml/xml.js +45 -23
  275. package/package.json +15 -9
  276. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  277. package/dist/core/modern-functions.d.ts +0 -1
@@ -1,5 +1,86 @@
1
- import type { NamedCellStyle, TableStyleTable } from './style.ts';
1
+ import type { CellStyle } from './style.ts';
2
2
  import type { TableStyle } from './table-style.ts';
3
+ /**
4
+ * A named cell style: the OOXML `cellStyleXfs`/`cellStyles` layer. A spreadsheet applies a built-in
5
+ * or custom style (e.g. "Normal", "Accent1") whose visual facets live in this shared, named layer
6
+ * rather than on each cell's direct format; a cell links to it and inherits any facet the direct
7
+ * format leaves unset. The facets are a cell's own (see {@link CellStyle}); `name` is the style's
8
+ * display name and `builtinId` its Excel gallery index when it is a built-in style.
9
+ */
10
+ export type NamedCellStyle = Readonly<CellStyle> & {
11
+ readonly name?: string;
12
+ readonly builtinId?: number;
13
+ };
14
+ /**
15
+ * A differential style (OOXML CT_Dxf): formatting laid *over* whatever a cell already carries. Only
16
+ * the facets present override; the rest of the cell's own style shows through. It carries the subset
17
+ * of the cell-style facets (see {@link CellStyle}) a `<dxf>` can express: font, number format, fill,
18
+ * and border.
19
+ *
20
+ * Differential styles live in one workbook-level table (`<dxfs>`) that several features index into:
21
+ * a conditional-formatting rule's highlight format, and a table style's per-element formatting
22
+ * (`<tableStyleElement dxfId="…">`). They are interned and shared, so two features asking for the
23
+ * same formatting land on one entry.
24
+ *
25
+ * Not every facet reaches every consumer. As a **table style element**, Excel applies only the font,
26
+ * fill, and border: its own object model exposes `Font`, `Interior`, and `Borders` on a table style
27
+ * element and nothing for a number format, so a `numFmt` set here is carried faithfully through a
28
+ * round-trip but has no visible effect. The type is left whole rather than split, because the same
29
+ * value is legitimately reused across both consumers and narrowing it would only move the surprise.
30
+ */
31
+ export type DifferentialStyle = Pick<CellStyle, 'font' | 'numFmt' | 'fill' | 'border'>;
32
+ /**
33
+ * The `<tableStyles>` block of a styles part: the custom table/pivot style definitions a file
34
+ * declares, and the two gallery names it nominates as the default for a new table and a new pivot.
35
+ *
36
+ * Each entry of {@link styles} is one `<tableStyle>…</tableStyle>` fragment kept verbatim, for the
37
+ * same reason a `<dxf>` is: a `tableStyleElement`'s `dxfId` indexes the differential-style table,
38
+ * which the writer re-emits **at its original indices**, so the references stay valid without
39
+ * reparsing anything. That index-stability is load-bearing: renumbering the dxf table would
40
+ * silently re-point every preserved table style at a different format.
41
+ *
42
+ * The two default names are ordinary strings, not fragments: they are re-escaped on write, so they
43
+ * are held decoded.
44
+ */
45
+ export interface TableStyleTable {
46
+ readonly styles: readonly TableStyleDefinition[];
47
+ readonly defaultTableStyle?: string | undefined;
48
+ readonly defaultPivotStyle?: string | undefined;
49
+ /**
50
+ * The namespace prefixes the verbatim {@link styles} fragments use, mapped to their URI and to
51
+ * whether the source marked the prefix ignorable (`mc:Ignorable`).
52
+ *
53
+ * Carrying a fragment verbatim carries its *prefixes* too. Excel stamps a revision id
54
+ * (`xr9:uid="{…}"`) on every `<tableStyle>` it writes, so a fragment re-emitted under a
55
+ * `<styleSheet>` that declares only the default namespace is not namespace-well-formed, and no
56
+ * consumer can parse the part at all, which is a far louder failure than the dropped table style
57
+ * this preservation exists to prevent. The writer re-declares each prefix on `<styleSheet>` and
58
+ * re-states the ignorable ones, exactly as the source did.
59
+ */
60
+ readonly namespaces?: readonly TableStyleNamespace[];
61
+ }
62
+ /**
63
+ * One preserved `<tableStyle>` definition: its bytes, and the `name` a table's style info matches it by.
64
+ *
65
+ * The name travels beside the fragment rather than being read back out of it. An authored style
66
+ * replaces the preserved definition it shares a name with, and a pattern pulling `name="…"` out of
67
+ * the bytes did not see `name='…'`, which is the same attribute in single quotes: the writer emitted
68
+ * both definitions under one name, the duplicate {@link Workbook.addTableStyle} promises cannot happen.
69
+ * The reader takes the name from the scanner that read the element, decoded, whatever its quoting.
70
+ */
71
+ export interface TableStyleDefinition {
72
+ /** The style's `name`, decoded; empty for a fragment that declared none. */
73
+ readonly name: string;
74
+ /** The `<tableStyle>…</tableStyle>` element exactly as the file spelled it. */
75
+ readonly xml: string;
76
+ }
77
+ /** One namespace declaration a preserved `<tableStyle>` fragment depends on. */
78
+ export interface TableStyleNamespace {
79
+ readonly prefix: string;
80
+ readonly uri: string;
81
+ /** Whether the source listed this prefix in the stylesheet's `mc:Ignorable`. */
82
+ readonly ignorable: boolean;
83
+ }
3
84
  /**
4
85
  * The preserved style tables of a workbook: the `<dxfs>` fragments, the named cell styles, the two
5
86
  * colour lists, and the table-style definitions a file declared plus the ones a caller authored.
@@ -19,6 +100,7 @@ export declare class WorkbookStyleTables {
19
100
  addCustomTableStyle(style: TableStyle): void;
20
101
  indexedPalette(): readonly string[];
21
102
  restoreDifferentialStyles(fragments: readonly string[]): void;
103
+ adoptDifferentialStyle(fragment: string): number;
22
104
  restoreNamedStyles(styles: readonly NamedCellStyle[]): void;
23
105
  restoreIndexedColors(fragments: readonly string[]): void;
24
106
  restoreMruColors(fragments: readonly string[]): void;
@@ -33,6 +33,10 @@ export class WorkbookStyleTables {
33
33
  restoreDifferentialStyles(fragments) {
34
34
  replaceContents(this.#differentialStyles, fragments);
35
35
  }
36
+ adoptDifferentialStyle(fragment) {
37
+ const existing = this.#differentialStyles.indexOf(fragment);
38
+ return existing === -1 ? this.#differentialStyles.push(fragment) - 1 : existing;
39
+ }
36
40
  restoreNamedStyles(styles) {
37
41
  replaceContents(this.#namedStyles, styles);
38
42
  }
@@ -19,9 +19,11 @@ export class WorkbookTheme {
19
19
  this.#declared = declared;
20
20
  }
21
21
  author(overrides) {
22
- for (const value of Object.values(overrides.colors ?? {}))
23
- normalizeThemeColor(value);
24
- Object.assign(this.#authored.colors, overrides.colors ?? {});
22
+ const colors = Object.fromEntries(Object.entries(overrides.colors ?? {}).map(([slot, value]) => [
23
+ slot,
24
+ normalizeThemeColor(value),
25
+ ]));
26
+ Object.assign(this.#authored.colors, colors);
25
27
  this.#authored.fonts = { ...this.#authored.fonts, ...overrides.fonts };
26
28
  }
27
29
  get colors() {
@@ -1,3 +1,4 @@
1
+ import { isRelType } from '../rel-type.js';
1
2
  import { addVbaReference, parseVbaProject, removeVbaModule, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, VbaAuthorError, vbaProjectSignatureKind, } from '../vba/index.js';
2
3
  import { replaceContents } from './containers.js';
3
4
  export class WorkbookVbaProject {
@@ -21,7 +22,7 @@ export class WorkbookVbaProject {
21
22
  set bytes(bytes) {
22
23
  if (bytes !== undefined)
23
24
  parseVbaProject(bytes);
24
- replaceContents(this.#references, this.#references.filter((r) => !r.relType.endsWith('/vbaProject')));
25
+ replaceContents(this.#references, this.#references.filter((r) => !isRelType(r.relType, 'vbaProject')));
25
26
  if (bytes !== undefined) {
26
27
  this.#references.push({
27
28
  relType: VBA_PROJECT_REL_TYPE,
@@ -70,7 +71,7 @@ export class WorkbookVbaProject {
70
71
  this.bytes = addVbaReference(bytes, ref);
71
72
  }
72
73
  #ref() {
73
- return this.#references.find((r) => r.relType.endsWith('/vbaProject'));
74
+ return this.#references.find((r) => isRelType(r.relType, 'vbaProject'));
74
75
  }
75
76
  #entry() {
76
77
  const ref = this.#ref();
@@ -1,13 +1,16 @@
1
1
  import { type CustomUiDocument } from '../customui/index.ts';
2
2
  import { type VbaLibraryReference, type VbaProject, type VbaProjectSignature } from '../vba/index.ts';
3
3
  import { type Person } from './comment-thread.ts';
4
+ import type { DateEpoch } from './date.ts';
5
+ import { type SheetSplice } from './formula-references.ts';
4
6
  import { type WorkbookImage, type WorksheetImages } from './image.ts';
5
7
  import { INTERNAL } from './internal.ts';
6
8
  import type { PreservedPart, PreservedRootReference } from './preserved.ts';
7
- import type { Color, Font, NamedCellStyle, TableStyleTable } from './style.ts';
9
+ import type { Color, Font } from './style.ts';
8
10
  import { type TableStyle } from './table-style.ts';
9
11
  import { type ThemeColorScheme, type ThemeFontScheme, type ThemeOverrides } from './theme.ts';
10
12
  import type { WorkbookProtection } from './workbook-protection.ts';
13
+ import { type NamedCellStyle, type TableStyleTable } from './workbook-styles.ts';
11
14
  import { type DeclaredThemeSchemes } from './workbook-theme.ts';
12
15
  import { type Visibility, Worksheet, type WorksheetState } from './worksheet.ts';
13
16
  /**
@@ -155,6 +158,25 @@ export declare class Workbook {
155
158
  * workbook whose cached results are authoritative stays unmarked.
156
159
  */
157
160
  fullCalcOnLoad: boolean;
161
+ /**
162
+ * Which date system this workbook's serials count in: `1900` (the Windows default) or `1904` (the
163
+ * `date1904` flag of `<workbookPr>`, Excel for Macintosh's original). It governs every conversion
164
+ * between a `Date` and the number a cell actually stores, in both directions, so setting it after
165
+ * cells hold dates changes what those cells mean rather than converting them.
166
+ *
167
+ * Read from the file and written back, because dropping it is not a cosmetic loss: the serials stay
168
+ * as they were and the consumer re-reads them under the other system, so the workbook silently
169
+ * changes meaning by four years and a day.
170
+ */
171
+ dateEpoch: DateEpoch;
172
+ /**
173
+ * The workbook's VBA identity (`<workbookPr codeName>`), the name a macro means by `ThisWorkbook`.
174
+ * Undefined for a workbook with no VBA project, which is what Excel writes for one.
175
+ *
176
+ * Preserved rather than modeled: nothing here reads it, but a `.xlsm` whose code name is dropped on
177
+ * a round trip has had the binding between its macros and its document cut.
178
+ */
179
+ codeName?: string;
158
180
  /**
159
181
  * Workbook-level structure/window protection: the OOXML `<workbookProtection>` element. Absent by
160
182
  * default (an unprotected workbook). Set it to lock the workbook shell, or leave it as read from a
@@ -324,7 +346,8 @@ export declare class Workbook {
324
346
  * slot left unauthored keeps the source's own encoding, including the `<a:sysClr>` form Excel uses
325
347
  * for `dk1`/`lt1` so they follow the viewer's window colours.
326
348
  *
327
- * @throws {AuthoringError} if a colour is not 6 or 8 hexadecimal digits.
349
+ * @throws {SyntaxError} if a colour is not 6 or 8 hexadecimal digits, with or without a leading `#`.
350
+ * Every colour is read before any is stored, so a refused call changes nothing.
328
351
  */
329
352
  setTheme(overrides: ThemeOverrides): void;
330
353
  /**
@@ -333,6 +356,9 @@ export declare class Workbook {
333
356
  *
334
357
  * Note the slot *order*: `theme="0"` is `lt1` and `theme="1"` is `dk1`, which is not the order the
335
358
  * slots appear in the theme part. See {@link THEME_COLOR_SLOTS}.
359
+ *
360
+ * Every slot reads back as six uppercase hexadecimal digits, whichever spelling {@link setTheme} was
361
+ * given or the theme part used.
336
362
  */
337
363
  get themeColors(): ThemeColorScheme;
338
364
  /** The theme's major (heading) and minor (body) typefaces, authored values over the source's. */
@@ -392,7 +418,8 @@ export declare class Workbook {
392
418
  *
393
419
  * A serializer composes these *over* the existing part rather than generating one from scratch, so
394
420
  * a preserved theme keeps its format scheme, its unauthored slots' exact encoding, and the
395
- * relationships it carries.
421
+ * relationships it carries. Its colours are already reduced to the six uppercase hexadecimal digits
422
+ * a slot holds.
396
423
  */
397
424
  get themeOverrides(): ThemeOverrides | undefined;
398
425
  /**
@@ -540,6 +567,12 @@ export interface WorkbookInternals {
540
567
  * re-write.
541
568
  */
542
569
  restoreDifferentialStyles(fragments: readonly string[]): void;
570
+ /**
571
+ * The index of a `<dxf>` fragment a sheet carried inline, found among the differential styles or
572
+ * appended after them, so a conditional format read from the extension form holds a `dxfId` like any
573
+ * other rule.
574
+ */
575
+ adoptDifferentialStyle(fragment: string): number;
543
576
  /**
544
577
  * Reinstate the custom indexed-color palette (`<colors><indexedColors>`) read from a file, each
545
578
  * entry a verbatim `<rgbColor rgb="…"/>` fragment, so a colour referenced by `indexed="…"` keeps
@@ -596,4 +629,9 @@ export interface WorkbookInternals {
596
629
  * {@link Workbook.addPerson} is the authoring verb.
597
630
  */
598
631
  restorePersons(persons: readonly Person[]): void;
632
+ /**
633
+ * Every row and column splice made to the workbook's sheets, in the order they were made, for the
634
+ * writer to replay over the references a preserved part spells.
635
+ */
636
+ splices(): readonly SheetSplice[];
599
637
  }
@@ -1,11 +1,14 @@
1
1
  import { isCustomUiRelType, parseCustomUi } from '../customui/index.js';
2
- import { AuthoringError } from '../errors.js';
2
+ import { AuthoringError, quoted } from '../errors.js';
3
3
  import { addVbaReference, removeVbaModule, } from '../vba/index.js';
4
4
  import { commentThreadGuid } from './comment-thread.js';
5
- import { findRegisteredImage, normalizeImageExtension, } from './image.js';
5
+ import { spliceFormula } from './formula-references.js';
6
+ import { pictureProperties } from './image.js';
6
7
  import { INTERNAL } from './internal.js';
8
+ import { INVALID_SHEET_NAME_CHARS, MAX_SHEET_NAME_LENGTH } from './limits.js';
7
9
  import { checkTableStyle } from './table-style.js';
8
10
  import { OFFICE_BODY_FACE, THEME_COLOR_SLOTS, } from './theme.js';
11
+ import { WorkbookMedia } from './workbook-media.js';
9
12
  import { WorkbookStyleTables } from './workbook-styles.js';
10
13
  import { WorkbookTheme } from './workbook-theme.js';
11
14
  import { WorkbookVbaProject } from './workbook-vba.js';
@@ -17,27 +20,28 @@ export const DEFAULT_WORKBOOK_VIEW = {
17
20
  height: 12220,
18
21
  activeTab: 0,
19
22
  };
20
- const MAX_SHEET_NAME_LENGTH = 31;
21
- const INVALID_SHEET_NAME_CHARS = /[*?:\\/[\]]/;
22
23
  export class Workbook {
23
24
  properties = {};
24
25
  view = { ...DEFAULT_WORKBOOK_VIEW };
25
26
  fullCalcOnLoad = false;
27
+ dateEpoch = 1900;
28
+ codeName;
26
29
  protection = undefined;
27
30
  #worksheets = [];
28
31
  #nextSheetId = 1;
29
- #media = [];
32
+ #media = new WorkbookMedia();
30
33
  #definedNames = [];
31
34
  #styles = new WorkbookStyleTables();
32
35
  #theme = new WorkbookTheme(() => this.#styles.indexedPalette());
33
36
  #preservedReferences = [];
37
+ #splices = [];
34
38
  #persons = new Map();
35
39
  get worksheets() {
36
40
  return this.#worksheets;
37
41
  }
38
42
  get activeTabIndex() {
39
43
  const { activeTab } = this.view;
40
- return Number.isInteger(activeTab) && activeTab > 0 && activeTab < this.#worksheets.length
44
+ return Number.isInteger(activeTab) && activeTab >= 0 && activeTab < this.#worksheets.length
41
45
  ? activeTab
42
46
  : 0;
43
47
  }
@@ -180,52 +184,36 @@ export class Workbook {
180
184
  return this.#persons.get(id);
181
185
  }
182
186
  addImage(options) {
183
- this.#media.push({
184
- extension: normalizeImageExtension(options.extension, options.buffer),
185
- data: options.buffer,
186
- });
187
- return this.#media.length - 1;
187
+ return this.#media.register(options.extension, options.buffer);
188
188
  }
189
189
  get media() {
190
- return this.#media;
190
+ return this.#media.all;
191
191
  }
192
192
  getImage(id) {
193
- return this.#media[id];
193
+ return this.#media.get(id);
194
194
  }
195
195
  exportImages(sheet) {
196
- const resolve = (id) => {
197
- const image = this.#media[id];
198
- if (image === undefined) {
199
- throw new AuthoringError(`worksheet "${sheet.name}" shows image id ${id}, which is not registered on this ` +
200
- "workbook: a sheet's images can only be exported by the workbook that holds them");
201
- }
202
- return image;
203
- };
204
196
  const background = sheet.backgroundImageId;
205
197
  return {
206
- anchored: sheet.images.map(({ imageId, anchor }) => ({ image: resolve(imageId), anchor })),
207
- background: background === undefined ? undefined : resolve(background),
198
+ anchored: sheet.images.map((picture) => ({
199
+ image: this.#media.require(picture.imageId, sheet.name),
200
+ anchor: picture.anchor,
201
+ ...pictureProperties(picture),
202
+ })),
203
+ background: background === undefined ? undefined : this.#media.require(background, sheet.name),
208
204
  };
209
205
  }
210
206
  importImages(sheet, images) {
211
207
  for (const id of new Set(sheet.images.map((image) => image.imageId)))
212
208
  sheet.removeImage(id);
213
209
  sheet.removeBackgroundImage();
214
- for (const { image, anchor } of images.anchored) {
215
- sheet.addImageAnchor(this.#registerImage(image), anchor);
210
+ for (const picture of images.anchored) {
211
+ sheet.addImageAnchor(this.#media.registerExisting(picture.image), picture.anchor, pictureProperties(picture));
216
212
  }
217
213
  if (images.background !== undefined) {
218
- sheet.addBackgroundImage(this.#registerImage(images.background));
214
+ sheet.addBackgroundImage(this.#media.registerExisting(images.background));
219
215
  }
220
216
  }
221
- #registerImage(image) {
222
- const candidate = {
223
- extension: normalizeImageExtension(image.extension, image.data),
224
- data: image.data,
225
- };
226
- return (findRegisteredImage(this.#media, candidate) ??
227
- this.addImage({ buffer: candidate.data, extension: candidate.extension }));
228
- }
229
217
  get definedNames() {
230
218
  return this.#definedNames;
231
219
  }
@@ -234,16 +222,31 @@ export class Workbook {
234
222
  throw new AuthoringError('a defined name cannot be empty');
235
223
  }
236
224
  if (definedName.scope !== undefined && this.getWorksheet(definedName.scope) === undefined) {
237
- throw new AuthoringError(`defined name "${definedName.name}" is scoped to unknown worksheet "${definedName.scope}"`);
225
+ throw new AuthoringError(`defined name ${quoted(definedName.name)} is scoped to unknown worksheet ${quoted(definedName.scope)}`);
238
226
  }
239
227
  this.#definedNames.push(definedName);
240
228
  }
241
229
  addWorksheet(name, options = {}) {
242
230
  this.#assertValidSheetName(name);
243
231
  const sheet = new Worksheet(name, this.#nextSheetId++, options.state ?? 'visible');
232
+ sheet[INTERNAL].setFormulaHost((edit) => {
233
+ this.#spliceFormulasBeyond(sheet, edit);
234
+ });
244
235
  this.#worksheets.push(sheet);
245
236
  return sheet;
246
237
  }
238
+ #spliceFormulasBeyond(spliced, edit) {
239
+ this.#splices.push(edit);
240
+ for (const sheet of this.#worksheets) {
241
+ if (sheet !== spliced)
242
+ sheet[INTERNAL].spliceFormulas(edit);
243
+ }
244
+ for (const [index, definedName] of this.#definedNames.entries()) {
245
+ const refersTo = spliceFormula(definedName.refersTo, definedName.scope, edit);
246
+ if (refersTo !== definedName.refersTo)
247
+ this.#definedNames[index] = { ...definedName, refersTo };
248
+ }
249
+ }
247
250
  getWorksheet(nameOrId) {
248
251
  if (typeof nameOrId === 'number') {
249
252
  return this.#worksheets.find((sheet) => sheet.id === nameOrId);
@@ -255,11 +258,11 @@ export class Workbook {
255
258
  const sheet = this.getWorksheet(nameOrId);
256
259
  if (sheet !== undefined)
257
260
  return sheet;
258
- const wanted = typeof nameOrId === 'number' ? `id ${nameOrId}` : JSON.stringify(nameOrId);
261
+ const wanted = typeof nameOrId === 'number' ? `id ${nameOrId}` : quoted(nameOrId);
259
262
  if (this.#worksheets.length === 0) {
260
263
  throw new AuthoringError(`no worksheet ${wanted}: this workbook has no worksheets`);
261
264
  }
262
- const have = this.#worksheets.map((sheet) => JSON.stringify(sheet.name)).join(', ');
265
+ const have = this.#worksheets.map((sheet) => quoted(sheet.name)).join(', ');
263
266
  throw new AuthoringError(`no worksheet ${wanted}; this workbook has ${have}`);
264
267
  }
265
268
  #assertValidSheetName(name) {
@@ -267,16 +270,16 @@ export class Workbook {
267
270
  throw new AuthoringError('worksheet name cannot be empty');
268
271
  }
269
272
  if (name.length > MAX_SHEET_NAME_LENGTH) {
270
- throw new AuthoringError(`worksheet name "${name}" exceeds the ${MAX_SHEET_NAME_LENGTH}-character limit`);
273
+ throw new AuthoringError(`worksheet name ${quoted(name)} exceeds the ${MAX_SHEET_NAME_LENGTH}-character limit`);
271
274
  }
272
275
  if (INVALID_SHEET_NAME_CHARS.test(name)) {
273
- throw new AuthoringError(`worksheet name "${name}" contains a character Excel forbids (* ? : \\ / [ ])`);
276
+ throw new AuthoringError(`worksheet name ${quoted(name)} contains a character Excel forbids (* ? : \\ / [ ])`);
274
277
  }
275
278
  if (name.startsWith("'") || name.endsWith("'")) {
276
- throw new AuthoringError(`worksheet name "${name}" cannot start or end with an apostrophe`);
279
+ throw new AuthoringError(`worksheet name ${quoted(name)} cannot start or end with an apostrophe`);
277
280
  }
278
281
  if (this.getWorksheet(name) !== undefined) {
279
- throw new AuthoringError(`a worksheet named "${name}" already exists (names are case-insensitive)`);
282
+ throw new AuthoringError(`a worksheet named ${quoted(name)} already exists (names are case-insensitive)`);
280
283
  }
281
284
  }
282
285
  [INTERNAL] = {
@@ -289,6 +292,7 @@ export class Workbook {
289
292
  restoreDifferentialStyles: (fragments) => {
290
293
  this.#styles.restoreDifferentialStyles(fragments);
291
294
  },
295
+ adoptDifferentialStyle: (fragment) => this.#styles.adoptDifferentialStyle(fragment),
292
296
  restoreIndexedColors: (fragments) => {
293
297
  this.#styles.restoreIndexedColors(fragments);
294
298
  },
@@ -312,5 +316,6 @@ export class Workbook {
312
316
  for (const person of persons)
313
317
  this.#persons.set(person.id, person);
314
318
  },
319
+ splices: () => this.#splices,
315
320
  };
316
321
  }
@@ -1,4 +1,5 @@
1
1
  import { type CommentThread } from './comment-thread.ts';
2
+ import { type AxisSplice } from './grid-shift.ts';
2
3
  export declare class WorksheetComments {
3
4
  #private;
4
5
  constructor(sheetName: () => string);
@@ -12,6 +13,6 @@ export declare class WorksheetComments {
12
13
  * the cell, so a thread left behind would put a note on one cell and its conversation on another,
13
14
  * a pairing the writer emits and Excel refuses.
14
15
  */
15
- shift(axis: 'row' | 'col', start: number, count: number, delta: number): void;
16
+ shift(splice: AxisSplice): void;
16
17
  restore(threads: readonly CommentThread[]): void;
17
18
  }
@@ -1,10 +1,12 @@
1
+ import { quoted } from '../errors.js';
1
2
  import { decodeCellRef, encodeAddress } from './address.js';
2
3
  import { commentThreadGuid, commentThreadOffset } from './comment-thread.js';
3
4
  import { replaceContents } from './containers.js';
4
- import { isDeletedSpan, shiftIndex } from './grid-shift.js';
5
+ import { shiftPoint } from './grid-shift.js';
5
6
  export class WorksheetComments {
6
7
  #sheetName;
7
8
  #threads = [];
9
+ #takenIds = new Set();
8
10
  constructor(sheetName) {
9
11
  this.#sheetName = sheetName;
10
12
  }
@@ -12,14 +14,14 @@ export class WorksheetComments {
12
14
  return this.#threads;
13
15
  }
14
16
  add(thread) {
15
- const taken = new Set(this.#threads.flatMap((held) => held.comments.map((comment) => comment.id)));
17
+ const claimed = new Set();
16
18
  const comments = thread.comments.map((comment) => {
17
19
  const id = commentThreadGuid(comment.id, 'a comment id');
18
- if (taken.has(id)) {
19
- throw new SyntaxError(`a comment id must be unique within a sheet, but "${id}" is already used on "${this.#sheetName()}": ` +
20
+ if (this.#takenIds.has(id) || claimed.has(id)) {
21
+ throw new SyntaxError(`a comment id must be unique within a sheet, but ${quoted(id)} is already used on ${quoted(this.#sheetName())}: ` +
20
22
  'a reply and the legacy fallback comment both find their thread by it');
21
23
  }
22
- taken.add(id);
24
+ claimed.add(id);
23
25
  return {
24
26
  ...comment,
25
27
  id,
@@ -38,30 +40,35 @@ export class WorksheetComments {
38
40
  };
39
41
  });
40
42
  this.#threads.push({ ...thread, ref: anchorRef(thread.ref), comments });
43
+ for (const id of claimed)
44
+ this.#takenIds.add(id);
41
45
  }
42
46
  at(reference) {
43
47
  const anchor = anchorRef(reference);
44
48
  return this.#threads.find((thread) => thread.ref === anchor);
45
49
  }
46
- shift(axis, start, count, delta) {
50
+ shift(splice) {
47
51
  const survivors = [];
48
52
  for (const thread of this.#threads) {
49
- const { col, row } = decodeCellRef(thread.ref);
50
- const line = axis === 'row' ? row : col;
51
- if (isDeletedSpan(line, line, start, count))
53
+ const anchor = decodeCellRef(thread.ref);
54
+ const moved = shiftPoint(anchor, splice);
55
+ if (moved === undefined)
52
56
  continue;
53
- const moved = shiftIndex(line, start, count, delta, axis);
54
- if (moved === line) {
57
+ if (moved.col === anchor.col && moved.row === anchor.row) {
55
58
  survivors.push(thread);
56
59
  continue;
57
60
  }
58
- const ref = axis === 'row' ? encodeAddress(col, moved) : encodeAddress(moved, row);
59
- survivors.push({ ...thread, ref });
61
+ survivors.push({ ...thread, ref: encodeAddress(moved.col, moved.row) });
60
62
  }
61
63
  replaceContents(this.#threads, survivors);
62
64
  }
63
65
  restore(threads) {
64
66
  replaceContents(this.#threads, threads);
67
+ this.#takenIds.clear();
68
+ for (const thread of threads) {
69
+ for (const comment of thread.comments)
70
+ this.#takenIds.add(comment.id);
71
+ }
65
72
  }
66
73
  }
67
74
  function anchorRef(reference) {
@@ -0,0 +1,49 @@
1
+ import type { MergeRect } from './merge.ts';
2
+ /** What removing a declared range did, which is two answers because a range and a rectangle are not
3
+ * the same thing: an unbounded whole-row/column merge is declared but has no rectangle, so dropping
4
+ * it leaves every geometry derived from the rectangles (the used extent, the index) still valid. */
5
+ export interface MergeRemoval {
6
+ /** Whether a merge with this exact range string was declared. */
7
+ readonly existed: boolean;
8
+ /** Whether a rectangle went with it, so anything derived from the rectangles is now stale. */
9
+ readonly rectsChanged: boolean;
10
+ }
11
+ export declare class WorksheetMerges {
12
+ #private;
13
+ /** The declared ranges, in the order they were added. Live, not a copy. */
14
+ get ranges(): readonly string[];
15
+ /**
16
+ * The bounded regions as rectangles. Live, and its *identity* is stable for the sheet's lifetime:
17
+ * {@link UsedExtent} holds this array by reference, so every rewrite here goes through
18
+ * `replaceContents` rather than reassignment.
19
+ */
20
+ get rects(): readonly MergeRect[];
21
+ /**
22
+ * Declare a merged range, returning the rectangle it covers, or `undefined` for an unbounded
23
+ * whole-row/column range, which is declared and overlap-checks against nothing. The range is stored
24
+ * in canonical form (see {@link canonicalMerge}).
25
+ *
26
+ * @throws {SyntaxError} if the range is unparseable or names a worksheet.
27
+ * @throws {AuthoringError} if the range overlaps an already-merged region. Excel forbids
28
+ * overlapping merges and writes such geometry as a file it then offers to repair.
29
+ */
30
+ add(range: string): MergeRect | undefined;
31
+ /**
32
+ * Drop a declared range and, with it, the rectangle it covers. The inverse of {@link add}, and it
33
+ * matches however the range is spelled, since both sides are compared in canonical form.
34
+ *
35
+ * @throws {SyntaxError} if the range is unparseable or names a worksheet.
36
+ */
37
+ remove(range: string): MergeRemoval;
38
+ /**
39
+ * Resolve a position to the top-left of the region covering it, or to itself when none does. This
40
+ * is what makes a write through a covered address land on the region's master.
41
+ */
42
+ masterOf(row: number, col: number): {
43
+ row: number;
44
+ col: number;
45
+ };
46
+ /** Replace every declared range and rectangle at once: what a structural splice re-anchors to. */
47
+ replaceAll(ranges: readonly string[], rects: readonly MergeRect[]): void;
48
+ clear(): void;
49
+ }
@@ -0,0 +1,65 @@
1
+ import { AuthoringError, InternalError, quoted } from '../errors.js';
2
+ import { boundedRect, decodeRange, encodeRect } from './address.js';
3
+ import { replaceContents } from './containers.js';
4
+ import { MergeIndex } from './merge-index.js';
5
+ function canonicalMerge(range) {
6
+ const decoded = decodeRange(range);
7
+ if (decoded.sheetName !== undefined) {
8
+ throw new SyntaxError(`merged range ${quoted(range)} names worksheet ${quoted(decoded.sheetName)}: a merge belongs to the sheet it is made on`);
9
+ }
10
+ const rect = boundedRect(decoded);
11
+ return { canonical: rect === undefined ? decoded.dimensions : encodeRect(rect), rect };
12
+ }
13
+ export class WorksheetMerges {
14
+ #ranges = [];
15
+ #rects = [];
16
+ #index = new MergeIndex(this.#rects);
17
+ get ranges() {
18
+ return this.#ranges;
19
+ }
20
+ get rects() {
21
+ return this.#rects;
22
+ }
23
+ add(range) {
24
+ const { canonical, rect } = canonicalMerge(range);
25
+ if (rect !== undefined) {
26
+ if (this.#index.overlapping(rect) !== undefined) {
27
+ throw new AuthoringError(`merged range ${quoted(range)} overlaps an existing merged region`);
28
+ }
29
+ this.#rects.push(rect);
30
+ this.#index.note(rect);
31
+ }
32
+ this.#ranges.push(canonical);
33
+ return rect;
34
+ }
35
+ remove(range) {
36
+ const { canonical, rect } = canonicalMerge(range);
37
+ const index = this.#ranges.indexOf(canonical);
38
+ if (index === -1)
39
+ return { existed: false, rectsChanged: false };
40
+ this.#ranges.splice(index, 1);
41
+ if (rect === undefined)
42
+ return { existed: true, rectsChanged: false };
43
+ const { top, left, bottom, right } = rect;
44
+ const at = this.#rects.findIndex((r) => r.top === top && r.left === left && r.bottom === bottom && r.right === right);
45
+ if (at === -1) {
46
+ throw new InternalError(`merged range ${quoted(canonical)} was declared without its rectangle`);
47
+ }
48
+ this.#rects.splice(at, 1);
49
+ this.#index.invalidate();
50
+ return { existed: true, rectsChanged: true };
51
+ }
52
+ masterOf(row, col) {
53
+ return this.#index.masterOf(row, col);
54
+ }
55
+ replaceAll(ranges, rects) {
56
+ replaceContents(this.#ranges, ranges);
57
+ replaceContents(this.#rects, rects);
58
+ this.#index.invalidate();
59
+ }
60
+ clear() {
61
+ this.#ranges.length = 0;
62
+ this.#rects.length = 0;
63
+ this.#index.invalidate();
64
+ }
65
+ }