@shbernal/ts-xlsx 2.1.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 (225) 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 +5 -4
  7. package/dist/core/autofilter.js +34 -35
  8. package/dist/core/axis-handle.d.ts +10 -0
  9. package/dist/core/axis-handle.js +17 -0
  10. package/dist/core/cell.d.ts +25 -0
  11. package/dist/core/cell.js +9 -3
  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 +5 -1
  16. package/dist/core/column.js +34 -35
  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 +14 -6
  21. package/dist/core/conditional-formatting.js +45 -43
  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 +9 -3
  27. package/dist/core/data-validation.js +21 -18
  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 +14 -10
  39. package/dist/core/image.js +9 -9
  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-index.d.ts +31 -0
  44. package/dist/core/merge-index.js +60 -0
  45. package/dist/core/merge.d.ts +5 -14
  46. package/dist/core/merge.js +18 -27
  47. package/dist/core/page-setup.d.ts +72 -2
  48. package/dist/core/page-setup.js +34 -8
  49. package/dist/core/pivot-table.d.ts +1 -1
  50. package/dist/core/pivot-table.js +67 -53
  51. package/dist/core/protection.js +2 -11
  52. package/dist/core/range.js +34 -30
  53. package/dist/core/row-input.js +2 -2
  54. package/dist/core/row.d.ts +5 -1
  55. package/dist/core/row.js +22 -23
  56. package/dist/core/style.d.ts +134 -75
  57. package/dist/core/style.js +112 -53
  58. package/dist/core/table-style.d.ts +1 -1
  59. package/dist/core/table-style.js +4 -4
  60. package/dist/core/table.d.ts +28 -10
  61. package/dist/core/table.js +54 -39
  62. package/dist/core/theme.js +6 -5
  63. package/dist/core/used-extent.d.ts +44 -0
  64. package/dist/core/used-extent.js +102 -0
  65. package/dist/core/value.d.ts +9 -0
  66. package/dist/core/value.js +36 -34
  67. package/dist/core/workbook-media.d.ts +34 -0
  68. package/dist/core/workbook-media.js +44 -0
  69. package/dist/core/workbook-styles.d.ts +67 -1
  70. package/dist/core/workbook-vba.js +3 -2
  71. package/dist/core/workbook.d.ts +23 -2
  72. package/dist/core/workbook.js +24 -40
  73. package/dist/core/worksheet-comments.d.ts +2 -1
  74. package/dist/core/worksheet-comments.js +20 -13
  75. package/dist/core/worksheet-merges.d.ts +44 -0
  76. package/dist/core/worksheet-merges.js +59 -0
  77. package/dist/core/worksheet-model.d.ts +9 -1
  78. package/dist/core/worksheet-model.js +24 -9
  79. package/dist/core/worksheet.d.ts +72 -12
  80. package/dist/core/worksheet.js +68 -70
  81. package/dist/customui/ribbon.js +6 -7
  82. package/dist/entries/core.d.ts +18 -13
  83. package/dist/entries/core.js +12 -6
  84. package/dist/entries/xlsx.d.ts +1 -1
  85. package/dist/errors.d.ts +64 -0
  86. package/dist/errors.js +22 -0
  87. package/dist/hex.d.ts +8 -0
  88. package/dist/hex.js +3 -0
  89. package/dist/io/csv/delimiter.d.ts +9 -0
  90. package/dist/io/csv/delimiter.js +6 -0
  91. package/dist/io/csv/read.js +11 -4
  92. package/dist/io/csv/write.d.ts +9 -2
  93. package/dist/io/csv/write.js +9 -38
  94. package/dist/io/opc/inflate.js +15 -1
  95. package/dist/io/opc/namespaces.d.ts +16 -0
  96. package/dist/io/opc/namespaces.js +4 -0
  97. package/dist/io/opc/part-paths.d.ts +10 -0
  98. package/dist/io/opc/part-paths.js +16 -1
  99. package/dist/io/opc/read-opc.d.ts +69 -10
  100. package/dist/io/opc/read-opc.js +49 -55
  101. package/dist/io/opc/read-options.d.ts +5 -1
  102. package/dist/io/opc/rels.d.ts +1 -6
  103. package/dist/io/opc/rels.js +1 -4
  104. package/dist/io/opc/sniff-format.d.ts +3 -3
  105. package/dist/io/opc/sniff-format.js +2 -2
  106. package/dist/io/style/xf-style.d.ts +48 -2
  107. package/dist/io/style/xf-style.js +14 -0
  108. package/dist/io/xlsb/formula.d.ts +2 -2
  109. package/dist/io/xlsb/formula.js +49 -34
  110. package/dist/io/xlsb/primitives.js +8 -14
  111. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  112. package/dist/io/xlsb/ptg-functions.js +385 -596
  113. package/dist/io/xlsb/read-styles.js +50 -83
  114. package/dist/io/xlsb/read-worksheet.d.ts +20 -1
  115. package/dist/io/xlsb/read-worksheet.js +73 -89
  116. package/dist/io/xlsb/read.d.ts +6 -3
  117. package/dist/io/xlsb/read.js +47 -29
  118. package/dist/io/xlsb/record-stream.d.ts +30 -0
  119. package/dist/io/xlsb/record-stream.js +20 -0
  120. package/dist/io/xlsb/record-types.d.ts +1 -5
  121. package/dist/io/xlsb/record-types.js +1 -5
  122. package/dist/io/xlsx/cell-accumulator.d.ts +9 -2
  123. package/dist/io/xlsx/cell-accumulator.js +32 -9
  124. package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
  125. package/dist/io/xlsx/cell-style-resolution.js +29 -0
  126. package/dist/io/xlsx/cell-value.d.ts +5 -3
  127. package/dist/io/xlsx/cell-value.js +13 -15
  128. package/dist/io/xlsx/color-xml.js +3 -2
  129. package/dist/io/xlsx/column-budget.d.ts +26 -0
  130. package/dist/io/xlsx/column-budget.js +22 -0
  131. package/dist/io/xlsx/comments.d.ts +6 -2
  132. package/dist/io/xlsx/comments.js +36 -31
  133. package/dist/io/xlsx/conditional-formatting.d.ts +9 -5
  134. package/dist/io/xlsx/conditional-formatting.js +51 -46
  135. package/dist/io/xlsx/data-validation.d.ts +9 -7
  136. package/dist/io/xlsx/data-validation.js +38 -35
  137. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  138. package/dist/io/xlsx/edit-vba.js +15 -29
  139. package/dist/io/xlsx/font-xml.d.ts +7 -0
  140. package/dist/io/xlsx/font-xml.js +104 -0
  141. package/dist/io/xlsx/hyperlinks.d.ts +41 -7
  142. package/dist/io/xlsx/hyperlinks.js +40 -28
  143. package/dist/io/xlsx/images.js +24 -25
  144. package/dist/io/xlsx/namespaces.d.ts +16 -0
  145. package/dist/io/xlsx/namespaces.js +7 -0
  146. package/dist/io/xlsx/package-plan.d.ts +23 -3
  147. package/dist/io/xlsx/package-plan.js +66 -25
  148. package/dist/io/xlsx/part-names.d.ts +41 -0
  149. package/dist/io/xlsx/part-names.js +46 -0
  150. package/dist/io/xlsx/pivot.js +8 -7
  151. package/dist/io/xlsx/read-parts.d.ts +59 -0
  152. package/dist/io/xlsx/read-parts.js +252 -0
  153. package/dist/io/xlsx/read-pivot.js +3 -1
  154. package/dist/io/xlsx/read-repair.d.ts +26 -0
  155. package/dist/io/xlsx/read-repair.js +39 -0
  156. package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +1 -1
  157. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +7 -3
  158. package/dist/io/xlsx/read-rows.d.ts +18 -5
  159. package/dist/io/xlsx/read-rows.js +50 -37
  160. package/dist/io/xlsx/read-shared-strings.js +5 -3
  161. package/dist/io/xlsx/read-styles.d.ts +1 -6
  162. package/dist/io/xlsx/read-styles.js +65 -109
  163. package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
  164. package/dist/io/xlsx/read-workbook-xml.js +170 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +11 -1
  166. package/dist/io/xlsx/read-worksheet.js +51 -193
  167. package/dist/io/xlsx/read.d.ts +5 -13
  168. package/dist/io/xlsx/read.js +111 -436
  169. package/dist/io/xlsx/rich-text.js +1 -1
  170. package/dist/io/xlsx/row-position.d.ts +18 -0
  171. package/dist/io/xlsx/row-position.js +11 -0
  172. package/dist/io/xlsx/row-xml.d.ts +128 -0
  173. package/dist/io/xlsx/row-xml.js +203 -0
  174. package/dist/io/xlsx/shared-formulas.js +2 -2
  175. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  176. package/dist/io/xlsx/sheet-properties.js +166 -37
  177. package/dist/io/xlsx/style-elements.d.ts +43 -0
  178. package/dist/io/xlsx/style-elements.js +156 -0
  179. package/dist/io/xlsx/styles.d.ts +12 -3
  180. package/dist/io/xlsx/styles.js +5 -204
  181. package/dist/io/xlsx/tables.js +11 -14
  182. package/dist/io/xlsx/theme-xml.d.ts +26 -4
  183. package/dist/io/xlsx/theme-xml.js +107 -69
  184. package/dist/io/xlsx/threaded-comments.d.ts +8 -10
  185. package/dist/io/xlsx/threaded-comments.js +3 -1
  186. package/dist/io/xlsx/workbook-xml.d.ts +54 -4
  187. package/dist/io/xlsx/workbook-xml.js +84 -57
  188. package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
  189. package/dist/io/xlsx/worksheet-xml.js +59 -242
  190. package/dist/io/xlsx/write-stream.d.ts +71 -16
  191. package/dist/io/xlsx/write-stream.js +79 -41
  192. package/dist/io/xlsx/write.d.ts +6 -2
  193. package/dist/io/xlsx/write.js +217 -153
  194. package/dist/rel-type.d.ts +16 -0
  195. package/dist/rel-type.js +6 -0
  196. package/dist/token-set.d.ts +28 -0
  197. package/dist/token-set.js +7 -0
  198. package/dist/vba/bytes.d.ts +14 -5
  199. package/dist/vba/bytes.js +16 -6
  200. package/dist/vba/cfb-format.d.ts +32 -0
  201. package/dist/vba/cfb-format.js +13 -0
  202. package/dist/vba/cfb-writer.d.ts +9 -0
  203. package/dist/vba/cfb-writer.js +28 -25
  204. package/dist/vba/cfb.d.ts +13 -1
  205. package/dist/vba/cfb.js +73 -57
  206. package/dist/vba/codepage.js +2 -1
  207. package/dist/vba/errors.d.ts +7 -0
  208. package/dist/vba/ms-ovba.js +79 -23
  209. package/dist/vba/project-editor.js +35 -40
  210. package/dist/vba/project.d.ts +11 -1
  211. package/dist/vba/project.js +40 -21
  212. package/dist/vba/vba-encoding.js +5 -6
  213. package/dist/xml/xml-attrs.d.ts +37 -0
  214. package/dist/xml/xml-attrs.js +30 -0
  215. package/dist/xml/xml-chars.d.ts +40 -0
  216. package/dist/xml/xml-chars.js +12 -0
  217. package/dist/xml/xml-namespaces.d.ts +31 -0
  218. package/dist/xml/xml-namespaces.js +50 -0
  219. package/dist/xml/xml-read.d.ts +128 -81
  220. package/dist/xml/xml-read.js +161 -178
  221. package/dist/xml/xml-scan.d.ts +78 -0
  222. package/dist/xml/xml-scan.js +180 -0
  223. package/dist/xml/xml.d.ts +29 -28
  224. package/dist/xml/xml.js +24 -24
  225. package/package.json +13 -9
