@shbernal/ts-xlsx 2.0.0 → 3.0.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 (147) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +26 -4
  5. package/dist/core/address.js +21 -9
  6. package/dist/core/autofilter.d.ts +12 -1
  7. package/dist/core/autofilter.js +35 -12
  8. package/dist/core/axis-handle.d.ts +8 -0
  9. package/dist/core/axis-handle.js +14 -0
  10. package/dist/core/cell.d.ts +24 -0
  11. package/dist/core/cell.js +9 -1
  12. package/dist/core/color-resolution.d.ts +1 -1
  13. package/dist/core/color-resolution.js +7 -8
  14. package/dist/core/column.d.ts +4 -1
  15. package/dist/core/column.js +31 -35
  16. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +45 -5
  19. package/dist/core/conditional-formatting.js +74 -0
  20. package/dist/core/data-validation-overlay.d.ts +11 -0
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +9 -2
  23. package/dist/core/data-validation.js +26 -0
  24. package/dist/core/grid-edits.d.ts +16 -0
  25. package/dist/core/grid-edits.js +26 -8
  26. package/dist/core/grid-shift.d.ts +19 -0
  27. package/dist/core/grid-shift.js +8 -0
  28. package/dist/core/image.d.ts +2 -0
  29. package/dist/core/image.js +2 -0
  30. package/dist/core/merge-index.d.ts +31 -0
  31. package/dist/core/merge-index.js +60 -0
  32. package/dist/core/merge.d.ts +12 -11
  33. package/dist/core/merge.js +36 -10
  34. package/dist/core/page-setup.d.ts +86 -2
  35. package/dist/core/page-setup.js +34 -1
  36. package/dist/core/pivot-table.d.ts +2 -0
  37. package/dist/core/pivot-table.js +23 -18
  38. package/dist/core/protection.d.ts +3 -0
  39. package/dist/core/protection.js +20 -14
  40. package/dist/core/row-input.d.ts +7 -0
  41. package/dist/core/row-input.js +10 -8
  42. package/dist/core/row.d.ts +4 -1
  43. package/dist/core/row.js +19 -23
  44. package/dist/core/style.d.ts +78 -7
  45. package/dist/core/style.js +90 -80
  46. package/dist/core/table.d.ts +26 -12
  47. package/dist/core/table.js +67 -33
  48. package/dist/core/theme.d.ts +3 -1
  49. package/dist/core/theme.js +4 -0
  50. package/dist/core/used-extent.d.ts +44 -0
  51. package/dist/core/used-extent.js +102 -0
  52. package/dist/core/workbook.d.ts +2 -2
  53. package/dist/core/worksheet-comments.d.ts +8 -0
  54. package/dist/core/worksheet-comments.js +18 -0
  55. package/dist/core/worksheet.d.ts +31 -7
  56. package/dist/core/worksheet.js +72 -86
  57. package/dist/customui/ribbon.js +23 -24
  58. package/dist/entries/core.d.ts +3 -3
  59. package/dist/entries/csv.d.ts +1 -1
  60. package/dist/entries/node-unavailable.d.ts +15 -0
  61. package/dist/entries/node-unavailable.js +20 -0
  62. package/dist/entries/node.d.ts +1 -0
  63. package/dist/entries/node.js +1 -0
  64. package/dist/entries/xlsx.d.ts +0 -1
  65. package/dist/entries/xlsx.js +0 -1
  66. package/dist/io/csv/read.js +1 -1
  67. package/dist/io/csv/write.d.ts +12 -2
  68. package/dist/io/csv/write.js +17 -2
  69. package/dist/io/opc/inflate.js +3 -13
  70. package/dist/io/opc/part-paths.d.ts +1 -0
  71. package/dist/io/opc/part-paths.js +15 -0
  72. package/dist/io/opc/read-opc.d.ts +22 -3
  73. package/dist/io/opc/read-opc.js +11 -39
  74. package/dist/io/opc/rels.d.ts +12 -6
  75. package/dist/io/opc/rels.js +3 -5
  76. package/dist/io/style/xf-style.d.ts +33 -4
  77. package/dist/io/style/xf-style.js +12 -1
  78. package/dist/io/xlsb/read-styles.js +6 -3
  79. package/dist/io/xlsb/read.d.ts +1 -1
  80. package/dist/io/xlsb/read.js +5 -8
  81. package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
  82. package/dist/io/xlsx/cell-accumulator.js +20 -58
  83. package/dist/io/xlsx/cell-value.d.ts +1 -1
  84. package/dist/io/xlsx/cell-value.js +7 -4
  85. package/dist/io/xlsx/color-xml.d.ts +1 -1
  86. package/dist/io/xlsx/color-xml.js +8 -7
  87. package/dist/io/xlsx/comments.js +6 -3
  88. package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
  89. package/dist/io/xlsx/conditional-formatting.js +81 -65
  90. package/dist/io/xlsx/data-validation.d.ts +10 -8
  91. package/dist/io/xlsx/data-validation.js +44 -41
  92. package/dist/io/xlsx/edit-vba.js +4 -11
  93. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  94. package/dist/io/xlsx/hyperlinks.js +26 -31
  95. package/dist/io/xlsx/images.js +33 -35
  96. package/dist/io/xlsx/package-plan.d.ts +9 -1
  97. package/dist/io/xlsx/package-plan.js +29 -16
  98. package/dist/io/xlsx/part-names.d.ts +41 -0
  99. package/dist/io/xlsx/part-names.js +46 -0
  100. package/dist/io/xlsx/read-pivot.js +4 -9
  101. package/dist/io/xlsx/read-rows.js +12 -13
  102. package/dist/io/xlsx/read-shared-strings.js +9 -47
  103. package/dist/io/xlsx/read-styles.d.ts +1 -1
  104. package/dist/io/xlsx/read-styles.js +65 -43
  105. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  106. package/dist/io/xlsx/read-worksheet.js +48 -63
  107. package/dist/io/xlsx/read.d.ts +3 -4
  108. package/dist/io/xlsx/read.js +124 -146
  109. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  110. package/dist/io/xlsx/rich-runs.js +95 -30
  111. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  112. package/dist/io/xlsx/sheet-properties.js +23 -26
  113. package/dist/io/xlsx/styles.d.ts +3 -15
  114. package/dist/io/xlsx/styles.js +83 -105
  115. package/dist/io/xlsx/tables.js +14 -13
  116. package/dist/io/xlsx/theme-xml.js +8 -5
  117. package/dist/io/xlsx/threaded-comments.js +20 -19
  118. package/dist/io/xlsx/workbook-xml.js +43 -36
  119. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  120. package/dist/io/xlsx/worksheet-xml.js +47 -52
  121. package/dist/io/xlsx/write-stream.d.ts +14 -1
  122. package/dist/io/xlsx/write-stream.js +40 -33
  123. package/dist/io/xlsx/write.d.ts +5 -0
  124. package/dist/io/xlsx/write.js +155 -131
  125. package/dist/sha512.d.ts +2 -0
  126. package/dist/sha512.js +146 -0
  127. package/dist/token-set.d.ts +15 -0
  128. package/dist/token-set.js +4 -0
  129. package/dist/vba/bytes.d.ts +1 -2
  130. package/dist/vba/bytes.js +1 -12
  131. package/dist/vba/cfb-format.d.ts +24 -0
  132. package/dist/vba/cfb-format.js +12 -0
  133. package/dist/vba/cfb-writer.js +17 -17
  134. package/dist/vba/cfb.js +1 -7
  135. package/dist/vba/dir-records.d.ts +53 -0
  136. package/dist/vba/dir-records.js +28 -0
  137. package/dist/vba/ms-ovba.js +32 -11
  138. package/dist/vba/project-editor.js +5 -34
  139. package/dist/vba/project.js +15 -31
  140. package/dist/vba/vba-encoding.js +1 -1
  141. package/dist/xml/xml-read.d.ts +117 -72
  142. package/dist/xml/xml-read.js +123 -169
  143. package/dist/xml/xml-scan.d.ts +100 -0
  144. package/dist/xml/xml-scan.js +208 -0
  145. package/dist/xml/xml.d.ts +44 -2
  146. package/dist/xml/xml.js +17 -5
  147. package/package.json +22 -6
