@shbernal/ts-xlsx 2.0.0 → 2.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 (106) 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 +11 -0
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/color-resolution.d.ts +1 -1
  9. package/dist/core/color-resolution.js +7 -8
  10. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  11. package/dist/core/conditional-formatting-overlay.js +11 -0
  12. package/dist/core/conditional-formatting.d.ts +45 -5
  13. package/dist/core/conditional-formatting.js +88 -0
  14. package/dist/core/data-validation-overlay.d.ts +11 -0
  15. package/dist/core/data-validation-overlay.js +21 -2
  16. package/dist/core/data-validation.d.ts +9 -2
  17. package/dist/core/data-validation.js +34 -0
  18. package/dist/core/grid-edits.d.ts +16 -0
  19. package/dist/core/grid-edits.js +26 -8
  20. package/dist/core/grid-shift.d.ts +19 -0
  21. package/dist/core/grid-shift.js +8 -0
  22. package/dist/core/image.d.ts +2 -0
  23. package/dist/core/image.js +4 -0
  24. package/dist/core/merge.d.ts +12 -1
  25. package/dist/core/merge.js +36 -2
  26. package/dist/core/page-setup.d.ts +16 -2
  27. package/dist/core/page-setup.js +8 -1
  28. package/dist/core/pivot-table.d.ts +2 -0
  29. package/dist/core/pivot-table.js +25 -16
  30. package/dist/core/protection.d.ts +3 -0
  31. package/dist/core/protection.js +20 -14
  32. package/dist/core/row-input.d.ts +7 -0
  33. package/dist/core/row-input.js +10 -8
  34. package/dist/core/style.d.ts +27 -0
  35. package/dist/core/style.js +82 -67
  36. package/dist/core/table.d.ts +25 -11
  37. package/dist/core/table.js +67 -31
  38. package/dist/core/theme.d.ts +3 -1
  39. package/dist/core/theme.js +4 -0
  40. package/dist/core/workbook.d.ts +2 -2
  41. package/dist/core/worksheet-comments.d.ts +8 -0
  42. package/dist/core/worksheet-comments.js +18 -0
  43. package/dist/core/worksheet.d.ts +15 -2
  44. package/dist/core/worksheet.js +28 -48
  45. package/dist/customui/ribbon.js +24 -23
  46. package/dist/entries/core.d.ts +3 -3
  47. package/dist/entries/csv.d.ts +1 -1
  48. package/dist/entries/node-unavailable.d.ts +15 -0
  49. package/dist/entries/node-unavailable.js +20 -0
  50. package/dist/entries/node.d.ts +1 -0
  51. package/dist/entries/node.js +1 -0
  52. package/dist/entries/xlsx.d.ts +0 -1
  53. package/dist/entries/xlsx.js +0 -1
  54. package/dist/io/csv/read.js +1 -1
  55. package/dist/io/csv/write.d.ts +12 -2
  56. package/dist/io/csv/write.js +17 -2
  57. package/dist/io/opc/inflate.js +2 -12
  58. package/dist/io/opc/rels.d.ts +11 -0
  59. package/dist/io/opc/rels.js +3 -2
  60. package/dist/io/style/xf-style.d.ts +7 -2
  61. package/dist/io/style/xf-style.js +6 -1
  62. package/dist/io/xlsx/cell-accumulator.d.ts +1 -3
  63. package/dist/io/xlsx/cell-accumulator.js +20 -58
  64. package/dist/io/xlsx/cell-value.d.ts +1 -1
  65. package/dist/io/xlsx/cell-value.js +7 -4
  66. package/dist/io/xlsx/color-xml.d.ts +1 -1
  67. package/dist/io/xlsx/color-xml.js +7 -6
  68. package/dist/io/xlsx/comments.js +4 -2
  69. package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
  70. package/dist/io/xlsx/conditional-formatting.js +60 -45
  71. package/dist/io/xlsx/data-validation.d.ts +1 -1
  72. package/dist/io/xlsx/data-validation.js +35 -33
  73. package/dist/io/xlsx/hyperlinks.js +6 -14
  74. package/dist/io/xlsx/images.js +19 -22
  75. package/dist/io/xlsx/read-pivot.js +2 -8
  76. package/dist/io/xlsx/read-shared-strings.js +8 -47
  77. package/dist/io/xlsx/read-styles.js +2 -1
  78. package/dist/io/xlsx/read-worksheet.js +14 -16
  79. package/dist/io/xlsx/read.d.ts +1 -1
  80. package/dist/io/xlsx/read.js +44 -74
  81. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  82. package/dist/io/xlsx/rich-runs.js +94 -30
  83. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  84. package/dist/io/xlsx/sheet-properties.js +13 -9
  85. package/dist/io/xlsx/styles.d.ts +3 -15
  86. package/dist/io/xlsx/styles.js +63 -88
  87. package/dist/io/xlsx/tables.js +13 -13
  88. package/dist/io/xlsx/theme-xml.js +7 -4
  89. package/dist/io/xlsx/threaded-comments.js +19 -19
  90. package/dist/io/xlsx/workbook-xml.js +14 -8
  91. package/dist/io/xlsx/worksheet-xml.js +34 -37
  92. package/dist/io/xlsx/write-stream.d.ts +8 -1
  93. package/dist/io/xlsx/write-stream.js +10 -11
  94. package/dist/sha512.d.ts +2 -0
  95. package/dist/sha512.js +146 -0
  96. package/dist/vba/bytes.d.ts +1 -2
  97. package/dist/vba/bytes.js +1 -12
  98. package/dist/vba/dir-records.d.ts +53 -0
  99. package/dist/vba/dir-records.js +28 -0
  100. package/dist/vba/project-editor.js +5 -34
  101. package/dist/vba/project.js +2 -18
  102. package/dist/xml/xml-read.d.ts +60 -0
  103. package/dist/xml/xml-read.js +47 -0
  104. package/dist/xml/xml.d.ts +43 -1
  105. package/dist/xml/xml.js +17 -5
  106. package/package.json +21 -4
