@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,16 +1,16 @@
1
- import { decodeRange, encodeAddress, tryDecodeCellRef } from './address.js';
1
+ import { boundedRect, decodeRange, encodeAddress, encodeRect, tryDecodeCellRef } from './address.js';
2
2
  import { shiftAutoFilter } from './autofilter.js';
3
3
  import { Cell, copyCellContent } from './cell.js';
4
4
  import { replaceContents } from './containers.js';
5
- import { isDeletedSpan, shiftIndex } from './grid-shift.js';
5
+ import { isDeletedSpan, shiftIndex, shiftRect } from './grid-shift.js';
6
6
  import { isOneCellAnchor } from './image.js';
7
+ import { positionalPlacements } from './row-input.js';
7
8
  import { isSharedFormulaValue } from './value.js';
8
9
  export class GridEdits {
9
10
  #rows;
10
11
  #rowProperties;
11
12
  #columns;
12
13
  #merges;
13
- #mergeRects;
14
14
  #tables;
15
15
  #images;
16
16
  #dataValidations;
@@ -22,7 +22,6 @@ export class GridEdits {
22
22
  this.#rowProperties = storage.rowProperties;
23
23
  this.#columns = storage.columns;
24
24
  this.#merges = storage.merges;
25
- this.#mergeRects = storage.mergeRects;
26
25
  this.#tables = storage.tables;
27
26
  this.#images = storage.images;
28
27
  this.#dataValidations = storage.dataValidations;
@@ -31,13 +30,15 @@ export class GridEdits {
31
30
  this.#autoFilter = storage.autoFilter;
32
31
  }
33
32
  spliceRows(start, count, inserted) {
34
- const delta = inserted.length - count;
33
+ const splice = { axis: 'row', start, count, delta: inserted.length - count };
35
34
  const shifted = new Map();
36
35
  for (const [row, cols] of this.#rows) {
37
36
  if (row < start)
38
37
  shifted.set(row, cols);
39
- else if (row >= start + count)
40
- shifted.set(row + delta, this.#relocateRow(cols, row + delta));
38
+ else if (row >= start + count) {
39
+ const dest = shiftIndex(row, splice);
40
+ shifted.set(dest, this.#relocateRow(cols, dest));
41
+ }
41
42
  }
42
43
  inserted.forEach((cols, i) => {
43
44
  shifted.set(start + i, this.#relocateRow(cols, start + i));
@@ -45,15 +46,16 @@ export class GridEdits {
45
46
  this.#rows.clear();
46
47
  for (const [row, cols] of shifted)
47
48
  this.#rows.set(row, cols);
48
- this.#shiftLineProperties(this.#rowProperties, start, count, delta);
49
- this.#shiftMerges('row', start, count, delta);
50
- this.#shiftTables('row', start, count, delta);
51
- this.#shiftImages('row', start, count, delta);
52
- this.#reanchorSharedFormulas('row', start, count, delta);
53
- this.#shiftRangeBoundOverlays('row', start, count, delta);
49
+ this.#shiftLineProperties(this.#rowProperties, splice);
50
+ this.#shiftMerges(splice);
51
+ this.#shiftTables(splice);
52
+ this.#shiftImages(splice);
53
+ this.#reanchorSharedFormulas(splice);
54
+ this.#shiftRangeBoundOverlays(splice);
54
55
  }
55
56
  spliceColumns(start, count, inserts) {
56
- const delta = inserts.length - count;
57
+ const splice = { axis: 'col', start, count, delta: inserts.length - count };
58
+ const shiftedRows = new Map();
57
59
  for (const [row, cols] of this.#rows) {
58
60
  const shifted = new Map();
59
61
  for (const [col, cell] of cols) {
@@ -61,37 +63,42 @@ export class GridEdits {
61
63
  shifted.set(col, cell);
62
64
  }
63
65
  else if (col >= start + count) {
64
- const dest = col + delta;
66
+ const dest = shiftIndex(col, splice);
65
67
  const moved = new Cell(row, dest);
66
68
  copyCellContent(cell, moved);
67
69
  shifted.set(dest, moved);
68
70
  }
69
71
  }
70
- inserts.forEach((values, i) => {
71
- const value = values[row - 1];
72
- if (value !== undefined) {
73
- const cell = new Cell(row, start + i);
74
- cell.value = value;
75
- shifted.set(start + i, cell);
76
- }
77
- });
78
- this.#rows.set(row, shifted);
72
+ shiftedRows.set(row, shifted);
79
73
  }
80
- this.#shiftLineProperties(this.#columns, start, count, delta);
81
- this.#shiftMerges('col', start, count, delta);
82
- this.#shiftTables('col', start, count, delta);
83
- this.#shiftImages('col', start, count, delta);
84
- this.#reanchorSharedFormulas('col', start, count, delta);
85
- this.#shiftRangeBoundOverlays('col', start, count, delta);
74
+ inserts.forEach((values, i) => {
75
+ for (const [row, value] of positionalPlacements(values)) {
76
+ const cell = new Cell(row, start + i);
77
+ cell.value = value;
78
+ let cols = shiftedRows.get(row);
79
+ if (cols === undefined) {
80
+ cols = new Map();
81
+ shiftedRows.set(row, cols);
82
+ }
83
+ cols.set(start + i, cell);
84
+ }
85
+ });
86
+ for (const [row, cols] of shiftedRows)
87
+ this.#rows.set(row, cols);
88
+ this.#shiftLineProperties(this.#columns, splice);
89
+ this.#shiftMerges(splice);
90
+ this.#shiftTables(splice);
91
+ this.#shiftImages(splice);
92
+ this.#reanchorSharedFormulas(splice);
93
+ this.#shiftRangeBoundOverlays(splice);
86
94
  }
87
- #shiftRangeBoundOverlays(axis, start, count, delta) {
88
- this.#dataValidations.shift(axis, start, count, delta);
89
- this.#conditionalFormattings.shift(axis, start, count, delta);
90
- this.#comments.shift(axis, start, count, delta);
95
+ #shiftRangeBoundOverlays(splice) {
96
+ this.#dataValidations.shift(splice);
97
+ this.#conditionalFormattings.shift(splice);
98
+ this.#comments.shift(splice);
91
99
  const filter = this.#autoFilter.get();
92
- if (filter !== undefined) {
93
- this.#autoFilter.set(shiftAutoFilter(filter, axis, start, count, delta));
94
- }
100
+ if (filter !== undefined)
101
+ this.#autoFilter.set(shiftAutoFilter(filter, splice));
95
102
  }
96
103
  #relocateRow(cols, destRow) {
97
104
  const moved = new Map();
@@ -107,7 +114,7 @@ export class GridEdits {
107
114
  }
108
115
  return moved;
109
116
  }
110
- #reanchorSharedFormulas(axis, start, count, delta) {
117
+ #reanchorSharedFormulas(splice) {
111
118
  for (const cols of this.#rows.values()) {
112
119
  for (const cell of cols.values()) {
113
120
  const value = cell.value;
@@ -116,9 +123,9 @@ export class GridEdits {
116
123
  const master = tryDecodeCellRef(value.sharedFormula);
117
124
  if (master === undefined)
118
125
  continue;
119
- const anchored = axis === 'row'
120
- ? encodeAddress(master.col, shiftIndex(master.row, start, count, delta, 'row'))
121
- : encodeAddress(shiftIndex(master.col, start, count, delta, 'col'), master.row);
126
+ const anchored = splice.axis === 'row'
127
+ ? encodeAddress(master.col, shiftIndex(master.row, splice))
128
+ : encodeAddress(shiftIndex(master.col, splice), master.row);
122
129
  if (anchored === value.sharedFormula)
123
130
  continue;
124
131
  const reanchored = { ...value, sharedFormula: anchored };
@@ -126,59 +133,51 @@ export class GridEdits {
126
133
  }
127
134
  }
128
135
  }
129
- #shiftLineProperties(map, start, count, delta) {
136
+ #shiftLineProperties(map, splice) {
130
137
  const shifted = new Map();
131
138
  for (const [index, value] of map) {
132
- if (index < start)
133
- shifted.set(index, value);
134
- else if (index >= start + count)
135
- shifted.set(index + delta, value);
139
+ if (isDeletedSpan(index, index, splice))
140
+ continue;
141
+ shifted.set(shiftIndex(index, splice), value);
136
142
  }
137
143
  map.clear();
138
144
  for (const [index, value] of shifted)
139
145
  map.set(index, value);
140
146
  }
141
- #shiftMerges(axis, start, count, delta) {
142
- const shift = (v) => shiftIndex(v, start, count, delta, axis);
147
+ #shiftMerges(splice) {
143
148
  const merges = [];
144
149
  const rects = [];
145
- for (const range of this.#merges) {
146
- const { top, left, bottom, right } = decodeRange(range);
147
- if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
150
+ for (const range of this.#merges.ranges) {
151
+ const decoded = boundedRect(decodeRange(range));
152
+ if (decoded === undefined) {
148
153
  merges.push(range);
149
154
  continue;
150
155
  }
151
- const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
152
- if (isDeletedSpan(lo, hi, start, count))
156
+ const rect = shiftRect(decoded, splice);
157
+ if (rect === undefined)
153
158
  continue;
154
- const rect = axis === 'row'
155
- ? { top: shift(top), left, bottom: shift(bottom), right }
156
- : { top, left: shift(left), bottom, right: shift(right) };
157
159
  rects.push(rect);
158
- merges.push(`${encodeAddress(rect.left, rect.top)}:${encodeAddress(rect.right, rect.bottom)}`);
160
+ merges.push(encodeRect(rect));
159
161
  }
160
- replaceContents(this.#merges, merges);
161
- replaceContents(this.#mergeRects, rects);
162
+ this.#merges.replaceAll(merges, rects);
162
163
  }
163
- #shiftTables(axis, start, count, delta) {
164
- const survivors = this.#tables.filter((table) => axis === 'row'
165
- ? table.shiftRows(start, count, delta)
166
- : table.shiftColumns(start, count, delta));
164
+ #shiftTables(splice) {
165
+ const survivors = this.#tables.filter((table) => splice.axis === 'row' ? table.shiftRows(splice) : table.shiftColumns(splice));
167
166
  replaceContents(this.#tables, survivors);
168
167
  }
169
- #shiftImages(axis, start, count, delta) {
170
- const shiftPoint = (point) => {
171
- const zeroBased = axis === 'row' ? point.row : point.col;
172
- const shifted = shiftIndex(zeroBased + 1, start, count, delta, axis) - 1;
168
+ #shiftImages(splice) {
169
+ const shiftAnchor = (point) => {
170
+ const zeroBased = splice.axis === 'row' ? point.row : point.col;
171
+ const shifted = shiftIndex(zeroBased + 1, splice) - 1;
173
172
  if (shifted === zeroBased)
174
173
  return point;
175
- return axis === 'row' ? { ...point, row: shifted } : { ...point, col: shifted };
174
+ return splice.axis === 'row' ? { ...point, row: shifted } : { ...point, col: shifted };
176
175
  };
177
176
  const moved = this.#images.map((image) => {
178
- const from = shiftPoint(image.anchor.from);
177
+ const from = shiftAnchor(image.anchor.from);
179
178
  const anchor = isOneCellAnchor(image.anchor)
180
179
  ? { ...image.anchor, from }
181
- : { ...image.anchor, from, to: shiftPoint(image.anchor.to) };
180
+ : { ...image.anchor, from, to: shiftAnchor(image.anchor.to) };
182
181
  return { imageId: image.imageId, anchor };
183
182
  });
184
183
  replaceContents(this.#images, moved);
@@ -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
  }
@@ -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,3 +1,5 @@
1
+ import { hex } from '../hex.js';
2
+ import { sha512 } from '../sha512.js';
1
3
  import { tokenSet } from '../token-set.js';
2
4
  export const PX_TO_EMU = 9525;
3
5
  export const isImageEditAs = tokenSet({ oneCell: true, twoCell: true, absolute: true });
@@ -40,9 +42,9 @@ export function normalizeImageExtension(extension, data) {
40
42
  }
41
43
  return sniffImageExtension(data);
42
44
  }
43
- export function findRegisteredImage(media, image) {
44
- const index = media.findIndex((held) => held.extension === image.extension &&
45
- held.data.length === image.data.length &&
46
- held.data.every((byte, i) => byte === image.data[i]));
47
- 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}`;
48
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;
@@ -1,5 +1,6 @@
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
  /**
@@ -15,12 +16,12 @@ export declare function clearCoveredValues(rows: Map<number, Map<number, Cell>>,
15
16
  * that can exist contributes no rectangle, the reader's rule for every other foreign attribute. */
16
17
  export declare function decodeSqrefRects(sqref: string): MergeRect[];
17
18
  /**
18
- * Re-anchor an OOXML `sqref` through a splice of `count` lines at `start` on `axis`, the inverse of
19
- * {@link decodeSqrefRects}. Returns `undefined` when the splice deleted every area the `sqref` named:
20
- * 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.
21
22
  *
22
23
  * Each space-separated area shifts on its own, and one the splice does not move is returned as the
23
24
  * *original text*. That matters for a file the library did not author: `B:B` and `B1:B1048576` decode
24
25
  * identically, so a re-encode would rewrite a foreign spelling and cost the byte-clean round trip.
25
26
  */
26
- 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,16 +1,15 @@
1
1
  import { encodeCornerRef, tryDecodeRange } from './address.js';
2
- import { isDeletedSpan, shiftIndex } from './grid-shift.js';
2
+ import { shiftSpan } from './grid-shift.js';
3
3
  export function clearCoveredValues(rows, rect) {
4
- for (let row = rect.top; row <= rect.bottom; row++) {
5
- const cols = rows.get(row);
6
- if (cols === undefined)
4
+ for (const [row, cols] of rows) {
5
+ if (row < rect.top || row > rect.bottom)
7
6
  continue;
8
- 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;
9
10
  if (row === rect.top && col === rect.left)
10
11
  continue;
11
- const covered = cols.get(col);
12
- if (covered !== undefined)
13
- covered.value = null;
12
+ covered.value = null;
14
13
  }
15
14
  }
16
15
  }
@@ -32,33 +31,33 @@ export function decodeSqrefRects(sqref) {
32
31
  }
33
32
  return rects;
34
33
  }
35
- export function shiftSqref(sqref, axis, start, count, delta) {
34
+ export function shiftSqref(sqref, splice) {
36
35
  const areas = [];
37
36
  for (const area of sqref.split(/\s+/)) {
38
37
  if (area === '')
39
38
  continue;
40
- const shifted = shiftSqrefArea(area, axis, start, count, delta);
39
+ const shifted = shiftSqrefArea(area, splice);
41
40
  if (shifted !== undefined)
42
41
  areas.push(shifted);
43
42
  }
44
43
  return areas.length > 0 ? areas.join(' ') : undefined;
45
44
  }
46
- function shiftSqrefArea(area, axis, start, count, delta) {
45
+ function shiftSqrefArea(area, splice) {
47
46
  const decoded = tryDecodeRange(area);
48
47
  if (decoded === undefined)
49
48
  return area;
50
49
  const { top, left, bottom, right } = decoded;
51
- const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
50
+ const rowAxis = splice.axis === 'row';
51
+ const [lo, hi] = rowAxis ? [top, bottom] : [left, right];
52
52
  if (lo === undefined || hi === undefined)
53
53
  return area;
54
- if (isDeletedSpan(lo, hi, start, count))
54
+ const moved = shiftSpan(lo, hi, splice);
55
+ if (moved === undefined)
55
56
  return undefined;
56
- const movedLo = shiftIndex(lo, start, count, delta, axis);
57
- const movedHi = shiftIndex(hi, start, count, delta, axis);
58
- if (movedLo === lo && movedHi === hi)
57
+ if (moved.lo === lo && moved.hi === hi)
59
58
  return area;
60
- const [tl, br] = axis === 'row'
61
- ? [encodeCornerRef(left, movedLo), encodeCornerRef(right, movedHi)]
62
- : [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)];
63
62
  return area.includes(':') ? `${tl}:${br}` : tl;
64
63
  }