@@ -1,4 +1,5 @@
1
- import type { ConditionalFormatting } from '../../core/conditional-formatting.ts';
1
+ import { type ConditionalFormatting } from '../../core/conditional-formatting.ts';
2
+ import { type CollectingPass } from '../../xml/xml-read.ts';
2
3
  import type { StyleRegistry } from './styles.ts';
3
4
  /**
4
5
  * Serialise every conditional formatting on a sheet into its `<conditionalFormatting>` blocks, in
@@ -15,14 +16,14 @@ export declare function conditionalFormattingsXml(formattings: readonly Conditio
15
16
  */
16
17
  export declare function conditionalFormattingsExtXml(formattings: readonly ConditionalFormatting[]): string;
17
18
  /**
18
- * Parse a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
19
+ * A pass reading a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
19
20
  * blocks supply every rule; the x14 extension (`<x14:conditionalFormatting>` inside `<extLst>`) is
20
21
  * read only to enrich a classic data bar with the facets the classic element cannot carry (the
21
22
  * gradient flag and the negative-fill and axis colours) matched by the shared id the two ends link
22
23
  * on. An extension rule with no classic counterpart (a rule that lives only in x14) is ignored, so it
23
24
  * is never half-read into a broken classic rule.
24
25
  */
25
- export declare function parseConditionalFormattings(xml: string): ConditionalFormatting[];
26
+ export declare function conditionalFormattingPass(): CollectingPass<ConditionalFormatting[]>;
26
27
  /**
27
28
  * Extract the differential-style (`<dxf>`) fragments from styles.xml, each verbatim. Preserving the
28
29
  * raw XML, rather than reparsing and re-serialising, is what keeps a foreign dxf's number format a
@@ -1,5 +1,7 @@
1
- import { boolPresent, boolStrict, coerceNumericLiteral, localName, numFinite, numInteger, parseXml, } from '../../xml/xml-read.js';
2
- import { boolAttr, escapeAttr, escapeText, stripFormulaEquals } from '../../xml/xml.js';
1
+ import { isCfValueObjectType, isConditionalFormattingOperator, isConditionalFormattingType, isIconSetType, isCfTimePeriod, } from '../../core/conditional-formatting.js';
2
+ import { elementSubtrees, TextCapture, } from '../../xml/xml-read.js';
3
+ import { boolPresent, boolStrict, coerceNumericLiteral, enumToken, localName, numFinite, numInteger, } from '../../xml/xml-scan.js';
4
+ import { boolAttr, checkedToken, escapeAttr, escapeText, numberText, stripFormulaEquals, textAttr, } from '../../xml/xml.js';
3
5
  import { colorAttrs, parseColor } from './color-xml.js';
4
6
  import { CF_EXT_URI, DATABAR_LINK_EXT_URI, XM_NS } from './namespaces.js';
5
7
  import { x14Ext } from './x14-ext.js';
@@ -61,7 +63,7 @@ function x14DataBarXml(ref, rule, guid) {
61
63
  `</x14:cfRule><xm:sqref>${escapeText(ref)}</xm:sqref></x14:conditionalFormatting>`);
62
64
  }
63
65
  function x14CfvoXml(cfvo) {
64
- const type = escapeAttr(cfvo.type);
66
+ const type = checkedToken(cfvo.type, isCfValueObjectType, 'conditional format value type');
65
67
  if (cfvo.value === undefined)
66
68
  return `<x14:cfvo type="${type}"/>`;
67
69
  return `<x14:cfvo type="${type}"><xm:f>${escapeText(String(cfvo.value))}</xm:f></x14:cfvo>`;
@@ -71,12 +73,16 @@ function cfRuleExtLinkXml(guid) {
71
73
  }
72
74
  function blockXml(cf, styles, priority, extLinks) {
73
75
  const rules = cf.rules.map((rule) => ruleXml(rule, styles, priority, extLinks)).join('');
76
+ if (rules === '')
77
+ return '';
74
78
  return `<conditionalFormatting sqref="${escapeAttr(cf.ref)}">${rules}</conditionalFormatting>`;
75
79
  }
76
80
  function ruleXml(rule, styles, priority, extLinks) {
77
81
  const p = rule.priority ?? priority.next;
78
82
  priority.next = Math.max(priority.next, p) + 1;
79
- const attrs = [`type="${escapeAttr(rule.type)}"`];
83
+ const attrs = [
84
+ `type="${checkedToken(rule.type, isConditionalFormattingType, 'conditional formatting type')}"`,
85
+ ];
80
86
  const dxfId = resolveDxfId(rule, styles);
81
87
  if (dxfId !== undefined)
82
88
  attrs.push(`dxfId="${dxfId}"`);
@@ -91,16 +97,18 @@ function ruleXml(rule, styles, priority, extLinks) {
91
97
  attrs.push('bottom="1"');
92
98
  if (rule.percent)
93
99
  attrs.push('percent="1"');
94
- if (rule.operator !== undefined)
95
- attrs.push(`operator="${escapeAttr(rule.operator)}"`);
100
+ if (rule.operator !== undefined) {
101
+ attrs.push(`operator="${checkedToken(rule.operator, isConditionalFormattingOperator, 'conditional formatting operator')}"`);
102
+ }
96
103
  if (rule.text !== undefined)
97
104
  attrs.push(`text="${escapeAttr(rule.text)}"`);
98
- if (rule.timePeriod !== undefined)
99
- attrs.push(`timePeriod="${escapeAttr(rule.timePeriod)}"`);
105
+ if (rule.timePeriod !== undefined) {
106
+ attrs.push(`timePeriod="${checkedToken(rule.timePeriod, isCfTimePeriod, 'time period')}"`);
107
+ }
100
108
  if (rule.rank !== undefined)
101
- attrs.push(`rank="${rule.rank}"`);
109
+ attrs.push(`rank="${numberText(rule.rank)}"`);
102
110
  if (rule.stdDev !== undefined)
103
- attrs.push(`stdDev="${rule.stdDev}"`);
111
+ attrs.push(`stdDev="${numberText(rule.stdDev)}"`);
104
112
  let body = SCALE_TYPES.has(rule.type) ? scaleXml(rule) : formulaeXml(rule.formulae);
105
113
  const extGuid = extLinks.get(rule);
106
114
  if (extGuid !== undefined)
@@ -112,8 +120,9 @@ function ruleXml(rule, styles, priority, extLinks) {
112
120
  function resolveDxfId(rule, styles) {
113
121
  if (SCALE_TYPES.has(rule.type))
114
122
  return undefined;
115
- if (rule.dxfId !== undefined)
116
- return Number(rule.dxfId);
123
+ const preserved = dxfIndex(rule.dxfId);
124
+ if (preserved !== undefined)
125
+ return preserved;
117
126
  if (rule.style !== undefined)
118
127
  return styles.differentialStyleId(rule.style);
119
128
  return undefined;
@@ -142,34 +151,34 @@ function colorScaleXml(rule) {
142
151
  return `<colorScale>${anchors}${colors}</colorScale>`;
143
152
  }
144
153
  function iconSetXml(rule) {
145
- const name = rule.iconSet !== undefined ? ` iconSet="${escapeAttr(rule.iconSet)}"` : '';
154
+ const name = rule.iconSet === undefined
155
+ ? ''
156
+ : ` iconSet="${checkedToken(rule.iconSet, isIconSetType, 'icon set')}"`;
146
157
  const anchors = (rule.cfvo ?? []).map(cfvoXml).join('');
147
158
  return `<iconSet${name}>${anchors}</iconSet>`;
148
159
  }
149
160
  function cfvoXml(cfvo) {
150
- const val = cfvo.value !== undefined ? ` val="${escapeAttr(String(cfvo.value))}"` : '';
151
- return `<cfvo type="${escapeAttr(cfvo.type)}"${val}/>`;
161
+ const type = checkedToken(cfvo.type, isCfValueObjectType, 'conditional format value type');
162
+ const val = cfvo.value === undefined ? '' : textAttr('val', String(cfvo.value));
163
+ return `<cfvo type="${type}"${val}/>`;
152
164
  }
153
- export function parseConditionalFormattings(xml) {
165
+ export function conditionalFormattingPass() {
154
166
  const blocks = [];
155
167
  let block;
156
168
  let draft;
157
169
  let scale;
158
- let capturingFormula = false;
159
- let formulaText = '';
170
+ const formulaCapture = new TextCapture('formula');
160
171
  const linked = [];
161
172
  const extById = new Map();
162
173
  let x14Ext;
163
174
  let x14ExtId;
164
- let capturingX14Id = false;
165
- let x14IdText = '';
166
- parseXml(xml, {
175
+ const x14IdCapture = new TextCapture('id');
176
+ const handlers = {
167
177
  onOpen(name, attrs, selfClosing) {
168
178
  const ln = localName(name);
169
179
  if (name.includes(':')) {
170
180
  if (ln === 'id' && draft !== undefined) {
171
- capturingX14Id = true;
172
- x14IdText = '';
181
+ x14IdCapture.open(ln, selfClosing);
173
182
  }
174
183
  else if (ln === 'cfRule') {
175
184
  x14Ext = attrs.type === 'dataBar' && attrs.id !== undefined ? emptyExt() : undefined;
@@ -191,7 +200,9 @@ export function parseConditionalFormattings(xml) {
191
200
  }
192
201
  else if (ln === 'cfRule' && block !== undefined) {
193
202
  if (selfClosing) {
194
- block.rules.push(finalizeRule(newDraft(attrs)));
203
+ const rule = finalizeRule(newDraft(attrs));
204
+ if (rule !== undefined)
205
+ block.rules.push(rule);
195
206
  }
196
207
  else {
197
208
  draft = newDraft(attrs);
@@ -201,8 +212,8 @@ export function parseConditionalFormattings(xml) {
201
212
  else if (draft !== undefined &&
202
213
  (ln === 'dataBar' || ln === 'colorScale' || ln === 'iconSet')) {
203
214
  scale = ln;
204
- if (ln === 'iconSet' && attrs.iconSet !== undefined)
205
- draft.iconSet = attrs.iconSet;
215
+ if (ln === 'iconSet')
216
+ draft.iconSet = enumToken(attrs.iconSet, isIconSetType);
206
217
  }
207
218
  else if (draft !== undefined && ln === 'cfvo') {
208
219
  draft.cfvo.push(parseCfvo(attrs));
@@ -215,23 +226,20 @@ export function parseConditionalFormattings(xml) {
215
226
  draft.colors.push(color);
216
227
  }
217
228
  else if (draft !== undefined && ln === 'formula') {
218
- capturingFormula = true;
219
- formulaText = '';
229
+ formulaCapture.open(ln, selfClosing);
220
230
  }
221
231
  },
222
232
  onText(chunk) {
223
- if (capturingFormula)
224
- formulaText += chunk;
225
- if (capturingX14Id)
226
- x14IdText += chunk;
233
+ formulaCapture.text(chunk);
234
+ x14IdCapture.text(chunk);
227
235
  },
228
236
  onClose(name) {
229
237
  const ln = localName(name);
230
238
  if (name.includes(':')) {
231
- if (ln === 'id' && capturingX14Id) {
239
+ const id = x14IdCapture.close(ln);
240
+ if (id !== undefined) {
232
241
  if (draft !== undefined)
233
- draft.x14Id = x14IdText;
234
- capturingX14Id = false;
242
+ draft.x14Id = id;
235
243
  }
236
244
  else if (ln === 'cfRule' && x14Ext !== undefined && x14ExtId !== undefined) {
237
245
  extById.set(x14ExtId, x14Ext);
@@ -240,20 +248,22 @@ export function parseConditionalFormattings(xml) {
240
248
  }
241
249
  return;
242
250
  }
243
- if (ln === 'formula' && capturingFormula) {
251
+ const formula = formulaCapture.close(ln);
252
+ if (formula !== undefined) {
244
253
  if (draft !== undefined)
245
- draft.formulae.push(coerceNumericLiteral(formulaText));
246
- capturingFormula = false;
254
+ draft.formulae.push(coerceNumericLiteral(formula));
247
255
  }
248
256
  else if (ln === 'dataBar' || ln === 'colorScale' || ln === 'iconSet') {
249
257
  scale = undefined;
250
258
  }
251
259
  else if (ln === 'cfRule' && draft !== undefined) {
252
260
  const rule = finalizeRule(draft);
253
- if (block !== undefined)
254
- block.rules.push(rule);
255
- if (draft.x14Id !== undefined)
256
- linked.push({ rule, id: draft.x14Id });
261
+ if (rule !== undefined) {
262
+ if (block !== undefined)
263
+ block.rules.push(rule);
264
+ if (draft.x14Id !== undefined)
265
+ linked.push({ rule, id: draft.x14Id });
266
+ }
257
267
  draft = undefined;
258
268
  }
259
269
  else if (ln === 'conditionalFormatting' && block !== undefined) {
@@ -261,38 +271,38 @@ export function parseConditionalFormattings(xml) {
261
271
  block = undefined;
262
272
  }
263
273
  },
264
- });
265
- for (const { rule, id } of linked) {
266
- const ext = extById.get(id);
267
- if (ext === undefined)
268
- continue;
269
- if (ext.gradient !== undefined)
270
- rule.gradient = ext.gradient;
271
- if (ext.negativeFillColor !== undefined)
272
- rule.negativeFillColor = ext.negativeFillColor;
273
- if (ext.axisColor !== undefined)
274
- rule.axisColor = ext.axisColor;
275
- }
276
- return blocks;
274
+ };
275
+ const result = () => {
276
+ for (const { rule, id } of linked) {
277
+ const ext = extById.get(id);
278
+ if (ext === undefined)
279
+ continue;
280
+ if (ext.gradient !== undefined)
281
+ rule.gradient = ext.gradient;
282
+ if (ext.negativeFillColor !== undefined)
283
+ rule.negativeFillColor = ext.negativeFillColor;
284
+ if (ext.axisColor !== undefined)
285
+ rule.axisColor = ext.axisColor;
286
+ }
287
+ return blocks;
288
+ };
289
+ return { handlers, result };
277
290
  }
278
291
  function emptyExt() {
279
292
  return { gradient: undefined, negativeFillColor: undefined, axisColor: undefined };
280
293
  }
281
294
  export function parseDxfs(stylesXml) {
282
- const block = /<dxfs\b[^>]*>([\s\S]*?)<\/dxfs>/.exec(stylesXml);
283
- if (block === null)
284
- return [];
285
- const inner = block[1] ?? '';
286
- return [...inner.matchAll(/<dxf\b[^>]*>[\s\S]*?<\/dxf>|<dxf\b[^>]*\/>/g)].map((m) => m[0] ?? '');
295
+ const { fragments } = elementSubtrees(stylesXml, new Map([['dxfs', 'dxf']]));
296
+ return [...(fragments.get('dxfs') ?? [])];
287
297
  }
288
298
  function newDraft(attrs) {
289
299
  return {
290
- type: attrs.type ?? '',
300
+ type: enumToken(attrs.type, isConditionalFormattingType),
291
301
  priority: numFinite(attrs.priority),
292
302
  stopIfTrue: boolStrict(attrs.stopIfTrue),
293
- operator: attrs.operator,
303
+ operator: enumToken(attrs.operator, isConditionalFormattingOperator),
294
304
  text: attrs.text,
295
- timePeriod: attrs.timePeriod,
305
+ timePeriod: enumToken(attrs.timePeriod, isCfTimePeriod),
296
306
  rank: numFinite(attrs.rank),
297
307
  stdDev: numFinite(attrs.stdDev),
298
308
  percent: boolStrict(attrs.percent),
@@ -308,10 +318,15 @@ function newDraft(attrs) {
308
318
  x14Id: undefined,
309
319
  };
310
320
  }
321
+ function dxfIndex(value) {
322
+ return numInteger(value, 0);
323
+ }
311
324
  function parseIndexAttr(value) {
312
- return numInteger(value, 0) === undefined ? undefined : value;
325
+ return dxfIndex(value) === undefined ? undefined : value;
313
326
  }
314
327
  function finalizeRule(draft) {
328
+ if (draft.type === undefined)
329
+ return undefined;
315
330
  const rule = { type: draft.type };
316
331
  if (draft.priority !== undefined)
317
332
  rule.priority = draft.priority;
@@ -350,7 +365,8 @@ function finalizeRule(draft) {
350
365
  return rule;
351
366
  }
352
367
  function parseCfvo(attrs) {
353
- const type = (attrs.type ?? 'num');
368
+ const raw = attrs.type;
369
+ const type = raw !== undefined && isCfValueObjectType(raw) ? raw : 'num';
354
370
  const cfvo = { type };
355
371
  if (attrs.val !== undefined) {
356
372
  cfvo.value = type === 'formula' ? attrs.val : coerceNumericLiteral(attrs.val);
@@ -1,5 +1,6 @@
1
- import type { DataValidationEntry } from '../../core/data-validation.ts';
1
+ import { type DataValidationEntry } from '../../core/data-validation.ts';
2
2
  import type { Worksheet } from '../../core/worksheet.ts';
3
+ import { type CollectingPass } from '../../xml/xml-read.ts';
3
4
  /** The standard `<dataValidations>` element for the rules stored in the legacy form, or '' when the
4
5
  * sheet has none of them, so a sheet with only extended (or no) validations stays byte-clean here.
5
6
  * The extended rules are emitted separately by {@link dataValidationsExtXml}. */