@@ -1,19 +1,68 @@
1
+ import { type CellPosition, type GridRect } from './address.ts';
1
2
  /**
2
- * Where a 1-based coordinate on `axis` lands after `count` lines are deleted at `start` and the
3
- * replacement lines shift what follows by `delta`: before the edit it stays put, at or after the
4
- * edited span it shifts, and inside a deleted span it clamps to the cut line. The clamp is the best
5
- * effort for a geometry straddling the cut; a caller that must instead *drop* what a delete swallowed
6
- * whole tests {@link isDeletedSpan} first.
3
+ * One structural edit to an axis: `count` lines removed at the 1-based `start`, with everything
4
+ * after the removed span moved by `delta`. `delta` is the *net* movement, so a pure insert of two
5
+ * lines is `{count: 0, delta: 2}` and a pure delete of three is `{count: 3, delta: -3}`.
6
+ *
7
+ * The four travel as one value because they describe one edit, and because three of them are
8
+ * `number`: passed positionally, two adjacent ones transposed compiles, typechecks, lints, and moves
9
+ * a merge, a dropdown, a highlight or a comment anchor onto cells the author never chose -- silently,
10
+ * since nothing about the resulting file is malformed. A named field cannot be transposed.
11
+ */
12
+ export interface AxisSplice {
13
+ /** The axis the lines were spliced on. */
14
+ readonly axis: 'row' | 'col';
15
+ /** 1-based first line of the removed span. */
16
+ readonly start: number;
17
+ /** How many lines were removed. */
18
+ readonly count: number;
19
+ /** Net movement of every line after the removed span. */
20
+ readonly delta: number;
21
+ }
22
+ /**
23
+ * Where a 1-based coordinate on the spliced axis lands: before the edit it stays put, at or after
24
+ * the edited span it shifts by `delta`, and inside a deleted span it clamps to the cut line. The
25
+ * clamp is the best effort for a geometry straddling the cut; a caller that must instead *drop*
26
+ * what a delete swallowed whole tests {@link isDeletedSpan} first.
7
27
  *
8
28
  * A shift never leaves the grid: the result is clamped to the axis's last line, so a region already
9
29
  * touching the bottom (or the right edge) keeps its edge there instead of naming a line the format
10
30
  * has no room for. That shrinks such a region by what it could not move, which is the lesser of the
11
31
  * two evils and is what Excel does to the same region on the same edit.
12
32
  */
