@shbernal/ts-xlsx 1.3.1 → 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 (186) 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 +72 -3
  5. package/dist/core/address.js +50 -10
  6. package/dist/core/autofilter.d.ts +13 -2
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/cell.d.ts +17 -16
  9. package/dist/core/cell.js +3 -7
  10. package/dist/core/color-resolution.d.ts +8 -8
  11. package/dist/core/color-resolution.js +7 -8
  12. package/dist/core/column.d.ts +8 -9
  13. package/dist/core/column.js +2 -4
  14. package/dist/core/comment-thread.d.ts +32 -23
  15. package/dist/core/comment-thread.js +4 -4
  16. package/dist/core/conditional-formatting-overlay.d.ts +6 -1
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +48 -8
  19. package/dist/core/conditional-formatting.js +88 -0
  20. package/dist/core/data-validation-overlay.d.ts +12 -1
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +12 -5
  23. package/dist/core/data-validation.js +34 -0
  24. package/dist/core/date.d.ts +3 -3
  25. package/dist/core/formula.d.ts +6 -6
  26. package/dist/core/grid-edits.d.ts +16 -0
  27. package/dist/core/grid-edits.js +29 -11
  28. package/dist/core/grid-shift.d.ts +19 -0
  29. package/dist/core/grid-shift.js +8 -0
  30. package/dist/core/image.d.ts +39 -2
  31. package/dist/core/image.js +10 -0
  32. package/dist/core/internal.d.ts +9 -1
  33. package/dist/core/limits.d.ts +4 -4
  34. package/dist/core/merge.d.ts +17 -12
  35. package/dist/core/merge.js +36 -5
  36. package/dist/core/page-setup.d.ts +20 -6
  37. package/dist/core/page-setup.js +8 -1
  38. package/dist/core/pivot-table.d.ts +4 -4
  39. package/dist/core/pivot-table.js +29 -20
  40. package/dist/core/preserved.d.ts +3 -3
  41. package/dist/core/protection.d.ts +7 -4
  42. package/dist/core/protection.js +20 -14
  43. package/dist/core/range.d.ts +16 -14
  44. package/dist/core/range.js +5 -5
  45. package/dist/core/row-input.d.ts +9 -8
  46. package/dist/core/row-input.js +12 -10
  47. package/dist/core/row.d.ts +7 -8
  48. package/dist/core/row.js +2 -4
  49. package/dist/core/style.d.ts +41 -14
  50. package/dist/core/style.js +82 -67
  51. package/dist/core/table-style.d.ts +9 -9
  52. package/dist/core/table-style.js +1 -1
  53. package/dist/core/table.d.ts +53 -51
  54. package/dist/core/table.js +83 -41
  55. package/dist/core/theme.d.ts +16 -44
  56. package/dist/core/theme.js +6 -148
  57. package/dist/core/value.d.ts +7 -7
  58. package/dist/core/workbook-protection.d.ts +2 -2
  59. package/dist/core/workbook-styles.d.ts +26 -0
  60. package/dist/core/workbook-styles.js +48 -0
  61. package/dist/core/workbook-theme.d.ts +25 -0
  62. package/dist/core/workbook-theme.js +49 -0
  63. package/dist/core/workbook-vba.d.ts +17 -0
  64. package/dist/core/workbook-vba.js +79 -0
  65. package/dist/core/workbook.d.ts +133 -87
  66. package/dist/core/workbook.js +74 -133
  67. package/dist/core/worksheet-comments.d.ts +17 -0
  68. package/dist/core/worksheet-comments.js +70 -0
  69. package/dist/core/worksheet-model.d.ts +4 -4
  70. package/dist/core/worksheet-model.js +1 -0
  71. package/dist/core/worksheet-pictures.d.ts +29 -0
  72. package/dist/core/worksheet-pictures.js +42 -0
  73. package/dist/core/worksheet.d.ts +100 -79
  74. package/dist/core/worksheet.js +63 -142
  75. package/dist/customui/errors.d.ts +1 -1
  76. package/dist/customui/ribbon.d.ts +3 -3
  77. package/dist/customui/ribbon.js +24 -23
  78. package/dist/entries/core.d.ts +7 -7
  79. package/dist/entries/core.js +1 -1
  80. package/dist/entries/csv.d.ts +1 -1
  81. package/dist/entries/node-unavailable.d.ts +15 -0
  82. package/dist/entries/node-unavailable.js +20 -0
  83. package/dist/entries/node.d.ts +1 -0
  84. package/dist/entries/node.js +1 -0
  85. package/dist/entries/vba.d.ts +1 -1
  86. package/dist/entries/vba.js +1 -1
  87. package/dist/entries/xlsx.d.ts +1 -1
  88. package/dist/entries/xlsx.js +1 -1
  89. package/dist/errors.d.ts +12 -12
  90. package/dist/io/csv/read.js +2 -3
  91. package/dist/io/csv/write.d.ts +19 -4
  92. package/dist/io/csv/write.js +34 -4
  93. package/dist/io/opc/errors.d.ts +6 -6
  94. package/dist/io/opc/inflate.d.ts +1 -1
  95. package/dist/io/opc/inflate.js +2 -12
  96. package/dist/io/opc/namespaces.d.ts +1 -1
  97. package/dist/io/opc/read-opc.d.ts +19 -3
  98. package/dist/io/opc/read-opc.js +14 -8
  99. package/dist/io/opc/read-options.d.ts +2 -2
  100. package/dist/io/opc/rels.d.ts +11 -0
  101. package/dist/io/opc/rels.js +3 -2
  102. package/dist/io/opc/sniff-format.d.ts +2 -2
  103. package/dist/io/style/xf-style.d.ts +44 -7
  104. package/dist/io/style/xf-style.js +27 -0
  105. package/dist/io/xlsb/errors.d.ts +1 -1
  106. package/dist/io/xlsb/formula.d.ts +4 -4
  107. package/dist/io/xlsb/primitives.d.ts +8 -8
  108. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  109. package/dist/io/xlsb/read-styles.js +2 -20
  110. package/dist/io/xlsb/read.d.ts +2 -2
  111. package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
  112. package/dist/io/xlsx/cell-accumulator.js +89 -46
  113. package/dist/io/xlsx/cell-value.d.ts +3 -3
  114. package/dist/io/xlsx/cell-value.js +9 -6
  115. package/dist/io/xlsx/color-xml.d.ts +1 -1
  116. package/dist/io/xlsx/color-xml.js +18 -23
  117. package/dist/io/xlsx/comments.d.ts +5 -5
  118. package/dist/io/xlsx/comments.js +17 -8
  119. package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
  120. package/dist/io/xlsx/conditional-formatting.js +64 -58
  121. package/dist/io/xlsx/data-validation.d.ts +3 -3
  122. package/dist/io/xlsx/data-validation.js +35 -33
  123. package/dist/io/xlsx/errors.d.ts +3 -3
  124. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  125. package/dist/io/xlsx/hyperlinks.js +10 -18
  126. package/dist/io/xlsx/images.d.ts +1 -1
  127. package/dist/io/xlsx/images.js +25 -28
  128. package/dist/io/xlsx/namespaces.d.ts +2 -2
  129. package/dist/io/xlsx/package-plan.js +5 -2
  130. package/dist/io/xlsx/read-pivot.js +4 -13
  131. package/dist/io/xlsx/read-rows.d.ts +13 -13
  132. package/dist/io/xlsx/read-rows.js +19 -55
  133. package/dist/io/xlsx/read-shared-strings.js +7 -44
  134. package/dist/io/xlsx/read-styles.d.ts +3 -3
  135. package/dist/io/xlsx/read-styles.js +42 -66
  136. package/dist/io/xlsx/read-worksheet.js +73 -129
  137. package/dist/io/xlsx/read.d.ts +4 -6
  138. package/dist/io/xlsx/read.js +104 -142
  139. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  140. package/dist/io/xlsx/rich-runs.js +94 -30
  141. package/dist/io/xlsx/rich-text.d.ts +1 -1
  142. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  143. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  144. package/dist/io/xlsx/sheet-properties.js +14 -10
  145. package/dist/io/xlsx/styles.d.ts +9 -22
  146. package/dist/io/xlsx/styles.js +65 -89
  147. package/dist/io/xlsx/tables.d.ts +2 -2
  148. package/dist/io/xlsx/tables.js +20 -25
  149. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  150. package/dist/io/xlsx/theme-xml.js +145 -0
  151. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  152. package/dist/io/xlsx/threaded-comments.js +26 -38
  153. package/dist/io/xlsx/workbook-xml.js +14 -8
  154. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  155. package/dist/io/xlsx/worksheet-xml.js +40 -48
  156. package/dist/io/xlsx/write-stream.d.ts +23 -16
  157. package/dist/io/xlsx/write-stream.js +13 -14
  158. package/dist/io/xlsx/write.d.ts +8 -7
  159. package/dist/io/xlsx/write.js +6 -3
  160. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  161. package/dist/sha512.d.ts +2 -0
  162. package/dist/sha512.js +146 -0
  163. package/dist/vba/bytes.d.ts +11 -0
  164. package/dist/vba/bytes.js +29 -0
  165. package/dist/vba/cfb-writer.d.ts +1 -1
  166. package/dist/vba/cfb.d.ts +1 -1
  167. package/dist/vba/cfb.js +15 -51
  168. package/dist/vba/codepage.d.ts +2 -2
  169. package/dist/vba/dir-records.d.ts +53 -0
  170. package/dist/vba/dir-records.js +28 -0
  171. package/dist/vba/errors.d.ts +2 -2
  172. package/dist/vba/index.d.ts +2 -2
  173. package/dist/vba/index.js +2 -2
  174. package/dist/vba/ms-ovba.d.ts +1 -1
  175. package/dist/vba/ms-ovba.js +1 -3
  176. package/dist/vba/project-editor.d.ts +9 -9
  177. package/dist/vba/project-editor.js +6 -44
  178. package/dist/vba/project.d.ts +5 -5
  179. package/dist/vba/project.js +3 -28
  180. package/dist/vba/vba-encoding.d.ts +1 -1
  181. package/dist/xml/errors.d.ts +1 -1
  182. package/dist/xml/xml-read.d.ts +98 -9
  183. package/dist/xml/xml-read.js +70 -0
  184. package/dist/xml/xml.d.ts +74 -10
  185. package/dist/xml/xml.js +39 -7
  186. package/package.json +59 -36