@@ -8,13 +9,14 @@ export declare function dataValidationsXml(entries: readonly DataValidationEntry
8
9
  * none. Emitted bare (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single
9
10
  * `<extLst>` beside the conditional-formatting extension: a worksheet may carry at most one. */
10
11
  export declare function dataValidationsExtXml(entries: readonly DataValidationEntry[]): string;
11
- /** Parse every standard `<dataValidation>` out of a worksheet part into range-bound rules. */
12
- export declare function parseDataValidations(xml: string): DataValidationEntry[];
13
- /** Parse every extended `<x14:dataValidation>` out of a worksheet's `<extLst>` into range-bound
14
- * rules tagged `extended`, so a cross-sheet or whole-column list validation Excel stored only in the
15
- * 2009 extension form is read back rather than dropped. The standard parser ignores these (they are
16
- * prefixed); this one, symmetrically, handles only the prefixed elements. */
17
- export declare function parseExtendedDataValidations(xml: string): DataValidationEntry[];
12
+ /** A pass gathering the standard `<dataValidation>` elements of a worksheet part, for a caller
13
+ * reading the part alongside its other readers in one parse. */
14
+ export declare function dataValidationPass(): CollectingPass<DataValidationEntry[]>;
15
+ /** A pass gathering the extended `<x14:dataValidation>` elements of a worksheet's `<extLst>`, so a
16
+ * cross-sheet or whole-column list validation Excel stored only in the 2009 extension form is read
17
+ * back rather than dropped. The standard pass ignores these (they are prefixed); this one,
18
+ * symmetrically, handles only the prefixed elements. */
19
+ export declare function extendedDataValidationPass(): CollectingPass<DataValidationEntry[]>;
18
20
  /** Fold parsed validations onto a sheet, each bound to its original range and carrying its form: an
19
21
  * `extended` entry is re-attached as extended so a round-trip writes it back to the x14 block. */
20
22
  export declare function applyDataValidations(sheet: Worksheet, entries: readonly DataValidationEntry[]): void;
@@ -1,5 +1,8 @@
1
- import { boolStrict, coerceNumericLiteral, localName, parseXml } from '../../xml/xml-read.js';
2
- import { escapeAttr, escapeText, stripFormulaEquals } from '../../xml/xml.js';
1
+ import { isDataValidationErrorStyle, isDataValidationOperator, isDataValidationType, } from '../../core/data-validation.js';
2
+ import { decodeSqrefRects } from '../../core/merge.js';
3
+ import { TextCapture } from '../../xml/xml-read.js';
4
+ import { boolStrict, coerceNumericLiteral, localName } from '../../xml/xml-scan.js';
5
+ import { checkedToken, escapeAttr, escapeText, stripFormulaEquals, textAttr } from '../../xml/xml.js';
3
6
  import { DATA_VALIDATION_EXT_URI, XM_NS } from './namespaces.js';
4
7
  import { x14Ext } from './x14-ext.js';
5
8
  const TYPED = new Set(['whole', 'decimal', 'date', 'time', 'textLength']);
@@ -18,16 +21,20 @@ export function dataValidationsExtXml(entries) {
18
21
  return x14Ext(DATA_VALIDATION_EXT_URI, `<x14:dataValidations count="${extended.length}" xmlns:xm="${XM_NS}">${items}</x14:dataValidations>`);
19
22
  }
20
23
  function ruleAttrs(rule) {
21
- return (` type="${rule.type}"` +
22
- (rule.errorStyle !== undefined ? ` errorStyle="${rule.errorStyle}"` : '') +
23
- (rule.operator !== undefined ? ` operator="${rule.operator}"` : '') +
24
+ return (` type="${checkedToken(rule.type, isDataValidationType, 'data validation type')}"` +
25
+ (rule.errorStyle === undefined
26
+ ? ''
27
+ : ` errorStyle="${checkedToken(rule.errorStyle, isDataValidationErrorStyle, 'data validation error style')}"`) +
28
+ (rule.operator === undefined
29
+ ? ''
30
+ : ` operator="${checkedToken(rule.operator, isDataValidationOperator, 'data validation operator')}"`) +
24
31
  (rule.allowBlank ? ' allowBlank="1"' : '') +
25
32
  (rule.showInputMessage ? ' showInputMessage="1"' : '') +
26
33
  (rule.showErrorMessage ? ' showErrorMessage="1"' : '') +
27
- (rule.errorTitle !== undefined ? ` errorTitle="${escapeAttr(rule.errorTitle)}"` : '') +
28
- (rule.error !== undefined ? ` error="${escapeAttr(rule.error)}"` : '') +
29
- (rule.promptTitle !== undefined ? ` promptTitle="${escapeAttr(rule.promptTitle)}"` : '') +
30
- (rule.prompt !== undefined ? ` prompt="${escapeAttr(rule.prompt)}"` : ''));
34
+ textAttr('errorTitle', rule.errorTitle) +
35
+ textAttr('error', rule.error) +
36
+ textAttr('promptTitle', rule.promptTitle) +
37
+ textAttr('prompt', rule.prompt));
31
38
  }
32
39
  function dataValidationXml(sqref, rule) {
33
40
  const [f1, f2] = operands(rule);
@@ -51,11 +58,11 @@ function extendedDataValidationXml(sqref, rule) {
51
58
  `<xm:sqref>${escapeText(sqref)}</xm:sqref>`;
52
59
  return `<x14:dataValidation${ruleAttrs(rule)}>${body}</x14:dataValidation>`;
53
60
  }
54
- export function parseDataValidations(xml) {
61
+ export function dataValidationPass() {
55
62
  const entries = [];
56
63
  let current;
57
64
  let slot;
58
- parseXml(xml, {
65
+ const handlers = {
59
66
  onOpen(name, attrs) {
60
67
  const ln = localName(name);
61
68
  if (ln === 'dataValidation' && !name.includes(':')) {
@@ -88,8 +95,8 @@ export function parseDataValidations(xml) {
88
95
  current = undefined;
89
96
  }
90
97
  },
91
- });
92
- return entries;
98
+ };
99
+ return { handlers, result: () => entries };
93
100
  }
94
101
  function buildEntry(attrs, formulae) {
95
102
  const { sqref } = attrs;
@@ -100,11 +107,12 @@ function buildEntry(attrs, formulae) {
100
107
  }
101
108
  function buildRule(attrs, formulae) {
102
109
  const { type } = attrs;
103
- if (type === undefined)
110
+ if (type === undefined || !isDataValidationType(type))
104
111
  return undefined;
105
- const rule = { type: type };
112
+ const rule = { type };
106
113
  if (attrs.operator !== undefined) {
107
- rule.operator = attrs.operator;
114
+ if (isDataValidationOperator(attrs.operator))
115
+ rule.operator = attrs.operator;
108
116
  }
109
117
  else if (TYPED.has(type)) {
110
118
  rule.operator = 'between';
@@ -115,7 +123,7 @@ function buildRule(attrs, formulae) {
115
123
  rule.showInputMessage = true;
116
124
  if (boolStrict(attrs.showErrorMessage))
117
125
  rule.showErrorMessage = true;
118
- if (attrs.errorStyle !== undefined)
126
+ if (attrs.errorStyle !== undefined && isDataValidationErrorStyle(attrs.errorStyle))
119
127
  rule.errorStyle = attrs.errorStyle;
120
128
  if (attrs.error !== undefined)
121
129
  rule.error = attrs.error;
@@ -132,14 +140,13 @@ function buildRule(attrs, formulae) {
132
140
  rule.formulae = parsed;
133
141
  return rule;
134
142
  }
135
- export function parseExtendedDataValidations(xml) {
143
+ export function extendedDataValidationPass() {
136
144
  const entries = [];
137
145
  let current;
138
146
  let slot;
139
- let capture;
140
- let text = '';
141
- parseXml(xml, {
142
- onOpen(name, attrs) {
147
+ const capture = new TextCapture(['f', 'sqref']);
148
+ const handlers = {
149
+ onOpen(name, attrs, selfClosing) {
143
150
  const ln = localName(name);
144
151
  const prefixed = name.includes(':');
145
152
  if (ln === 'dataValidation' && prefixed) {
@@ -151,30 +158,24 @@ export function parseExtendedDataValidations(xml) {
151
158
  else if (current !== undefined && prefixed && ln === 'formula2') {
152
159
  slot = 1;
153
160
  }
154
- else if (current !== undefined && ln === 'f') {
155
- capture = 'formula';
156
- text = '';
157
- }
158
- else if (current !== undefined && ln === 'sqref') {
159
- capture = 'sqref';
160
- text = '';
161
+ else if (current !== undefined) {
162
+ capture.open(ln, selfClosing);
161
163
  }
162
164
  },
163
165
  onText(chunk) {
164
- if (capture !== undefined)
165
- text += chunk;
166
+ capture.text(chunk);
166
167
  },
167
168
  onClose(name) {
168
169
  const ln = localName(name);
169
- if (ln === 'f' && capture === 'formula') {
170
- if (current !== undefined && slot !== undefined)
171
- current.formulae[slot] = text;
172
- capture = undefined;
173
- }
174
- else if (ln === 'sqref' && capture === 'sqref') {
175
- if (current !== undefined)
170
+ const text = capture.close(ln);
171
+ if (text !== undefined) {
172
+ if (ln === 'f') {
173
+ if (current !== undefined && slot !== undefined)
174
+ current.formulae[slot] = text;
175
+ }
176
+ else if (current !== undefined) {
176
177
  current.sqref = text;
177
- capture = undefined;
178
+ }
178
179
  }
179
180
  else if ((ln === 'formula1' || ln === 'formula2') && name.includes(':')) {
180
181
  slot = undefined;
@@ -186,8 +187,8 @@ export function parseExtendedDataValidations(xml) {
186
187
  current = undefined;
187
188
  }
188
189
  },
189
- });
190
- return entries;
190
+ };
191
+ return { handlers, result: () => entries };
191
192
  }
192
193
  function buildExtendedEntry(attrs, formulae, sqref) {
193
194
  if (sqref === '')
@@ -197,6 +198,8 @@ function buildExtendedEntry(attrs, formulae, sqref) {
197
198
  }
198
199
  export function applyDataValidations(sheet, entries) {
199
200
  for (const { sqref, rule, extended } of entries) {
201
+ if (decodeSqrefRects(sqref).length === 0)
202
+ continue;
200
203
  sheet.addDataValidation(sqref, rule, extended ? { extended: true } : {});
201
204
  }
202
205
  }
@@ -1,8 +1,8 @@
1
1
  import { strFromU8, strToU8, unzipSync, zipSync } from 'fflate';
2
2
  import { VbaAuthorError } from '../../vba/errors.js';
3
3
  import { addVbaReference, removeVbaModule, } from '../../vba/project-editor.js';
4
- import { relsPathFor } from '../opc/part-paths.js';
5
- import { parseRelationshipRecords, relationshipTargetByType, resolveRelativePart, resolveWorkbookPart, } from '../opc/read-opc.js';
4
+ import { relsPathFor, resolveRelativePart } from '../opc/part-paths.js';
5
+ import { parseRelationshipRecords, relationshipTargetByType } from '../opc/read-opc.js';
6
6
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
7
7
  const OFFICE_DOCUMENT_REL = 'officeDocument';
8
8
  const VBA_PROJECT_REL = 'vbaProject';
@@ -14,7 +14,7 @@ export function editXlsxVbaAddReference(xlsx, ref) {
14
14
  return applyToVbaProjectPart(xlsx, (bin) => addVbaReference(bin, ref));
15
15
  }
16
16
  function applyToVbaProjectPart(xlsx, apply) {
17
- const files = unzipSync(xlsx);
17
+ const files = Object.assign(Object.create(null), unzipSync(xlsx));
18
18
  const binPath = locateVbaProjectPart(files);
19
19
  const bin = binPath === undefined ? undefined : files[binPath];
20
20
  if (binPath === undefined || bin === undefined) {
@@ -38,14 +38,7 @@ function locateVbaProjectPart(files) {
38
38
  const vbaTarget = relationshipTargetByType(workbookRels, VBA_PROJECT_REL);
39
39
  if (vbaTarget === undefined)
40
40
  return undefined;
41
- return resolveVbaTarget(workbookPath, vbaTarget);
42
- }
43
- function resolveVbaTarget(workbookPath, target) {
44
- if (target.startsWith('/'))
45
- return target.slice(1);
46
- if (workbookPath.startsWith('xl/'))
47
- return resolveWorkbookPart(target);
48
- return resolveRelativePart(workbookPath, target);
41
+ return resolveRelativePart(workbookPath, vbaTarget);
49
42
  }
50
43
  function dropStaleSignature(files, binPath) {
51
44
  const binRelsPath = relsPathFor(binPath);
@@ -1,4 +1,5 @@
1
1
  import type { Worksheet } from '../../core/worksheet.ts';
2
+ import { type CollectingPass } from '../../xml/xml-read.ts';
2
3
  import type { SheetRelIds } from './package-plan.ts';
3
4
  /** A hyperlink gathered from a sheet for serialisation: the cell it sits on, its target, and an
4
5
  * optional tooltip. The visible label is the cell's own value and is serialised as that value. */
@@ -34,8 +35,9 @@ interface ParsedHyperlink {
34
35
  readonly location?: string;
35
36
  readonly tooltip?: string;
36
37
  }
37
- /** Parse every `<hyperlink>` element out of a worksheet part. */
38
- export declare function parseSheetHyperlinks(xml: string): ParsedHyperlink[];
38
+ /** A pass gathering every `<hyperlink>` element of a worksheet part, for a caller reading the part
39
+ * alongside its other readers in one parse. */
40
+ export declare function sheetHyperlinkPass(): CollectingPass<ParsedHyperlink[]>;
39
41
  /** Fold parsed hyperlinks onto a sheet's cells, wrapping each cell's existing value (its visible
40
42
  * label) into a {@link HyperlinkValue}. `targetOf` resolves a relationship id to its raw Target: a
41
43
  * URL for the external links hyperlinks almost always are, so it must stay unresolved against the