13
- export declare function shiftIndex(v: number, start: number, count: number, delta: number, axis: 'row' | 'col'): number;
33
+ export declare function shiftIndex(v: number, splice: AxisSplice): number;
14
34
  /**
15
- * Whether the inclusive span `lo..hi` lies entirely within the `count` lines deleted at `start`: the
16
- * test that separates "this moved" from "this is gone". A single coordinate is the degenerate span
35
+ * Whether the inclusive span `lo..hi` lies entirely within the deleted lines: the test that
36
+ * separates "this moved" from "this is gone". A single coordinate is the degenerate span
17
37
  * `lo === hi`.
18
38
  */
19
- export declare function isDeletedSpan(lo: number, hi: number, start: number, count: number): boolean;
39
+ export declare function isDeletedSpan(lo: number, hi: number, splice: AxisSplice): boolean;
40
+ /**
41
+ * Both edges of a region's span on the spliced axis, moved together, or `undefined` when the delete
42
+ * swallowed the span whole. The two answers are exclusive on purpose: a span straddling the cut
43
+ * clamps and survives, one wholly inside it is dropped, and a caller must not clamp its way out of
44
+ * a drop.
45
+ *
46
+ * The one-axis form, for a region whose *other* axis is not a number the caller holds -- a `sqref`
47
+ * area may be unbounded across the axis it is not being spliced on, and re-encoding it as bounded
48
+ * would rewrite the file's own spelling. Callers holding a real rectangle want {@link shiftRect}.
49
+ */
50
+ export declare function shiftSpan(lo: number, hi: number, splice: AxisSplice): {
51
+ lo: number;
52
+ hi: number;
53
+ } | undefined;
54
+ /**
55
+ * A rectangle re-anchored through the splice, or `undefined` when the delete swallowed it whole.
56
+ * The unspliced axis passes through untouched; both edges of the spliced axis move and clamp.
57
+ *
58
+ * This projection -- pick the spliced axis's two edges out of the rectangle, ask whether they
59
+ * survived, move them, put the rectangle back together -- is the shape every range-bound thing in
60
+ * the model re-anchors by, and writing it per caller is how the axis ternary gets inverted in one
61
+ * of them.
62
+ */
63
+ export declare function shiftRect(rect: GridRect, splice: AxisSplice): GridRect | undefined;
64
+ /**
65
+ * The degenerate one-coordinate form of {@link shiftRect}: a cell that moves, or `undefined` when
66
+ * the delete took the line it sat on. The coordinate off the spliced axis passes through.
67
+ */
68
+ export declare function shiftPoint(point: CellPosition, splice: AxisSplice): CellPosition | undefined;
@@ -1,8 +1,30 @@
1
1
  import { MAX_COLUMN, MAX_ROW } from './address.js';