@@ -1,5 +1,6 @@
1
- import { boolAttr, escapeAttr, escapeText, stripFormulaEquals } from '../../xml/xml.js';
2
- import { boolStrict, coerceNumericLiteral, localName, parseXml } from '../../xml/xml-read.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,41 +150,41 @@ 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;
176
184
  x14ExtId = attrs.id;
177
185
  }
178
186
  else if (x14Ext !== undefined && ln === 'dataBar') {
179
- x14Ext.gradient = attrs.gradient !== '0';
187
+ x14Ext.gradient = boolPresent(attrs.gradient);
180
188
  }
181
189
  else if (x14Ext !== undefined && ln === 'negativeFillColor') {
182
190
  x14Ext.negativeFillColor = parseColor(attrs);
@@ -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,17 +296,17 @@ export function parseDxfs(stylesXml) {
287
296
  }
288
297
  function newDraft(attrs) {
289
298
  return {
290
- type: attrs.type ?? '',
291
- priority: parseFiniteAttr(attrs.priority),
299
+ type: enumToken(attrs.type, isConditionalFormattingType),
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,
296
- rank: parseFiniteAttr(attrs.rank),
297
- stdDev: parseFiniteAttr(attrs.stdDev),
304
+ timePeriod: enumToken(attrs.timePeriod, isCfTimePeriod),
305
+ rank: numFinite(attrs.rank),
306
+ stdDev: numFinite(attrs.stdDev),
298
307
  percent: boolStrict(attrs.percent),
299
308
  bottom: boolStrict(attrs.bottom),
300
- aboveAverage: attrs.aboveAverage === undefined ? undefined : attrs.aboveAverage !== '0',
309
+ aboveAverage: attrs.aboveAverage === undefined ? undefined : boolPresent(attrs.aboveAverage),
301
310
  equalAverage: boolStrict(attrs.equalAverage),
302
311
  dxfId: parseIndexAttr(attrs.dxfId),
303
312
  iconSet: undefined,
@@ -308,19 +317,15 @@ function newDraft(attrs) {
308
317
  x14Id: undefined,
309
318
  };
310
319
  }
311
- function parseFiniteAttr(value) {
312
- if (value === undefined)
313
- return undefined;
314
- const n = Number(value);
315
- return Number.isFinite(n) ? n : undefined;
320
+ function dxfIndex(value) {
321
+ return numInteger(value, 0);
316
322
  }
317
323
  function parseIndexAttr(value) {
318
- if (value === undefined)
319
- return undefined;
320
- const n = Number(value);
321
- return Number.isInteger(n) && n >= 0 ? value : undefined;
324
+ return dxfIndex(value) === undefined ? undefined : value;
322
325
  }
323
326
  function finalizeRule(draft) {
327
+ if (draft.type === undefined)
328
+ return undefined;
324
329
  const rule = { type: draft.type };
325
330
  if (draft.priority !== undefined)
326
331
  rule.priority = draft.priority;
@@ -359,7 +364,8 @@ function finalizeRule(draft) {
359
364
  return rule;
360
365
  }
361
366
  function parseCfvo(attrs) {
362
- const type = (attrs.type ?? 'num');
367
+ const raw = attrs.type;
368
+ const type = raw !== undefined && isCfValueObjectType(raw) ? raw : 'num';
363
369
  const cfvo = { type };
364
370
  if (attrs.val !== undefined) {
365
371
  cfvo.value = type === 'formula' ? attrs.val : coerceNumericLiteral(attrs.val);
@@ -1,12 +1,12 @@
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
- * sheet has none of them so a sheet with only extended (or no) validations stays byte-clean here.
4
+ * sheet has none of them, so a sheet with only extended (or no) validations stays byte-clean here.
5
5
  * The extended rules are emitted separately by {@link dataValidationsExtXml}. */
6
6
  export declare function dataValidationsXml(entries: readonly DataValidationEntry[]): string;
7
7
  /** The `<ext>` carrying the extended (`<x14:dataValidation>`) rules, or '' when the sheet declares
8
8
  * none. Emitted bare (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single
9
- * `<extLst>` beside the conditional-formatting extension a worksheet may carry at most one. */
9
+ * `<extLst>` beside the conditional-formatting extension: a worksheet may carry at most one. */
10
10
  export declare function dataValidationsExtXml(entries: readonly DataValidationEntry[]): string;
11
11
  /** Parse every standard `<dataValidation>` out of a worksheet part into range-bound rules. */
12
12
  export declare function parseDataValidations(xml: string): DataValidationEntry[];
@@ -1,5 +1,7 @@
1
- import { escapeAttr, escapeText, stripFormulaEquals } from '../../xml/xml.js';
2
- import { boolStrict, coerceNumericLiteral, localName, parseXml } from '../../xml/xml-read.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,14 +1,14 @@
1
1
  import { XlsxError } from '../../errors.ts';
2
2
  /**
3
3
  * Thrown when an `.xlsx` package's XML content is well-formed but does not describe a workbook this
4
- * library can act on a `xl/workbook.xml` that declares no worksheets, say.
4
+ * library can act on: a `xl/workbook.xml` that declares no worksheets, say.
5
5
  *
6
6
  * It sits between two neighbours. {@link XmlParseError} reports that the *markup* did not parse;
7
7
  * {@link UnsupportedFormatError} reports that the *container* is not one we read at all. This one
8
8
  * fires when both of those were fine and the document itself is nonetheless not a workbook.
9
9
  *
10
- * The reader is deliberately lenient about content it does not recognise an unknown element is
11
- * skipped, not fatal so this is rare by design, and reaching it means something a workbook cannot
10
+ * The reader is deliberately lenient about content it does not recognise (an unknown element is
11
+ * skipped, not fatal) so this is rare by design, and reaching it means something a workbook cannot
12
12
  * do without being corrupt.
13
13
  */
14
14
  export declare class XlsxParseError extends XlsxError {
@@ -37,6 +37,8 @@ interface ParsedHyperlink {
37
37
  /** Parse every `<hyperlink>` element out of a worksheet part. */
38
38
  export declare function parseSheetHyperlinks(xml: string): ParsedHyperlink[];
39
39
  /** Fold parsed hyperlinks onto a sheet's cells, wrapping each cell's existing value (its visible
40
- * label) into a {@link HyperlinkValue}. `rels` maps a relationship id to its target URL. */
41
- export declare function applyHyperlinks(sheet: Worksheet, links: readonly ParsedHyperlink[], rels: Map<string, string>): void;
40
+ * label) into a {@link HyperlinkValue}. `targetOf` resolves a relationship id to its raw Target: a
41
+ * URL for the external links hyperlinks almost always are, so it must stay unresolved against the
42
+ * package rather than being handed over as a part path. */
43
+ export declare function applyHyperlinks(sheet: Worksheet, links: readonly ParsedHyperlink[], targetOf: (relId: string) => string | undefined): void;
42
44
  export {};
@@ -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
- import { escapeAttr } from '../../xml/xml.js';
4
3
  import { localName, parseXml } from '../../xml/xml-read.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('');
@@ -59,13 +59,13 @@ export function parseSheetHyperlinks(xml) {
59
59
  });
60
60
  return links;
61
61
  }
62
- export function applyHyperlinks(sheet, links, rels) {
62
+ export function applyHyperlinks(sheet, links, targetOf) {
63
63
  for (const link of links) {
64
- const target = resolveTarget(link, rels);
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,17 +80,9 @@ export function applyHyperlinks(sheet, links, rels) {
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
- function resolveTarget(link, rels) {
83
+ function resolveTarget(link, targetOf) {
92
84
  if (link.rid !== undefined) {
93
- const base = rels.get(link.rid);
85
+ const base = targetOf(link.rid);
94
86
  if (base === undefined)
95
87
  return undefined;
96
88
  return link.location !== undefined ? `${base}#${link.location}` : base;
@@ -27,7 +27,7 @@ export interface ParsedImageAnchor {
27
27
  * Anchors that are not pictures (a chart, a shape) carry no `<a:blip r:embed>` and are skipped, so a
28
28
  * mixed drawing yields only its images. */
29
29
  export declare function parseDrawing(xml: string): ParsedImageAnchor[];
30
- /** Whether a drawing part holds anchor content beyond plain pictures a chart, shape, connector, or
30
+ /** Whether a drawing part holds anchor content beyond plain pictures: a chart, shape, connector, or
31
31
  * group. Excel packs every one of a sheet's anchors into a single drawing part, so a sheet with both a
32
32
  * picture and a chart yields a mixed drawing; modeling only its pictures and re-serialising from them
33
33
  * would drop the chart. The reader uses this to fall back to whole-drawing byte-preservation instead. */
@@ -1,6 +1,6 @@
1
- import { isOneCellAnchor, } from '../../core/image.js';
2
- import { XML_DECLARATION } from '../../xml/xml.js';
3
- import { localName, parseXml } from '../../xml/xml-read.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,15 +92,14 @@ 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++;
100
99
  }
101
100
  else if (local === 'xfrm' && picDepth > 0) {
102
- const rot = Number(attrs.rot);
103
- if (Number.isFinite(rot) && rot !== 0)
101
+ const rot = numFinite(attrs.rot);
102
+ if (rot !== undefined && rot !== 0)
104
103
  rotation = rot;
105
104
  }
106
105
  else if (local === 'from') {
@@ -110,9 +109,9 @@ export function parseDrawing(xml) {
110
109
  target = to;
111
110
  }
112
111
  else if (local === 'ext' && picDepth === 0) {
113
- const cx = Number(attrs.cx);
114
- const cy = Number(attrs.cy);
115
- if (Number.isFinite(cx) && Number.isFinite(cy))
112
+ const cx = numFinite(attrs.cx, 0);
113
+ const cy = numFinite(attrs.cy, 0);
114
+ if (cx !== undefined && cy !== undefined)
116
115
  ext = { cx, cy };
117
116
  }
118
117
  else if (local === 'blip') {
@@ -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)) {
135
- const value = Number(text);
136
- if (Number.isFinite(value))
131
+ const text = coord.close(local);
132
+ if (text !== undefined) {
133
+ const value = numFinite(text);
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;