@@ -1,5 +1,6 @@
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 { boolPresent, boolStrict, coerceNumericLiteral, enumToken, localName, numFinite, numInteger, parseXml, TextCapture, } from '../../xml/xml-read.js';
3
+ import { boolAttr, checkedToken, escapeAttr, escapeText, numberText, stripFormulaEquals, textAttr, } from '../../xml/xml.js';
3
4
  import { colorAttrs, parseColor } from './color-xml.js';
4
5
  import { CF_EXT_URI, DATABAR_LINK_EXT_URI, XM_NS } from './namespaces.js';
5
6
  import { x14Ext } from './x14-ext.js';
@@ -61,7 +62,7 @@ function x14DataBarXml(ref, rule, guid) {
61
62
  `</x14:cfRule><xm:sqref>${escapeText(ref)}</xm:sqref></x14:conditionalFormatting>`);
62
63
  }
63
64
  function x14CfvoXml(cfvo) {
64
- const type = escapeAttr(cfvo.type);
65
+ const type = checkedToken(cfvo.type, isCfValueObjectType, 'conditional format value type');
65
66
  if (cfvo.value === undefined)
66
67
  return `<x14:cfvo type="${type}"/>`;
67
68
  return `<x14:cfvo type="${type}"><xm:f>${escapeText(String(cfvo.value))}</xm:f></x14:cfvo>`;
@@ -71,12 +72,16 @@ function cfRuleExtLinkXml(guid) {
71
72
  }
72
73
  function blockXml(cf, styles, priority, extLinks) {
73
74
  const rules = cf.rules.map((rule) => ruleXml(rule, styles, priority, extLinks)).join('');
75
+ if (rules === '')
76
+ return '';
74
77
  return `<conditionalFormatting sqref="${escapeAttr(cf.ref)}">${rules}</conditionalFormatting>`;
75
78
  }
76
79
  function ruleXml(rule, styles, priority, extLinks) {
77
80
  const p = rule.priority ?? priority.next;
78
81
  priority.next = Math.max(priority.next, p) + 1;
79
- const attrs = [`type="${escapeAttr(rule.type)}"`];
82
+ const attrs = [
83
+ `type="${checkedToken(rule.type, isConditionalFormattingType, 'conditional formatting type')}"`,
84
+ ];
80
85
  const dxfId = resolveDxfId(rule, styles);
81
86
  if (dxfId !== undefined)
82
87
  attrs.push(`dxfId="${dxfId}"`);
@@ -91,16 +96,18 @@ function ruleXml(rule, styles, priority, extLinks) {
91
96
  attrs.push('bottom="1"');
92
97
  if (rule.percent)
93
98
  attrs.push('percent="1"');
94
- if (rule.operator !== undefined)
95
- attrs.push(`operator="${escapeAttr(rule.operator)}"`);
99
+ if (rule.operator !== undefined) {
100
+ attrs.push(`operator="${checkedToken(rule.operator, isConditionalFormattingOperator, 'conditional formatting operator')}"`);
101
+ }
96
102
  if (rule.text !== undefined)
97
103
  attrs.push(`text="${escapeAttr(rule.text)}"`);
98
- if (rule.timePeriod !== undefined)
99
- attrs.push(`timePeriod="${escapeAttr(rule.timePeriod)}"`);
104
+ if (rule.timePeriod !== undefined) {
105
+ attrs.push(`timePeriod="${checkedToken(rule.timePeriod, isCfTimePeriod, 'time period')}"`);
106
+ }
100
107
  if (rule.rank !== undefined)
101
- attrs.push(`rank="${rule.rank}"`);
108
+ attrs.push(`rank="${numberText(rule.rank)}"`);
102
109
  if (rule.stdDev !== undefined)
103
- attrs.push(`stdDev="${rule.stdDev}"`);
110
+ attrs.push(`stdDev="${numberText(rule.stdDev)}"`);
104
111
  let body = SCALE_TYPES.has(rule.type) ? scaleXml(rule) : formulaeXml(rule.formulae);
105
112
  const extGuid = extLinks.get(rule);
106
113
  if (extGuid !== undefined)
@@ -112,8 +119,9 @@ function ruleXml(rule, styles, priority, extLinks) {
112
119
  function resolveDxfId(rule, styles) {
113
120
  if (SCALE_TYPES.has(rule.type))
114
121
  return undefined;
115
- if (rule.dxfId !== undefined)
116
- return Number(rule.dxfId);
122
+ const preserved = dxfIndex(rule.dxfId);
123
+ if (preserved !== undefined)
124
+ return preserved;
117
125
  if (rule.style !== undefined)
118
126
  return styles.differentialStyleId(rule.style);
119
127
  return undefined;
@@ -142,34 +150,34 @@ function colorScaleXml(rule) {
142
150
  return `<colorScale>${anchors}${colors}</colorScale>`;
143
151
  }
144
152
  function iconSetXml(rule) {
145
- const name = rule.iconSet !== undefined ? ` iconSet="${escapeAttr(rule.iconSet)}"` : '';
153
+ const name = rule.iconSet === undefined
154
+ ? ''
155
+ : ` iconSet="${checkedToken(rule.iconSet, isIconSetType, 'icon set')}"`;
146
156
  const anchors = (rule.cfvo ?? []).map(cfvoXml).join('');
147
157
  return `<iconSet${name}>${anchors}</iconSet>`;
148
158
  }
149
159
  function cfvoXml(cfvo) {
150
- const val = cfvo.value !== undefined ? ` val="${escapeAttr(String(cfvo.value))}"` : '';
151
- return `<cfvo type="${escapeAttr(cfvo.type)}"${val}/>`;
160
+ const type = checkedToken(cfvo.type, isCfValueObjectType, 'conditional format value type');
161
+ const val = cfvo.value === undefined ? '' : textAttr('val', String(cfvo.value));
162
+ return `<cfvo type="${type}"${val}/>`;
152
163
  }
153
164
  export function parseConditionalFormattings(xml) {
154
165
  const blocks = [];
155
166
  let block;
156
167
  let draft;
157
168
  let scale;
158
- let capturingFormula = false;
159
- let formulaText = '';
169
+ const formulaCapture = new TextCapture('formula');
160
170
  const linked = [];
161
171
  const extById = new Map();
162
172
  let x14Ext;
163
173
  let x14ExtId;
164
- let capturingX14Id = false;
165
- let x14IdText = '';
174
+ const x14IdCapture = new TextCapture('id');
166
175
  parseXml(xml, {
167
176
  onOpen(name, attrs, selfClosing) {
168
177
  const ln = localName(name);
169
178
  if (name.includes(':')) {
170
179
  if (ln === 'id' && draft !== undefined) {
171
- capturingX14Id = true;
172
- x14IdText = '';
180
+ x14IdCapture.open(ln, selfClosing);
173
181
  }
174
182
  else if (ln === 'cfRule') {
175
183
  x14Ext = attrs.type === 'dataBar' && attrs.id !== undefined ? emptyExt() : undefined;
@@ -191,7 +199,9 @@ export function parseConditionalFormattings(xml) {
191
199
  }
192
200
  else if (ln === 'cfRule' && block !== undefined) {
193
201
  if (selfClosing) {
194
- block.rules.push(finalizeRule(newDraft(attrs)));
202
+ const rule = finalizeRule(newDraft(attrs));
203
+ if (rule !== undefined)
204
+ block.rules.push(rule);
195
205
  }
196
206
  else {
197
207
  draft = newDraft(attrs);
@@ -201,8 +211,8 @@ export function parseConditionalFormattings(xml) {
201
211
  else if (draft !== undefined &&
202
212
  (ln === 'dataBar' || ln === 'colorScale' || ln === 'iconSet')) {
203
213
  scale = ln;
204
- if (ln === 'iconSet' && attrs.iconSet !== undefined)
205
- draft.iconSet = attrs.iconSet;
214
+ if (ln === 'iconSet')
215
+ draft.iconSet = enumToken(attrs.iconSet, isIconSetType);
206
216
  }
207
217
  else if (draft !== undefined && ln === 'cfvo') {
208
218
  draft.cfvo.push(parseCfvo(attrs));
@@ -215,23 +225,20 @@ export function parseConditionalFormattings(xml) {
215
225
  draft.colors.push(color);
216
226
  }
217
227
  else if (draft !== undefined && ln === 'formula') {
218
- capturingFormula = true;
219
- formulaText = '';
228
+ formulaCapture.open(ln, selfClosing);
220
229
  }
221
230
  },
222
231
  onText(chunk) {
223
- if (capturingFormula)
224
- formulaText += chunk;
225
- if (capturingX14Id)
226
- x14IdText += chunk;
232
+ formulaCapture.text(chunk);
233
+ x14IdCapture.text(chunk);
227
234
  },
228
235
  onClose(name) {
229
236
  const ln = localName(name);
230
237
  if (name.includes(':')) {
231
- if (ln === 'id' && capturingX14Id) {
238
+ const id = x14IdCapture.close(ln);
239
+ if (id !== undefined) {
232
240
  if (draft !== undefined)
233
- draft.x14Id = x14IdText;
234
- capturingX14Id = false;
241
+ draft.x14Id = id;
235
242
  }
236
243
  else if (ln === 'cfRule' && x14Ext !== undefined && x14ExtId !== undefined) {
237
244
  extById.set(x14ExtId, x14Ext);
@@ -240,20 +247,22 @@ export function parseConditionalFormattings(xml) {
240
247
  }
241
248
  return;
242
249
  }
243
- if (ln === 'formula' && capturingFormula) {
250
+ const formula = formulaCapture.close(ln);
251
+ if (formula !== undefined) {
244
252
  if (draft !== undefined)
245
- draft.formulae.push(coerceNumericLiteral(formulaText));
246
- capturingFormula = false;
253
+ draft.formulae.push(coerceNumericLiteral(formula));
247
254
  }
248
255
  else if (ln === 'dataBar' || ln === 'colorScale' || ln === 'iconSet') {
249
256
  scale = undefined;
250
257
  }
251
258
  else if (ln === 'cfRule' && draft !== undefined) {
252
259
  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 });
260
+ if (rule !== undefined) {
261
+ if (block !== undefined)
262
+ block.rules.push(rule);
263
+ if (draft.x14Id !== undefined)
264
+ linked.push({ rule, id: draft.x14Id });
265
+ }
257
266
  draft = undefined;
258
267
  }
259
268
  else if (ln === 'conditionalFormatting' && block !== undefined) {
@@ -287,12 +296,12 @@ export function parseDxfs(stylesXml) {
287
296
  }
288
297
  function newDraft(attrs) {
289
298
  return {
290
- type: attrs.type ?? '',
299
+ type: enumToken(attrs.type, isConditionalFormattingType),
291
300
  priority: numFinite(attrs.priority),
292
301
  stopIfTrue: boolStrict(attrs.stopIfTrue),
293
- operator: attrs.operator,
302
+ operator: enumToken(attrs.operator, isConditionalFormattingOperator),
294
303
  text: attrs.text,
295
- timePeriod: attrs.timePeriod,
304
+ timePeriod: enumToken(attrs.timePeriod, isCfTimePeriod),
296
305
  rank: numFinite(attrs.rank),
297
306
  stdDev: numFinite(attrs.stdDev),
298
307
  percent: boolStrict(attrs.percent),
@@ -308,10 +317,15 @@ function newDraft(attrs) {
308
317
  x14Id: undefined,
309
318
  };
310
319
  }
320
+ function dxfIndex(value) {
321
+ return numInteger(value, 0);
322
+ }
311
323
  function parseIndexAttr(value) {
312
- return numInteger(value, 0) === undefined ? undefined : value;
324
+ return dxfIndex(value) === undefined ? undefined : value;
313
325
  }
314
326
  function finalizeRule(draft) {
327
+ if (draft.type === undefined)
328
+ return undefined;
315
329
  const rule = { type: draft.type };
316
330
  if (draft.priority !== undefined)
317
331
  rule.priority = draft.priority;
@@ -350,7 +364,8 @@ function finalizeRule(draft) {
350
364
  return rule;
351
365
  }
352
366
  function parseCfvo(attrs) {
353
- const type = (attrs.type ?? 'num');
367
+ const raw = attrs.type;
368
+ const type = raw !== undefined && isCfValueObjectType(raw) ? raw : 'num';
354
369
  const cfvo = { type };
355
370
  if (attrs.val !== undefined) {
356
371
  cfvo.value = type === 'formula' ? attrs.val : coerceNumericLiteral(attrs.val);
@@ -1,4 +1,4 @@
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
3
  /** The standard `<dataValidations>` element for the rules stored in the legacy form, or '' when the
4
4
  * sheet has none of them, so a sheet with only extended (or no) validations stays byte-clean here.
@@ -1,5 +1,7 @@
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 { boolStrict, coerceNumericLiteral, localName, parseXml, TextCapture, } from '../../xml/xml-read.js';
4
+ import { checkedToken, escapeAttr, escapeText, stripFormulaEquals, textAttr } from '../../xml/xml.js';
3
5
  import { DATA_VALIDATION_EXT_URI, XM_NS } from './namespaces.js';
4
6
  import { x14Ext } from './x14-ext.js';
5
7
  const TYPED = new Set(['whole', 'decimal', 'date', 'time', 'textLength']);
@@ -18,16 +20,20 @@ export function dataValidationsExtXml(entries) {
18
20
  return x14Ext(DATA_VALIDATION_EXT_URI, `<x14:dataValidations count="${extended.length}" xmlns:xm="${XM_NS}">${items}</x14:dataValidations>`);
19
21
  }
20
22
  function ruleAttrs(rule) {
21
- return (` type="${rule.type}"` +
22
- (rule.errorStyle !== undefined ? ` errorStyle="${rule.errorStyle}"` : '') +
23
- (rule.operator !== undefined ? ` operator="${rule.operator}"` : '') +
23
+ return (` type="${checkedToken(rule.type, isDataValidationType, 'data validation type')}"` +
24
+ (rule.errorStyle === undefined
25
+ ? ''
26
+ : ` errorStyle="${checkedToken(rule.errorStyle, isDataValidationErrorStyle, 'data validation error style')}"`) +
27
+ (rule.operator === undefined
28
+ ? ''
29
+ : ` operator="${checkedToken(rule.operator, isDataValidationOperator, 'data validation operator')}"`) +
24
30
  (rule.allowBlank ? ' allowBlank="1"' : '') +
25
31
  (rule.showInputMessage ? ' showInputMessage="1"' : '') +
26
32
  (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)}"` : ''));
33
+ textAttr('errorTitle', rule.errorTitle) +
34
+ textAttr('error', rule.error) +
35
+ textAttr('promptTitle', rule.promptTitle) +
36
+ textAttr('prompt', rule.prompt));
31
37
  }
32
38
  function dataValidationXml(sqref, rule) {
33
39
  const [f1, f2] = operands(rule);
@@ -100,11 +106,12 @@ function buildEntry(attrs, formulae) {
100
106
  }
101
107
  function buildRule(attrs, formulae) {
102
108
  const { type } = attrs;
103
- if (type === undefined)
109
+ if (type === undefined || !isDataValidationType(type))
104
110
  return undefined;
105
- const rule = { type: type };
111
+ const rule = { type };
106
112
  if (attrs.operator !== undefined) {
107
- rule.operator = attrs.operator;
113
+ if (isDataValidationOperator(attrs.operator))
114
+ rule.operator = attrs.operator;
108
115
  }
109
116
  else if (TYPED.has(type)) {
110
117
  rule.operator = 'between';
@@ -115,7 +122,7 @@ function buildRule(attrs, formulae) {
115
122
  rule.showInputMessage = true;
116
123
  if (boolStrict(attrs.showErrorMessage))
117
124
  rule.showErrorMessage = true;
118
- if (attrs.errorStyle !== undefined)
125
+ if (attrs.errorStyle !== undefined && isDataValidationErrorStyle(attrs.errorStyle))
119
126
  rule.errorStyle = attrs.errorStyle;
120
127
  if (attrs.error !== undefined)
121
128
  rule.error = attrs.error;
@@ -136,10 +143,9 @@ export function parseExtendedDataValidations(xml) {
136
143
  const entries = [];
137
144
  let current;
138
145
  let slot;
139
- let capture;
140
- let text = '';
146
+ const capture = new TextCapture(['f', 'sqref']);
141
147
  parseXml(xml, {
142
- onOpen(name, attrs) {
148
+ onOpen(name, attrs, selfClosing) {
143
149
  const ln = localName(name);
144
150
  const prefixed = name.includes(':');
145
151
  if (ln === 'dataValidation' && prefixed) {
@@ -151,30 +157,24 @@ export function parseExtendedDataValidations(xml) {
151
157
  else if (current !== undefined && prefixed && ln === 'formula2') {
152
158
  slot = 1;
153
159
  }
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 = '';
160
+ else if (current !== undefined) {
161
+ capture.open(ln, selfClosing);
161
162
  }
162
163
  },
163
164
  onText(chunk) {
164
- if (capture !== undefined)
165
- text += chunk;
165
+ capture.text(chunk);
166
166
  },
167
167
  onClose(name) {
168
168
  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)
169
+ const text = capture.close(ln);
170
+ if (text !== undefined) {
171
+ if (ln === 'f') {
172
+ if (current !== undefined && slot !== undefined)
173
+ current.formulae[slot] = text;
174
+ }
175
+ else if (current !== undefined) {
176
176
  current.sqref = text;
177
- capture = undefined;
177
+ }
178
178
  }
179
179
  else if ((ln === 'formula1' || ln === 'formula2') && name.includes(':')) {
180
180
  slot = undefined;
@@ -197,6 +197,8 @@ function buildExtendedEntry(attrs, formulae, sqref) {
197
197
  }
198
198
  export function applyDataValidations(sheet, entries) {
199
199
  for (const { sqref, rule, extended } of entries) {
200
+ if (decodeSqrefRects(sqref).length === 0)
201
+ continue;
200
202
  sheet.addDataValidation(sqref, rule, extended ? { extended: true } : {});
201
203
  }
202
204
  }
@@ -1,7 +1,7 @@
1
- import { decodeRange } from '../../core/address.js';
1
+ import { tryDecodeRange } from '../../core/address.js';
2
2
  import { isHyperlinkValue, isRichTextValue } from '../../core/value.js';
3
3
  import { localName, parseXml } from '../../xml/xml-read.js';
4
- import { escapeAttr } from '../../xml/xml.js';
4
+ import { escapeAttr, textAttr } from '../../xml/xml.js';
5
5
  export function collectHyperlinks(sheet) {
6
6
  const links = [];
7
7
  for (const { cells } of sheet.rows()) {
@@ -33,8 +33,8 @@ export function hyperlinksXml(links) {
33
33
  const items = links
34
34
  .map((link) => {
35
35
  const rid = link.relId !== undefined ? ` r:id="${link.relId}"` : '';
36
- const location = link.location !== undefined ? ` location="${escapeAttr(link.location)}"` : '';
37
- const tooltip = link.tooltip !== undefined ? ` tooltip="${escapeAttr(link.tooltip)}"` : '';
36
+ const location = textAttr('location', link.location);
37
+ const tooltip = textAttr('tooltip', link.tooltip);
38
38
  return `<hyperlink ref="${escapeAttr(link.ref)}"${rid}${location}${tooltip}/>`;
39
39
  })
40
40
  .join('');
@@ -64,8 +64,8 @@ export function applyHyperlinks(sheet, links, targetOf) {
64
64
  const target = resolveTarget(link, targetOf);
65
65
  if (target === undefined)
66
66
  continue;
67
- const decoded = decodeRefSafe(link.ref);
68
- if (decoded === undefined)
67
+ const decoded = tryDecodeRange(link.ref);
68
+ if (decoded === undefined || decoded.tl.col === undefined || decoded.tl.row === undefined)
69
69
  continue;
70
70
  const cell = sheet.getCell(decoded.tl.address);
71
71
  const cellValue = cell.value;
@@ -80,14 +80,6 @@ export function applyHyperlinks(sheet, links, targetOf) {
80
80
  cell.value = value;
81
81
  }
82
82
  }
83
- function decodeRefSafe(ref) {
84
- try {
85
- return decodeRange(ref);
86
- }
87
- catch {
88
- return undefined;
89
- }
90
- }
91
83
  function resolveTarget(link, targetOf) {
92
84
  if (link.rid !== undefined) {
93
85
  const base = targetOf(link.rid);
@@ -1,6 +1,6 @@
1
- import { isOneCellAnchor, } from '../../core/image.js';
2
- import { localName, numFinite, parseXml } from '../../xml/xml-read.js';
3
- import { XML_DECLARATION } from '../../xml/xml.js';
1
+ import { isImageEditAs, isOneCellAnchor, } from '../../core/image.js';
2
+ import { enumToken, localName, numFinite, parseXml, TextCapture } from '../../xml/xml-read.js';
3
+ import { checkedToken, numAttr, numberText, XML_DECLARATION } from '../../xml/xml.js';
4
4
  import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
5
5
  import { relationship, relationshipsPart } from '../opc/rels.js';
6
6
  import { DRAWINGML_NS, XDR_NS } from './namespaces.js';
@@ -35,7 +35,7 @@ function anchorXml(image, id) {
35
35
  : twoCellAnchorXml(anchor.from, anchor.to, anchor.editAs ?? 'oneCell', anchor.rotation, image.embedId, id);
36
36
  }
37
37
  function twoCellAnchorXml(from, to, editAs, rotation, embedId, id) {
38
- return (`<xdr:twoCellAnchor editAs="${editAs}">` +
38
+ return (`<xdr:twoCellAnchor editAs="${checkedToken(editAs, isImageEditAs, 'image anchor edit mode')}">` +
39
39
  `<xdr:from>${anchorPointXml(from)}</xdr:from>` +
40
40
  `<xdr:to>${anchorPointXml(to)}</xdr:to>` +
41
41
  picXml(embedId, id, rotation) +
@@ -45,13 +45,13 @@ function twoCellAnchorXml(from, to, editAs, rotation, embedId, id) {
45
45
  function oneCellAnchorXml(from, ext, rotation, embedId, id) {
46
46
  return ('<xdr:oneCellAnchor>' +
47
47
  `<xdr:from>${anchorPointXml(from)}</xdr:from>` +
48
- `<xdr:ext cx="${ext.cx}" cy="${ext.cy}"/>` +
48
+ `<xdr:ext${numAttr('cx', ext.cx)}${numAttr('cy', ext.cy)}/>` +
49
49
  picXml(embedId, id, rotation) +
50
50
  '<xdr:clientData/>' +
51
51
  '</xdr:oneCellAnchor>');
52
52
  }
53
53
  function picXml(embedId, id, rotation) {
54
- const xfrm = rotation !== undefined ? `<a:xfrm rot="${rotation}"/>` : '';
54
+ const xfrm = rotation !== undefined ? `<a:xfrm${numAttr('rot', rotation)}/>` : '';
55
55
  return ('<xdr:pic>' +
56
56
  `<xdr:nvPicPr><xdr:cNvPr id="${id}" name="Picture ${id}"/>` +
57
57
  '<xdr:cNvPicPr><a:picLocks noChangeAspect="1"/></xdr:cNvPicPr></xdr:nvPicPr>' +
@@ -61,8 +61,10 @@ function picXml(embedId, id, rotation) {
61
61
  '</xdr:pic>');
62
62
  }
63
63
  function anchorPointXml(point) {
64
- return (`<xdr:col>${point.col}</xdr:col><xdr:colOff>${point.colOff ?? 0}</xdr:colOff>` +
65
- `<xdr:row>${point.row}</xdr:row><xdr:rowOff>${point.rowOff ?? 0}</xdr:rowOff>`);
64
+ return (`<xdr:col>${numberText(point.col)}</xdr:col>` +
65
+ `<xdr:colOff>${numberText(point.colOff ?? 0)}</xdr:colOff>` +
66
+ `<xdr:row>${numberText(point.row)}</xdr:row>` +
67
+ `<xdr:rowOff>${numberText(point.rowOff ?? 0)}</xdr:rowOff>`);
66
68
  }
67
69
  export function drawingRelsXml(mediaTargets) {
68
70
  return relationshipsPart(mediaTargets.map((target, i) => relationship(`rId${i + 1}`, IMAGE_REL_TYPE, target)));
@@ -70,7 +72,6 @@ export function drawingRelsXml(mediaTargets) {
70
72
  function blankPoint() {
71
73
  return { col: 0, row: 0, colOff: 0, rowOff: 0 };
72
74
  }
73
- const EDIT_AS = new Set(['oneCell', 'twoCell', 'absolute']);
74
75
  export function parseDrawing(xml) {
75
76
  const anchors = [];
76
77
  let from = null;
@@ -81,10 +82,9 @@ export function parseDrawing(xml) {
81
82
  let embed;
82
83
  let target = null;
83
84
  let picDepth = 0;
84
- let coord = '';
85
- let text = '';
85
+ const coord = new TextCapture(COORDINATES);
86
86
  parseXml(xml, {
87
- onOpen(name, attrs) {
87
+ onOpen(name, attrs, selfClosing) {
88
88
  const local = localName(name);
89
89
  if (local === 'twoCellAnchor' || local === 'oneCellAnchor') {
90
90
  from = blankPoint();
@@ -92,8 +92,7 @@ export function parseDrawing(xml) {
92
92
  ext = undefined;
93
93
  rotation = undefined;
94
94
  embed = undefined;
95
- const mode = attrs.editAs;
96
- editAs = mode !== undefined && EDIT_AS.has(mode) ? mode : undefined;
95
+ editAs = enumToken(attrs.editAs, isImageEditAs);
97
96
  }
98
97
  else if (local === 'pic') {
99
98
  picDepth++;
@@ -120,22 +119,20 @@ export function parseDrawing(xml) {
120
119
  if (value !== undefined)
121
120
  embed = value;
122
121
  }
123
- else if (target !== null && COORDINATES.has(local)) {
124
- coord = local;
125
- text = '';
122
+ else if (target !== null) {
123
+ coord.open(local, selfClosing);
126
124
  }
127
125
  },
128
126
  onText(chunk) {
129
- if (coord !== '')
130
- text += chunk;
127
+ coord.text(chunk);
131
128
  },
132
129
  onClose(name) {
133
130
  const local = localName(name);
134
- if (target !== null && coord === local && COORDINATES.has(local)) {
131
+ const text = coord.close(local);
132
+ if (text !== undefined) {
135
133
  const value = numFinite(text);
136
- if (value !== undefined)
134
+ if (target !== null && value !== undefined)
137
135
  setCoordinate(target, local, value);
138
- coord = '';
139
136
  }
140
137
  else if (local === 'from' || local === 'to') {
141
138
  target = null;
@@ -1,4 +1,4 @@
1
- import { pivotMetricFromSubtotal, } from '../../core/pivot-table.js';
1
+ import { isDeclarablePivotSourceKind, pivotMetricFromSubtotal, } from '../../core/pivot-table.js';
2
2
  import { localName, numInteger, parseXml } from '../../xml/xml-read.js';
3
3
  export function parsePivotTable(tableXml, cacheXml) {
4
4
  const { fields, source } = parsePivotCacheDefinition(cacheXml);
@@ -85,16 +85,10 @@ function parsePivotTableDefinition(tableXml) {
85
85
  });
86
86
  return { name, cacheId, rowFields, columnFields, valueField, valueCaption, metric };
87
87
  }
88
- const SOURCE_KINDS = new Set([
89
- 'worksheet',
90
- 'external',
91
- 'consolidation',
92
- 'scenario',
93
- ]);
94
88
  function sourceKind(type) {
95
89
  if (type === undefined)
96
90
  return 'worksheet';
97
- return SOURCE_KINDS.has(type) ? type : 'unknown';
91
+ return isDeclarablePivotSourceKind(type) ? type : 'unknown';
98
92
  }
99
93
  function toIndex(value) {
100
94
  return numInteger(value, 0) ?? -1;
@@ -1,60 +1,21 @@
1
- import { decodeSpreadsheetText, localName, parseXml } from '../../xml/xml-read.js';
1
+ import { localName, parseXml } from '../../xml/xml-read.js';
2
2
  import { RunAccumulator } from './rich-runs.js';
3
3
  export function parseSharedStrings(xml) {
4
4
  if (xml === '')
5
5
  return [];
6
6
  const strings = [];
7
- let plain = '';
8
- const runs = new RunAccumulator();
9
- let isRich = false;
10
- let capture = false;
11
- let text = '';
7
+ const runs = new RunAccumulator({ container: 'si', readRuns: true });
12
8
  parseXml(xml, {
13
- onOpen(name, attrs) {
14
- const local = localName(name);
15
- switch (local) {
16
- case 'si':
17
- plain = '';
18
- runs.beginContainer();
19
- isRich = false;
20
- break;
21
- case 'r':
22
- isRich = true;
23
- runs.beginRun();
24
- break;
25
- case 'rPr':
26
- runs.beginProperties();
27
- break;
28
- case 't':
29
- capture = true;
30
- text = '';
31
- break;
32
- default:
33
- runs.applyProperty(local, attrs);
34
- break;
35
- }
9
+ onOpen(name, attrs, selfClosing) {
10
+ runs.open(localName(name), attrs, selfClosing);
36
11
  },
37
12
  onText(chunk) {
38
- if (capture)
39
- text += chunk;
13
+ runs.text(chunk);
40
14
  },
41
15
  onClose(name) {
42
- const local = localName(name);
43
- switch (local) {
44
- case 't': {
45
- const decoded = decodeSpreadsheetText(text);
46
- if (!runs.appendText(decoded))
47
- plain += decoded;
48
- capture = false;
49
- break;
50
- }
51
- case 'r':
52
- runs.endRun();
53
- break;
54
- case 'si':
55
- strings.push(isRich ? { richText: runs.runs } : plain);
56
- break;
57
- }
16
+ if (runs.close(localName(name)) !== 'container')
17
+ return;
18
+ strings.push(runs.isRich ? { richText: runs.runs } : runs.plainText);
58
19
  },
59
20
  });
60
21
  return strings;
@@ -4,6 +4,7 @@ import { numFmtCodeFor, resolveStyleTable, } from '../style/xf-style.js';
4
4
  import { parseColor } from './color-xml.js';
5
5
  const BORDER_EDGE_NAMES = ['left', 'right', 'top', 'bottom', 'diagonal'];
6
6
  const BORDER_EDGES = new Set(BORDER_EDGE_NAMES);
7
+ const isBorderEdgeName = (name) => BORDER_EDGES.has(name);
7
8
  const STYLE_EMPTY_CLOSES = new Set([
8
9
  'font',
9
10
  'border',
@@ -192,7 +193,7 @@ function parseBorders(events) {
192
193
  borderDraft.diagonalDown = true;
193
194
  }
194
195
  else if (borderDraft !== null) {
195
- if (BORDER_EDGES.has(local)) {
196
+ if (isBorderEdgeName(local)) {
196
197
  if (attrs.style !== undefined && isBorderStyle(attrs.style)) {
197
198
  currentEdge = local;
198
199
  borderDraft[currentEdge] = { style: attrs.style };