2
- export function shiftIndex(v, start, count, delta, axis) {
2
+ export function shiftIndex(v, splice) {
3
+ const { start, count, delta } = splice;
3
4
  const moved = v < start ? v : v >= start + count ? v + delta : start;
4
- return Math.min(moved, axis === 'row' ? MAX_ROW : MAX_COLUMN);
5
+ return Math.min(moved, splice.axis === 'row' ? MAX_ROW : MAX_COLUMN);
5
6
  }
6
- export function isDeletedSpan(lo, hi, start, count) {
7
- return lo >= start && hi < start + count;
7
+ export function isDeletedSpan(lo, hi, splice) {
8
+ return lo >= splice.start && hi < splice.start + splice.count;
9
+ }
10
+ export function shiftSpan(lo, hi, splice) {
11
+ if (isDeletedSpan(lo, hi, splice))
12
+ return undefined;
13
+ return { lo: shiftIndex(lo, splice), hi: shiftIndex(hi, splice) };
14
+ }
15
+ export function shiftRect(rect, splice) {
16
+ const rowAxis = splice.axis === 'row';
17
+ const moved = shiftSpan(rowAxis ? rect.top : rect.left, rowAxis ? rect.bottom : rect.right, splice);
18
+ if (moved === undefined)
19
+ return undefined;
20
+ return rowAxis
21
+ ? { ...rect, top: moved.lo, bottom: moved.hi }
22
+ : { ...rect, left: moved.lo, right: moved.hi };
23
+ }
24
+ export function shiftPoint(point, splice) {
25
+ const line = splice.axis === 'row' ? point.row : point.col;
26
+ if (isDeletedSpan(line, line, splice))
27
+ return undefined;
28
+ const moved = shiftIndex(line, splice);
29
+ return splice.axis === 'row' ? { col: point.col, row: moved } : { col: moved, row: point.row };
8
30
  }
@@ -18,7 +18,7 @@ export declare const PX_TO_EMU = 9525;
18
18
  * defaults to `oneCell` when the attribute is omitted. */
19
19
  export type ImageEditAs = 'oneCell' | 'twoCell' | 'absolute';
20
20
  /** Narrow a raw `<xdr:twoCellAnchor editAs>` token to a known {@link ImageEditAs}. */
21
- export declare function isImageEditAs(value: string): value is ImageEditAs;
21
+ export declare const isImageEditAs: (value: string) => value is ImageEditAs;
22
22
  /** A fixed image size in EMUs: the extent of a one-cell anchor, which pixel dimensions convert into
23
23
  * via {@link PX_TO_EMU}. */
24
24
  export interface Extent {
@@ -99,15 +99,19 @@ export interface WorksheetImages {
99
99
  readonly background: WorkbookImage | undefined;
100
100
  }
101
101
  /**
102
- * The id under which `image` is already registered in `media`, or `undefined` if it is not.
102
+ * A picture's content identity as a map key: its kind, its length, and a digest of its bytes.
103
103
  *
104
- * Content-addressed rather than reference-addressed: two byte-identical pictures are one picture,
105
- * however they reached the registry. This is what keeps repeated imports from growing the media
106
- * list without bound (the same logo carried onto twenty sheets registers once), and it is why the
107
- * comparison is over bytes rather than object identity, which a picture arriving from another
108
- * workbook would never satisfy.
104
+ * **Content-addressed, not reference-addressed.** Two byte-identical pictures are one picture,
105
+ * however they reached the registry. That is what keeps repeated imports from growing the media list
106
+ * without bound, so the same logo carried onto twenty sheets registers once, and it is why identity
107
+ * cannot be object identity: a picture arriving from another workbook would never satisfy that.
109
108
  *
110
- * The length check comes first and short-circuits, so pictures of different sizes never reach the
111
- * byte loop; only same-extension, same-length candidates are compared in full.
109
+ * **A key rather than a scan**, which is the part that changed. The comparison used to walk the whole
110
+ * media list byte by byte per candidate, and importing a sheet's pictures asks it once per anchored
111
+ * image, so merging a workbook of fifty distinct megabyte images compared bytes fifty times over
112
+ * fifty candidates. The workbook indexes each picture as it is registered instead.
113
+ *
114
+ * SHA-512 rather than a cheap checksum because a collision here silently substitutes one picture for
115
+ * another; the length is in the key as well, so a collision would have to match that too.
112
116
  */
113
- export declare function findRegisteredImage(media: readonly WorkbookImage[], image: WorkbookImage): number | undefined;
117
+ export declare function imageContentKey(image: WorkbookImage): string;
@@ -1,8 +1,8 @@
1
+ import { hex } from '../hex.js';
2
+ import { sha512 } from '../sha512.js';
3
+ import { tokenSet } from '../token-set.js';
1
4
  export const PX_TO_EMU = 9525;
2
- const IMAGE_EDIT_AS = { oneCell: true, twoCell: true, absolute: true };
3
- export function isImageEditAs(value) {
4
- return Object.hasOwn(IMAGE_EDIT_AS, value);
5
- }
5
+ export const isImageEditAs = tokenSet({ oneCell: true, twoCell: true, absolute: true });
6
6
  export function isOneCellAnchor(anchor) {
7
7
  return 'ext' in anchor;
8
8
  }
@@ -42,9 +42,9 @@ export function normalizeImageExtension(extension, data) {
42
42
  }
43
43
  return sniffImageExtension(data);
44
44
  }
45
- export function findRegisteredImage(media, image) {
46
- const index = media.findIndex((held) => held.extension === image.extension &&
47
- held.data.length === image.data.length &&
48
- held.data.every((byte, i) => byte === image.data[i]));
49
- return index === -1 ? undefined : index;
45
+ export function imageContentKey(image) {
46
+ let digest = '';
47
+ for (const byte of sha512(image.data))
48
+ digest += hex(byte, 2);
49
+ return `${image.extension}:${image.data.length}:${digest}`;
50
50
  }
@@ -1,6 +1,12 @@
1
1
  /**
2
- * Keys the codec-only operations on `Workbook` and `Worksheet` (see `WorkbookInternals` /
3
- * `WorksheetInternals`, declared beside their classes).
2
+ * Keys the operations the library's own machinery may perform on a published class and a caller may
3
+ * not (see `WorkbookInternals` / `WorksheetInternals`, declared beside their classes).
4
+ *
5
+ * Not only the model: `WorksheetStreamWriter` hangs its construction and its row-flush plumbing off
6
+ * the same key, on the static side and the instance side respectively, because that class has the
7
+ * identical problem one layer up. Its constructor took the writer's style registry and its
8
+ * `flushedSheet()` returned the writer's flushed-row record, so seven internal types were named by a
9
+ * published signature and none of them was a type a consumer could write down.
4
10
  */
5
11
  export declare const INTERNAL: unique symbol;
6
12
  /**
@@ -28,3 +28,26 @@ export declare const MAX_ROW_HEIGHT = 409.5;
28
28
  * value for it is a bug rather than a shortcut.
29
29
  */
30
30
  export declare const MAX_COLUMN_WIDTH = 255;
31
+ /**
32
+ * The longest sheet name Excel accepts, in UTF-16 code units. A longer one is refused outright rather
33
+ * than truncated: a truncated name silently collides with its neighbours.
34
+ */
35
+ export declare const MAX_SHEET_NAME_LENGTH = 31;
36
+ /**
37
+ * The characters Excel forbids anywhere in a sheet name. A name may also not begin or end with an
38
+ * apostrophe, which this pattern does not express because the position is what makes it illegal: a
39
+ * sheet-qualified reference quotes the name with apostrophes, so one at either edge cannot be told
40
+ * from the quoting.
41
+ */
42
+ export declare const INVALID_SHEET_NAME_CHARS: RegExp;
43
+ /** The longest table name Excel accepts, in UTF-16 code units. */
44
+ export declare const MAX_TABLE_NAME_LENGTH = 255;
45
+ /**
46
+ * Excel's table-name grammar: start with a letter, underscore, or backslash; every later character a
47
+ * letter, digit, period, or underscore. Unicode letters and digits are allowed.
48
+ *
49
+ * Excel additionally forbids a name that *is* a cell reference (`A1`, `R1C1`), which this pattern
50
+ * deliberately does not: the regression corpus treats cell-reference-shaped names like `T1` as valid
51
+ * table names, so enforcing that rule would reject a fixture the contract accepts.
52
+ */
53
+ export declare const TABLE_NAME_PATTERN: RegExp;
@@ -1,2 +1,6 @@
1
1
  export const MAX_ROW_HEIGHT = 409.5;
2
2
  export const MAX_COLUMN_WIDTH = 255;
3
+ export const MAX_SHEET_NAME_LENGTH = 31;
4
+ export const INVALID_SHEET_NAME_CHARS = /[*?:\\/[\]]/;
5
+ export const MAX_TABLE_NAME_LENGTH = 255;
6
+ export const TABLE_NAME_PATTERN = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
@@ -0,0 +1,31 @@
1
+ import { type GridRect } from './address.ts';
2
+ import type { MergeRect } from './merge.ts';
3
+ /**
4
+ * A row-banded index over a sheet's merged regions. Holds the region list by reference and never
5
+ * mutates it: the sheet stays the owner, reports what it adds, and says when it has rewritten the
6
+ * list wholesale (a splice, an unmerge, a model assignment), which the index answers by rebuilding
7
+ * on the next query rather than by tracking the edit.
8
+ */
9
+ export declare class MergeIndex {
10
+ #private;
11
+ constructor(rects: readonly MergeRect[]);
12
+ /** Note a region added to the list. Ignored while stale: the rebuild reads the list itself. */
13
+ note(rect: MergeRect): void;
14
+ /** Report that the region list has been rewritten from outside this index. */
15
+ invalidate(): void;
16
+ /**
17
+ * The region overlapping `rect`, or `undefined` when the rectangle is free. Which of several
18
+ * overlapping regions is returned is unspecified beyond being one of them; a sheet admits no
19
+ * overlap, so at most one ever applies.
20
+ */
21
+ overlapping(rect: GridRect): MergeRect | undefined;
22
+ /**
23
+ * Resolve a position to the top-left of the merged region covering it, or to itself when no region
24
+ * does. Only fully-bounded regions participate: an unbounded whole-row/column merge carries no
25
+ * rectangle and so resolves nothing.
26
+ */
27
+ masterOf(row: number, col: number): {
28
+ row: number;
29
+ col: number;
30
+ };
31
+ }
@@ -0,0 +1,60 @@
1
+ import { rectsOverlap } from './address.js';
2
+ const BAND_ROWS = 64;
3
+ function bandOf(row) {
4
+ return Math.floor(row / BAND_ROWS);
5
+ }
6
+ export class MergeIndex {
7
+ #rects;
8
+ #bands = new Map();
9
+ #tallest = 0;
10
+ #stale = true;
11
+ constructor(rects) {
12
+ this.#rects = rects;
13
+ }
14
+ note(rect) {
15
+ if (!this.#stale)
16
+ this.#place(rect);
17
+ }
18
+ invalidate() {
19
+ this.#stale = true;
20
+ }
21
+ overlapping(rect) {
22
+ this.#refresh();
23
+ const first = bandOf(Math.max(1, rect.top - this.#tallest + 1));
24
+ const last = bandOf(rect.bottom);
25
+ for (let band = first; band <= last; band++) {
26
+ const bucket = this.#bands.get(band);
27
+ if (bucket === undefined)
28
+ continue;
29
+ for (const candidate of bucket) {
30
+ if (rectsOverlap(candidate, rect))
31
+ return candidate;
32
+ }
33
+ }
34
+ return undefined;
35
+ }
36
+ masterOf(row, col) {
37
+ const covering = this.overlapping({ top: row, left: col, bottom: row, right: col });
38
+ return covering === undefined ? { row, col } : { row: covering.top, col: covering.left };
39
+ }
40
+ #place(rect) {
41
+ const height = rect.bottom - rect.top + 1;
42
+ if (height > this.#tallest)
43
+ this.#tallest = height;
44
+ const band = bandOf(rect.top);
45
+ const bucket = this.#bands.get(band);
46
+ if (bucket === undefined)
47
+ this.#bands.set(band, [rect]);
48
+ else
49
+ bucket.push(rect);
50
+ }
51
+ #refresh() {
52
+ if (!this.#stale)
53
+ return;
54
+ this.#stale = false;
55
+ this.#bands = new Map();
56
+ this.#tallest = 0;
57
+ for (const rect of this.#rects)
58
+ this.#place(rect);
59
+ }
60
+ }
@@ -1,17 +1,8 @@
1
1
  import { type GridRect } from './address.ts';
2
2
  import type { Cell } from './cell.ts';
3
+ import { type AxisSplice } from './grid-shift.ts';
3
4
  /** A merged region, as the {@link GridRect} every range-shaped thing in the library is. */
4
5
  export type MergeRect = GridRect;
5
- /**
6
- * Resolve a position to the master (top-left) of the merged region covering it, or to itself when no
7
- * region does. First covering region wins; `Worksheet.mergeCells` rejects overlaps, so at most one
8
- * region ever applies. Only fully-bounded rects participate: an unbounded whole-row/column merge
9
- * carries no rect and so resolves nothing.
10
- */
11
- export declare function masterOf(rects: readonly MergeRect[], row: number, col: number): {
12
- row: number;
13
- col: number;
14
- };
15
6
  /**
16
7
  * Drop any value already sitting in a merge's covered non-anchor cells, keeping only the top-left
17
8
  * anchor, the collapse Excel performs on merge. A leftover covered value would serialise as a
@@ -25,12 +16,12 @@ export declare function clearCoveredValues(rows: Map<number, Map<number, Cell>>,
25
16
  * that can exist contributes no rectangle, the reader's rule for every other foreign attribute. */
26
17
  export declare function decodeSqrefRects(sqref: string): MergeRect[];
27
18
  /**
28
- * Re-anchor an OOXML `sqref` through a splice of `count` lines at `start` on `axis`, the inverse of
29
- * {@link decodeSqrefRects}. Returns `undefined` when the splice deleted every area the `sqref` named:
30
- * an empty `sqref` is not writable, so the entry holding it goes too.
19
+ * Re-anchor an OOXML `sqref` through a splice, the inverse of {@link decodeSqrefRects}. Returns
20
+ * `undefined` when the splice deleted every area the `sqref` named: an empty `sqref` is not
21
+ * writable, so the entry holding it goes too.
31
22
  *
32
23
  * Each space-separated area shifts on its own, and one the splice does not move is returned as the
33
24
  * *original text*. That matters for a file the library did not author: `B:B` and `B1:B1048576` decode
34
25
  * identically, so a re-encode would rewrite a foreign spelling and cost the byte-clean round trip.
35
26
  */
36
- export declare function shiftSqref(sqref: string, axis: 'row' | 'col', start: number, count: number, delta: number): string | undefined;
27
+ export declare function shiftSqref(sqref: string, splice: AxisSplice): string | undefined;
@@ -1,24 +1,15 @@
1
1
  import { encodeCornerRef, tryDecodeRange } from './address.js';
2
- import { isDeletedSpan, shiftIndex } from './grid-shift.js';
3
- export function masterOf(rects, row, col) {
4
- for (const rect of rects) {
5
- if (row >= rect.top && row <= rect.bottom && col >= rect.left && col <= rect.right) {
6
- return { row: rect.top, col: rect.left };
7
- }
8
- }
9
- return { row, col };
10
- }
2
+ import { shiftSpan } from './grid-shift.js';
11
3
  export function clearCoveredValues(rows, rect) {
12
- for (let row = rect.top; row <= rect.bottom; row++) {
13
- const cols = rows.get(row);
14
- if (cols === undefined)
4
+ for (const [row, cols] of rows) {
5
+ if (row < rect.top || row > rect.bottom)
15
6
  continue;
16
- for (let col = rect.left; col <= rect.right; col++) {
7
+ for (const [col, covered] of cols) {
8
+ if (col < rect.left || col > rect.right)
9
+ continue;
17
10
  if (row === rect.top && col === rect.left)
18
11
  continue;
19
- const covered = cols.get(col);
20
- if (covered !== undefined)
21
- covered.value = null;
12
+ covered.value = null;
22
13
  }
23
14
  }
24
15
  }
@@ -40,33 +31,33 @@ export function decodeSqrefRects(sqref) {
40
31
  }
41
32
  return rects;
42
33
  }
43
- export function shiftSqref(sqref, axis, start, count, delta) {
34
+ export function shiftSqref(sqref, splice) {
44
35
  const areas = [];
45
36
  for (const area of sqref.split(/\s+/)) {
46
37
  if (area === '')
47
38
  continue;
48
- const shifted = shiftSqrefArea(area, axis, start, count, delta);
39
+ const shifted = shiftSqrefArea(area, splice);
49
40
  if (shifted !== undefined)
50
41
  areas.push(shifted);
51
42
  }
52
43
  return areas.length > 0 ? areas.join(' ') : undefined;
53
44
  }
54
- function shiftSqrefArea(area, axis, start, count, delta) {
45
+ function shiftSqrefArea(area, splice) {
55
46
  const decoded = tryDecodeRange(area);
56
47
  if (decoded === undefined)
57
48
  return area;
58
49
  const { top, left, bottom, right } = decoded;
59
- const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
50
+ const rowAxis = splice.axis === 'row';
51
+ const [lo, hi] = rowAxis ? [top, bottom] : [left, right];
60
52
  if (lo === undefined || hi === undefined)
61
53
  return area;
62
- if (isDeletedSpan(lo, hi, start, count))
54
+ const moved = shiftSpan(lo, hi, splice);
55
+ if (moved === undefined)
63
56
  return undefined;
64
- const movedLo = shiftIndex(lo, start, count, delta, axis);
65
- const movedHi = shiftIndex(hi, start, count, delta, axis);
66
- if (movedLo === lo && movedHi === hi)
57
+ if (moved.lo === lo && moved.hi === hi)
67
58
  return area;
68
- const [tl, br] = axis === 'row'
69
- ? [encodeCornerRef(left, movedLo), encodeCornerRef(right, movedHi)]
70
- : [encodeCornerRef(movedLo, top), encodeCornerRef(movedHi, bottom)];
59
+ const [tl, br] = rowAxis
60
+ ? [encodeCornerRef(left, moved.lo), encodeCornerRef(right, moved.hi)]
61
+ : [encodeCornerRef(moved.lo, top), encodeCornerRef(moved.hi, bottom)];
71
62
  return area.includes(':') ? `${tl}:${br}` : tl;
72
63
  }
@@ -1,3 +1,4 @@
1
+ import type { AssertNever } from './internal.ts';
1
2
  /**
2
3
  * Paper orientation, as `<pageSetup orientation>` carries it.
3
4
  *
@@ -7,11 +8,11 @@
7
8
  */
8
9
  export type PageOrientation = 'portrait' | 'landscape';
9
10
  /** Narrow a raw `<pageSetup orientation>` token to a known {@link PageOrientation}. */
10
- export declare function isPageOrientation(value: string): value is PageOrientation;
11
+ export declare const isPageOrientation: (value: string) => value is PageOrientation;
11
12
  /** The order pages are numbered and printed in across a sheet wider and taller than one page. */
12
13
  export type PageOrder = 'downThenOver' | 'overThenDown';
13
14
  /** Narrow a raw `<pageSetup pageOrder>` token to a known {@link PageOrder}. */
14
- export declare function isPageOrder(value: string): value is PageOrder;
15
+ export declare const isPageOrder: (value: string) => value is PageOrder;
15
16
  /**
16
17
  * Print-scaling and orientation settings. These map onto two OOXML elements: `fitToPage` is the
17
18
  * `<pageSetUpPr>` flag (a `<sheetPr>` child) that switches Excel from fixed-zoom to fit-to-page
@@ -47,6 +48,61 @@ export interface PageSetup {
47
48
  */
48
49
  printerSettings?: Uint8Array;
49
50
  }
51
+ /**
52
+ * How one `<pageSetup>` attribute encodes: which model key it is and what kind of value it carries.
53
+ *
54
+ * Format-blind on purpose, the same way {@link AlignmentFacet} is: `PageSetup` is a core type and
55
+ * the layering gate forbids core importing a serialisation, so the table states what an attribute
56
+ * *is* and each codec supplies the reading and the writing off the `kind`. The OOXML attribute name
57
+ * is the model key throughout, so it is not restated.
58
+ */
59
+ export type PageSetupFacet = {
60
+ readonly key: 'paperSize' | 'scale' | 'fitToWidth' | 'fitToHeight';
61
+ /** A non-negative integer: a page count, a percentage, or a paper-size id. */
62
+ readonly kind: 'count';
63
+ } | {
64
+ readonly key: 'pageOrder' | 'orientation';
65
+ readonly kind: 'token';
66
+ /** The enumeration guard, and what to call it in the error when a value fails it. */
67
+ readonly isValid: (value: string) => boolean;
68
+ readonly label: string;
69
+ };
70
+ /**
71
+ * The six `<pageSetup>` attributes, declared once, in CT_PageSetup order. Both directions key off
72
+ * this list, so an attribute written but not read (it survives a re-write and vanishes on load) or
73
+ * read but not written is a compile error rather than something a reviewer has to notice.
74
+ */
75
+ export declare const PAGE_SETUP_FACETS: readonly [{
76
+ readonly key: 'paperSize';
77
+ readonly kind: 'count';
78
+ }, {
79
+ readonly key: 'scale';
80
+ readonly kind: 'count';
81
+ }, {
82
+ readonly key: 'fitToWidth';
83
+ readonly kind: 'count';
84
+ }, {
85
+ readonly key: 'fitToHeight';
86
+ readonly kind: 'count';
87
+ }, {
88
+ readonly key: 'pageOrder';
89
+ readonly kind: 'token';
90
+ readonly isValid: (value: string) => value is PageOrder;
91
+ readonly label: 'page order';
92
+ }, {
93
+ readonly key: 'orientation';
94
+ readonly kind: 'token';
95
+ readonly isValid: (value: string) => value is PageOrientation;
96
+ readonly label: 'page orientation';
97
+ }];
98
+ /**
99
+ * Compile-time proof that {@link PAGE_SETUP_FACETS} covers every `<pageSetup>` attribute.
100
+ *
101
+ * `fitToPage` and `printerSettings` are excluded because neither is one: `fitToPage` is a
102
+ * `<sheetPr>` child's flag and `printerSettings` is the blob behind an `r:id`, so both are written
103
+ * and read somewhere else entirely and a table entry for them would describe nothing.
104
+ */
105
+ export type EveryPageSetupFacetIsDeclared = AssertNever<Exclude<keyof PageSetup, 'fitToPage' | 'printerSettings' | (typeof PAGE_SETUP_FACETS)[number]['key']>>;
50
106
  /**
51
107
  * Print-toggle flags from the `<printOptions>` element. Each maps to a boolean OOXML attribute that
52
108
  * defaults false, except `gridLinesSet`, which defaults true and gates whether `gridLines` is
@@ -65,6 +121,11 @@ export interface PrintOptions {
65
121
  /** Whether the `gridLines` flag is authoritative; when `false`, Excel ignores `gridLines`. */
66
122
  gridLinesSet?: boolean;
67
123
  }
124
+ /** The `<printOptions>` flags, in CT_PrintOptions attribute order. Each is a plain OOXML boolean, so
125
+ * the list is the whole of what either direction needs to know. */
126
+ export declare const PRINT_OPTION_FLAGS: readonly ["horizontalCentered", "verticalCentered", "headings", "gridLines", "gridLinesSet"];
127
+ /** Compile-time proof that {@link PRINT_OPTION_FLAGS} covers every {@link PrintOptions} flag. */
128
+ export type EveryPrintOptionFlagIsDeclared = AssertNever<Exclude<keyof PrintOptions, (typeof PRINT_OPTION_FLAGS)[number]>>;
68
129
  /**
69
130
  * A manual page break (`<brk>`). For a row break, `id` is the row the layout splits *before*; for a
70
131
  * column break it is the column. `max` bounds the break's extent across the other axis (Excel writes
@@ -92,6 +153,10 @@ export interface PageMargins {
92
153
  header?: number;
93
154
  footer?: number;
94
155
  }
156
+ /** The `<pageMargins>` sides, in the order CT_PageMargins declares them. */
157
+ export declare const MARGIN_SIDES: readonly ["left", "right", "top", "bottom", "header", "footer"];
158
+ /** Compile-time proof that {@link MARGIN_SIDES} covers every {@link PageMargins} side. */
159
+ export type EveryMarginSideIsDeclared = AssertNever<Exclude<keyof PageMargins, (typeof MARGIN_SIDES)[number]>>;
95
160
  /**
96
161
  * Page header/footer text, one string per page class. Excel only honours the even- and
97
162
  * first-page variants when the writer also sets the gating flags (`differentOddEven`,
@@ -106,3 +171,8 @@ export interface HeaderFooter {
106
171
  firstHeader?: string;
107
172
  firstFooter?: string;
108
173
  }
174
+ /** The `<headerFooter>` children, in CT_HeaderFooter child order. The element name is the model key
175
+ * throughout, so one list serves the reader's capture, the reader's commit, and the writer. */
176
+ export declare const HEADER_FOOTER_ELEMENTS: readonly ["oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter"];
177
+ /** Compile-time proof that {@link HEADER_FOOTER_ELEMENTS} covers every {@link HeaderFooter} slot. */
178
+ export type EveryHeaderFooterElementIsDeclared = AssertNever<Exclude<keyof HeaderFooter, (typeof HEADER_FOOTER_ELEMENTS)[number]>>;
@@ -1,8 +1,34 @@
1
- const PAGE_ORIENTATIONS = { portrait: true, landscape: true };
2
- export function isPageOrientation(value) {
3
- return Object.hasOwn(PAGE_ORIENTATIONS, value);
4
- }
5
- const PAGE_ORDERS = { downThenOver: true, overThenDown: true };
6
- export function isPageOrder(value) {
7
- return Object.hasOwn(PAGE_ORDERS, value);
8
- }
1
+ import { tokenSet } from '../token-set.js';
2
+ export const isPageOrientation = tokenSet({ portrait: true, landscape: true });
3
+ export const isPageOrder = tokenSet({ downThenOver: true, overThenDown: true });
4
+ export const PAGE_SETUP_FACETS = [
5
+ { key: 'paperSize', kind: 'count' },
6
+ { key: 'scale', kind: 'count' },
7
+ { key: 'fitToWidth', kind: 'count' },
8
+ { key: 'fitToHeight', kind: 'count' },
9
+ { key: 'pageOrder', kind: 'token', isValid: isPageOrder, label: 'page order' },
10
+ { key: 'orientation', kind: 'token', isValid: isPageOrientation, label: 'page orientation' },
11
+ ];
12
+ export const PRINT_OPTION_FLAGS = [
13
+ 'horizontalCentered',
14
+ 'verticalCentered',
15
+ 'headings',
16
+ 'gridLines',
17
+ 'gridLinesSet',
18
+ ];
19
+ export const MARGIN_SIDES = [
20
+ 'left',
21
+ 'right',
22
+ 'top',
23
+ 'bottom',
24
+ 'header',
25
+ 'footer',
26
+ ];
27
+ export const HEADER_FOOTER_ELEMENTS = [
28
+ 'oddHeader',
29
+ 'oddFooter',
30
+ 'evenHeader',
31
+ 'evenFooter',
32
+ 'firstHeader',
33
+ 'firstFooter',
34
+ ];
@@ -18,7 +18,7 @@ export interface ParsedPivotField {
18
18
  * `unknown` covers a `type` the file declares that is none of these. */
19
19
  export type PivotSourceKind = 'worksheet' | 'external' | 'consolidation' | 'scenario' | 'unknown';
20
20
  /** Narrow a raw `<cacheSource type>` token to a {@link PivotSourceKind} a file may declare. */
21
- export declare function isDeclarablePivotSourceKind(value: string): value is Exclude<PivotSourceKind, 'unknown'>;
21
+ export declare const isDeclarablePivotSourceKind: (value: string) => value is "consolidation" | "external" | "scenario" | "worksheet";
22
22
  /** Where a pivot cache draws its rows from. {@link kind} names the source type; {@link sheet} and
23
23
  * {@link ref} locate the range only when it is `worksheet` and are empty strings otherwise, so a
24
24
  * consumer can tell a genuinely non-worksheet source apart from a worksheet source that failed to