@shbernal/ts-xlsx 3.1.0 → 3.2.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 (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -3,20 +3,25 @@ import type { Worksheet } from '../../core/worksheet.ts';
3
3
  import { type CollectingPass } from '../../xml/xml-read.ts';
4
4
  /** The standard `<dataValidations>` element for the rules stored in the legacy form, or '' when the
5
5
  * sheet has none of them, so a sheet with only extended (or no) validations stays byte-clean here.
6
- * The extended rules are emitted separately by {@link dataValidationsExtXml}. */
7
- export declare function dataValidationsXml(entries: readonly DataValidationEntry[]): string;
6
+ * The extended rules are emitted separately by {@link dataValidationsExtXml}.
7
+ *
8
+ * An operand is stored with the function prefixes a cell formula takes, as Excel stores it;
9
+ * `formulaNames` is `formulaNamesInScope` for the sheet. */
10
+ export declare function dataValidationsXml(entries: readonly DataValidationEntry[], formulaNames: ReadonlySet<string>): string;
8
11
  /** The `<ext>` carrying the extended (`<x14:dataValidation>`) rules, or '' when the sheet declares
9
12
  * none. Emitted bare (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single
10
13
  * `<extLst>` beside the conditional-formatting extension: a worksheet may carry at most one. */
11
- export declare function dataValidationsExtXml(entries: readonly DataValidationEntry[]): string;
14
+ export declare function dataValidationsExtXml(entries: readonly DataValidationEntry[], formulaNames: ReadonlySet<string>): string;
12
15
  /** 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[]>;
16
+ * reading the part alongside its other readers in one parse. An operand sheds its function prefixes
17
+ * against `definedNames`, the workbook's names as `definedNameKeys` spells them. */
18
+ export declare function dataValidationPass(definedNames: ReadonlySet<string>): CollectingPass<DataValidationEntry[]>;
15
19
  /** A pass gathering the extended `<x14:dataValidation>` elements of a worksheet's `<extLst>`, so a
16
20
  * cross-sheet or whole-column list validation Excel stored only in the 2009 extension form is read
17
21
  * 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[]>;
22
+ * symmetrically, handles only the prefixed elements. An operand sheds its function prefixes against
23
+ * `definedNames`, as the standard pass's does. */
24
+ export declare function extendedDataValidationPass(definedNames: ReadonlySet<string>): CollectingPass<DataValidationEntry[]>;
20
25
  /** Fold parsed validations onto a sheet, each bound to its original range and carrying its form: an
21
26
  * `extended` entry is re-attached as extended so a round-trip writes it back to the x14 block. */
22
27
  export declare function applyDataValidations(sheet: Worksheet, entries: readonly DataValidationEntry[]): void;
@@ -1,26 +1,30 @@
1
- import { isDataValidationErrorStyle, isDataValidationOperator, isDataValidationType, } from '../../core/data-validation.js';
2
- import { stripFormulaEquals } from '../../core/formula.js';
1
+ import { isDataValidationErrorStyle, isDataValidationImeMode, isDataValidationOperator, isDataValidationType, } from '../../core/data-validation.js';
2
+ import { mangleFormula, stripFormulaEquals, unmangleFunctions } from '../../core/formula.js';
3
3
  import { decodeSqrefRects } from '../../core/merge.js';
4
- import { coerceNumericLiteral } from '../../xml/xml-attrs.js';
4
+ import { coerceNumericLiteral, enumToken } from '../../xml/xml-attrs.js';
5
5
  import { TextCapture } from '../../xml/xml-read.js';
6
6
  import { boolStrict, localName } from '../../xml/xml-scan.js';
7
7
  import { checkedToken, escapeAttr, escapeText, textAttr } from '../../xml/xml.js';
8
+ import { admitting } from '../read-policy/read-repair.js';
8
9
  import { DATA_VALIDATION_EXT_URI, isExtensionElement, isMainNamespaceElement, XM_NS, } from './namespaces.js';
9
- import { admitting } from './read-repair.js';
10
10
  import { x14Ext } from './x14-ext.js';
11
11
  const TYPED = new Set(['whole', 'decimal', 'date', 'time', 'textLength']);
12
- export function dataValidationsXml(entries) {
12
+ export function dataValidationsXml(entries, formulaNames) {
13
13
  const standard = entries.filter((entry) => !entry.extended);
14
14
  if (standard.length === 0)
15
15
  return '';
16
- const items = standard.map(({ sqref, rule }) => dataValidationXml(sqref, rule)).join('');
16
+ const items = standard
17
+ .map(({ sqref, rule }) => dataValidationXml(sqref, rule, formulaNames))
18
+ .join('');
17
19
  return `<dataValidations count="${standard.length}">${items}</dataValidations>`;
18
20
  }
19
- export function dataValidationsExtXml(entries) {
21
+ export function dataValidationsExtXml(entries, formulaNames) {
20
22
  const extended = entries.filter((entry) => entry.extended);
21
23
  if (extended.length === 0)
22
24
  return '';
23
- const items = extended.map(({ sqref, rule }) => extendedDataValidationXml(sqref, rule)).join('');
25
+ const items = extended
26
+ .map(({ sqref, rule }) => extendedDataValidationXml(sqref, rule, formulaNames))
27
+ .join('');
24
28
  return x14Ext(DATA_VALIDATION_EXT_URI, `<x14:dataValidations count="${extended.length}" xmlns:xm="${XM_NS}">${items}</x14:dataValidations>`);
25
29
  }
26
30
  function ruleAttrs(rule) {
@@ -28,10 +32,14 @@ function ruleAttrs(rule) {
28
32
  (rule.errorStyle === undefined
29
33
  ? ''
30
34
  : ` errorStyle="${checkedToken(rule.errorStyle, isDataValidationErrorStyle, 'data validation error style')}"`) +
35
+ (rule.imeMode === undefined
36
+ ? ''
37
+ : ` imeMode="${checkedToken(rule.imeMode, isDataValidationImeMode, 'data validation IME mode')}"`) +
31
38
  (rule.operator === undefined
32
39
  ? ''
33
40
  : ` operator="${checkedToken(rule.operator, isDataValidationOperator, 'data validation operator')}"`) +
34
41
  (rule.allowBlank ? ' allowBlank="1"' : '') +
42
+ (rule.suppressDropDown ? ' showDropDown="1"' : '') +
35
43
  (rule.showInputMessage ? ' showInputMessage="1"' : '') +
36
44
  (rule.showErrorMessage ? ' showErrorMessage="1"' : '') +
37
45
  textAttr('errorTitle', rule.errorTitle) +
@@ -39,30 +47,28 @@ function ruleAttrs(rule) {
39
47
  textAttr('promptTitle', rule.promptTitle) +
40
48
  textAttr('prompt', rule.prompt));
41
49
  }
42
- function dataValidationXml(sqref, rule) {
43
- const [f1, f2] = operands(rule);
44
- const body = (f1 !== undefined ? `<formula1>${escapeText(stripFormulaEquals(f1))}</formula1>` : '') +
45
- (f2 !== undefined ? `<formula2>${escapeText(stripFormulaEquals(f2))}</formula2>` : '');
50
+ function dataValidationXml(sqref, rule, formulaNames) {
51
+ const [f1, f2] = rule.formulae ?? [];
52
+ const body = (f1 !== undefined ? `<formula1>${operandText(f1, formulaNames)}</formula1>` : '') +
53
+ (f2 !== undefined ? `<formula2>${operandText(f2, formulaNames)}</formula2>` : '');
46
54
  return `<dataValidation${ruleAttrs(rule)} sqref="${escapeAttr(sqref)}">${body}</dataValidation>`;
47
55
  }
48
- function operands(rule) {
49
- const drop = (v) => typeof v === 'number' && !Number.isFinite(v) ? undefined : v;
56
+ function extendedDataValidationXml(sqref, rule, formulaNames) {
50
57
  const [f1, f2] = rule.formulae ?? [];
51
- return [drop(f1), drop(f2)];
52
- }
53
- function extendedDataValidationXml(sqref, rule) {
54
- const [f1, f2] = operands(rule);
55
58
  const body = (f1 !== undefined
56
- ? `<x14:formula1><xm:f>${escapeText(stripFormulaEquals(f1))}</xm:f></x14:formula1>`
59
+ ? `<x14:formula1><xm:f>${operandText(f1, formulaNames)}</xm:f></x14:formula1>`
57
60
  : '') +
58
61
  (f2 !== undefined
59
- ? `<x14:formula2><xm:f>${escapeText(stripFormulaEquals(f2))}</xm:f></x14:formula2>`
62
+ ? `<x14:formula2><xm:f>${operandText(f2, formulaNames)}</xm:f></x14:formula2>`
60
63
  : '') +
61
64
  `<xm:sqref>${escapeText(sqref)}</xm:sqref>`;
62
65
  return `<x14:dataValidation${ruleAttrs(rule)}>${body}</x14:dataValidation>`;
63
66
  }
67
+ function operandText(operand, formulaNames) {
68
+ return escapeText(mangleFormula(stripFormulaEquals(operand), formulaNames));
69
+ }
64
70
  const FORMULA_ELEMENTS = new Set(['formula1', 'formula2']);
65
- export function dataValidationPass() {
71
+ export function dataValidationPass(definedNames) {
66
72
  const entries = [];
67
73
  let current;
68
74
  const formula = new TextCapture(FORMULA_ELEMENTS);
@@ -88,7 +94,7 @@ export function dataValidationPass() {
88
94
  return;
89
95
  }
90
96
  if (ln === 'dataValidation' && current !== undefined) {
91
- const built = buildEntry(current.attrs, current.formulae);
97
+ const built = buildEntry(current.attrs, current.formulae, definedNames);
92
98
  if (built !== undefined)
93
99
  entries.push(built);
94
100
  current = undefined;
@@ -97,14 +103,14 @@ export function dataValidationPass() {
97
103
  };
98
104
  return { handlers, result: () => entries };
99
105
  }
100
- function buildEntry(attrs, formulae) {
106
+ function buildEntry(attrs, formulae, definedNames) {
101
107
  const { sqref } = attrs;
102
108
  if (sqref === undefined)
103
109
  return undefined;
104
- const rule = buildRule(attrs, formulae);
110
+ const rule = buildRule(attrs, formulae, definedNames);
105
111
  return rule === undefined ? undefined : { sqref, rule };
106
112
  }
107
- function buildRule(attrs, formulae) {
113
+ function buildRule(attrs, formulae, definedNames) {
108
114
  const { type } = attrs;
109
115
  if (type === undefined || !isDataValidationType(type))
110
116
  return undefined;
@@ -118,12 +124,17 @@ function buildRule(attrs, formulae) {
118
124
  }
119
125
  if (boolStrict(attrs.allowBlank))
120
126
  rule.allowBlank = true;
127
+ if (boolStrict(attrs.showDropDown))
128
+ rule.suppressDropDown = true;
121
129
  if (boolStrict(attrs.showInputMessage))
122
130
  rule.showInputMessage = true;
123
131
  if (boolStrict(attrs.showErrorMessage))
124
132
  rule.showErrorMessage = true;
125
133
  if (attrs.errorStyle !== undefined && isDataValidationErrorStyle(attrs.errorStyle))
126
134
  rule.errorStyle = attrs.errorStyle;
135
+ const imeMode = enumToken(attrs.imeMode, isDataValidationImeMode);
136
+ if (imeMode !== undefined)
137
+ rule.imeMode = imeMode;
127
138
  if (attrs.error !== undefined)
128
139
  rule.error = attrs.error;
129
140
  if (attrs.errorTitle !== undefined)
@@ -134,12 +145,13 @@ function buildRule(attrs, formulae) {
134
145
  rule.promptTitle = attrs.promptTitle;
135
146
  const parsed = formulae
136
147
  .filter((f) => f !== undefined)
148
+ .map((f) => unmangleFunctions(f, definedNames))
137
149
  .map((f) => (type === 'list' || type === 'custom' ? f : coerceNumericLiteral(f)));
138
150
  if (parsed.length > 0)
139
151
  rule.formulae = parsed;
140
152
  return rule;
141
153
  }
142
- export function extendedDataValidationPass() {
154
+ export function extendedDataValidationPass(definedNames) {
143
155
  const entries = [];
144
156
  let current;
145
157
  let slot;
@@ -181,7 +193,7 @@ export function extendedDataValidationPass() {
181
193
  slot = undefined;
182
194
  }
183
195
  else if (ln === 'dataValidation' && extension && current !== undefined) {
184
- const built = buildExtendedEntry(current.attrs, current.formulae, current.sqref);
196
+ const built = buildExtendedEntry(current.attrs, current.formulae, current.sqref, definedNames);
185
197
  if (built !== undefined)
186
198
  entries.push(built);
187
199
  current = undefined;
@@ -190,10 +202,10 @@ export function extendedDataValidationPass() {
190
202
  };
191
203
  return { handlers, result: () => entries };
192
204
  }
193
- function buildExtendedEntry(attrs, formulae, sqref) {
205
+ function buildExtendedEntry(attrs, formulae, sqref, definedNames) {
194
206
  if (sqref === '')
195
207
  return undefined;
196
- const rule = buildRule(attrs, formulae);
208
+ const rule = buildRule(attrs, formulae, definedNames);
197
209
  return rule === undefined ? undefined : { sqref, rule, extended: true };
198
210
  }
199
211
  export function applyDataValidations(sheet, entries) {
@@ -1,8 +1,9 @@
1
- import { strFromU8, strToU8, zipSync } from 'fflate';
1
+ import { strToU8, zipSync } from 'fflate';
2
2
  import { VbaAuthorError } from '../../vba/errors.js';
3
3
  import { addVbaReference, removeVbaModule, } from '../../vba/project-editor.js';
4
+ import { elementRange } from '../../xml/xml-read.js';
4
5
  import { relsPathFor, resolveRelativePart } from '../opc/part-paths.js';
5
- import { parseRelationshipRecords, readPartRelationships } from '../opc/read-opc.js';
6
+ import { packageAccessors, parseRelationshipRecords, readPartRelationships, } from '../opc/read-opc.js';
6
7
  import { DEFAULT_MAX_UNCOMPRESSED } from '../opc/read-options.js';
7
8
  import { inflateSpreadsheetPackage } from '../opc/sniff-format.js';
8
9
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
@@ -17,56 +18,62 @@ export function editXlsxVbaAddReference(xlsx, ref, options = {}) {
17
18
  }
18
19
  function applyToVbaProjectPart(xlsx, options, apply) {
19
20
  const files = inflateSpreadsheetPackage(xlsx, options.maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED);
20
- const binPath = locateVbaProjectPart(files);
21
- const bin = binPath === undefined ? undefined : files[binPath];
22
- if (binPath === undefined || bin === undefined) {
21
+ const pkg = packageAccessors(files);
22
+ const binPath = locateVbaProjectPart(pkg);
23
+ const binKey = binPath === undefined ? undefined : pkg.partKey(binPath);
24
+ const bin = binKey === undefined ? undefined : files[binKey];
25
+ if (binPath === undefined || binKey === undefined || bin === undefined) {
23
26
  throw new VbaAuthorError('package has no VBA project to edit');
24
27
  }
25
- files[binPath] = apply(bin);
26
- dropStaleSignature(files, binPath);
28
+ files[binKey] = apply(bin);
29
+ dropStaleSignature(files, pkg, binPath);
27
30
  return zipSync(files, { mtime: FIXED_ENTRY_MTIME });
28
31
  }
29
- function locateVbaProjectPart(files) {
30
- const partText = (path) => textPart(files, path);
31
- const workbookPath = readPartRelationships('', partText).targetPath(OFFICE_DOCUMENT_REL);
32
+ function locateVbaProjectPart(pkg) {
33
+ const workbookPath = readPartRelationships('', pkg.partText).targetPath(OFFICE_DOCUMENT_REL);
32
34
  if (workbookPath === undefined)
33
35
  return undefined;
34
- return readPartRelationships(workbookPath, partText).targetPath(VBA_PROJECT_REL);
36
+ return readPartRelationships(workbookPath, pkg.partText).targetPath(VBA_PROJECT_REL);
35
37
  }
36
- function dropStaleSignature(files, binPath) {
37
- const binRelsPath = relsPathFor(binPath);
38
- const binRels = textPart(files, binRelsPath);
39
- if (binRels === undefined)
38
+ function dropStaleSignature(files, pkg, binPath) {
39
+ const binRelsKey = pkg.partKey(relsPathFor(binPath));
40
+ const binRels = binRelsKey === undefined ? undefined : pkg.partText(binRelsKey);
41
+ if (binRelsKey === undefined || binRels === undefined)
40
42
  return;
41
43
  const signatureRels = parseRelationshipRecords(binRels).filter((rel) => !rel.external && rel.type.includes(VBA_SIGNATURE_REL_INFIX));
42
44
  if (signatureRels.length === 0)
43
45
  return;
44
- let contentTypes = textPart(files, '[Content_Types].xml');
46
+ const contentTypesKey = pkg.partKey(CONTENT_TYPES_PART);
47
+ let contentTypes = contentTypesKey === undefined ? undefined : pkg.partText(contentTypesKey);
45
48
  let rels = binRels;
46
49
  for (const rel of signatureRels) {
47
50
  const partPath = resolveRelativePart(binPath, rel.target);
48
- delete files[partPath];
49
- rels = removeRelationshipById(rels, rel.id);
50
- if (contentTypes !== undefined)
51
- contentTypes = removeContentTypeOverride(contentTypes, partPath);
51
+ const partKey = pkg.partKey(partPath);
52
+ if (partKey !== undefined)
53
+ delete files[partKey];
54
+ const partName = `/${partPath}`.toLowerCase();
55
+ rels = removeElements(rels, 'Relationship', (attrs) => attrs.Id === rel.id);
56
+ if (contentTypes !== undefined) {
57
+ contentTypes = removeElements(contentTypes, 'Override', (attrs) => attrs.PartName?.toLowerCase() === partName);
58
+ }
52
59
  }
53
60
  if (parseRelationshipRecords(rels).length === 0)
54
- delete files[binRelsPath];
61
+ delete files[binRelsKey];
55
62
  else
56
- files[binRelsPath] = strToU8(rels);
57
- if (contentTypes !== undefined)
58
- files['[Content_Types].xml'] = strToU8(contentTypes);
59
- }
60
- function textPart(files, path) {
61
- const bytes = files[path];
62
- return bytes === undefined ? undefined : strFromU8(bytes);
63
- }
64
- function removeRelationshipById(xml, id) {
65
- return xml.replace(/<Relationship\b[^>]*?\/>|<Relationship\b[\s\S]*?<\/Relationship>/g, (element) => (new RegExp(`\\bId="${escapeRegExp(id)}"`).test(element) ? '' : element));
66
- }
67
- function removeContentTypeOverride(xml, partPath) {
68
- return xml.replace(/<Override\b[^>]*?\/>/g, (element) => element.includes(`PartName="/${partPath}"`) ? '' : element);
63
+ files[binRelsKey] = strToU8(rels);
64
+ if (contentTypesKey !== undefined && contentTypes !== undefined) {
65
+ files[contentTypesKey] = strToU8(contentTypes);
66
+ }
69
67
  }
70
- function escapeRegExp(value) {
71
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
68
+ const CONTENT_TYPES_PART = '[Content_Types].xml';
69
+ function removeElements(xml, local, drop) {
70
+ let kept = '';
71
+ let rest = xml;
72
+ let range = elementRange(rest, [local]);
73
+ while (range !== undefined) {
74
+ kept += rest.slice(0, drop(range.attrs) ? range.start : range.end);
75
+ rest = rest.slice(range.end);
76
+ range = elementRange(rest, [local]);
77
+ }
78
+ return kept + rest;
72
79
  }
@@ -1,61 +1,26 @@
1
- import type { Cell } from '../../core/cell.ts';
1
+ import type { Hyperlink } from '../../core/hyperlink.ts';
2
2
  import type { Worksheet } from '../../core/worksheet.ts';
3
3
  import { type CollectingPass } from '../../xml/xml-read.ts';
4
- import type { RelIdAllocator } from './package-plan.ts';
5
- /** A hyperlink gathered from a sheet for serialisation: the cell it sits on, its target, and an
6
- * optional tooltip. The visible label is the cell's own value and is serialised as that value.
7
- *
8
- * `row`/`col` are the anchor's position, kept so links gathered from separate passes (the sheet's live
9
- * rows, and the rows the streaming writer already flushed and evicted) can be merged back into the
10
- * row-major order Excel writes them in. They are not serialised; `ref` is.
11
- */
12
- export interface CollectedHyperlink {
13
- readonly ref: string;
14
- readonly row: number;
15
- readonly col: number;
16
- readonly target: string;
17
- readonly tooltip?: string;
18
- }
4
+ import type { RelationshipLedger } from './package-plan.ts';
19
5
  /** A hyperlink resolved for serialisation. An external target carries a `relId` (the sheet
20
- * relationship holding the URL) plus that `target`; an internal target carries a `location` (the
21
- * in-workbook reference). Exactly one of `relId`/`location` is ever set. */
6
+ * relationship holding the URL); an internal target carries a `location` (the in-workbook
7
+ * reference). Exactly one of `relId`/`location` is ever set. */
22
8
  export interface HyperlinkPlan {
23
9
  readonly ref: string;
24
10
  readonly relId?: string;
25
- readonly target?: string;
26
11
  readonly location?: string;
27
12
  readonly tooltip?: string;
28
13
  }
29
14
  /**
30
- * Gather every hyperlink among a run of cells.
15
+ * Split a sheet's links into internal (location, no rel) and external (relationship) forms, recording
16
+ * each external link's URL in the sheet's ledger so its relationship follows every other sheet-local
17
+ * relationship in canonical order. An internal (`#`-prefixed) link records nothing.
31
18
  *
32
- * Takes the cells rather than the sheet, because the streaming writer has to ask this question of a
33
- * row at the moment it commits: after that the row's cells are evicted, and a post-hoc walk of
34
- * `sheet.rows()` finds nothing. Both writers therefore ask the same function, over whatever cells
35
- * they still hold.
19
+ * The links keep the order the sheet holds them in. That order is what decides which of two links over
20
+ * one cell `Worksheet.hyperlinkAt` reports, and a re-read adds them in document order, so writing them
21
+ * any other way would change the answer across a save.
36
22
  */
37
- export declare function collectHyperlinks(cells: Iterable<Cell>): CollectedHyperlink[];
38
- /** Every cell a sheet still holds, row-major: the live half of what a writer must gather. */
39
- export declare function liveCells(sheet: Worksheet): Generator<Cell, void, undefined>;
40
- /** An external hyperlink: the two fields a `TargetMode="External"` relationship needs, both present. */
41
- export type ExternalHyperlinkPlan = HyperlinkPlan & {
42
- readonly relId: string;
43
- readonly target: string;
44
- };
45
- /**
46
- * Whether a planned hyperlink is the external kind, narrowing it so the relationship writer reads
47
- * `relId` and `target` as the strings they are.
48
- *
49
- * A predicate rather than a filter plus two casts: the filter already proved both fields present, and
50
- * a cast repeating that proof one line later is a claim the compiler cannot check against the filter
51
- * it is supposed to be echoing. `planHyperlinks` sets exactly one of `relId`/`location`, so testing
52
- * either field is testing the kind.
53
- */
54
- export declare function isExternalHyperlink(link: HyperlinkPlan): link is ExternalHyperlinkPlan;
55
- /** Split collected links into internal (location, no rel) and external (relationship) forms, drawing
56
- * each external link's relationship id from the sheet's allocator so external ids follow every other
57
- * sheet-local relationship in canonical order. An internal ('#'-prefixed) link consumes no id. */
58
- export declare function planHyperlinks(links: readonly CollectedHyperlink[], rels: RelIdAllocator): HyperlinkPlan[];
23
+ export declare function planHyperlinks(links: readonly Hyperlink[], rels: RelationshipLedger): HyperlinkPlan[];
59
24
  /** The `<hyperlinks>` element, or '' when the sheet has none. Attribute order follows CT_Hyperlink:
60
25
  * `ref`, `r:id`, `location`, `tooltip`. */
61
26
  export declare function hyperlinksXml(links: readonly HyperlinkPlan[]): string;
@@ -70,9 +35,10 @@ interface ParsedHyperlink {
70
35
  /** A pass gathering every `<hyperlink>` element of a worksheet part, for a caller reading the part
71
36
  * alongside its other readers in one parse. */
72
37
  export declare function sheetHyperlinkPass(): CollectingPass<ParsedHyperlink[]>;
73
- /** Fold parsed hyperlinks onto a sheet's cells, wrapping each cell's existing value (its visible
74
- * label) into a {@link HyperlinkValue}. `targetOf` resolves a relationship id to its raw Target: a
75
- * URL for the external links hyperlinks almost always are, so it must stay unresolved against the
76
- * package rather than being handed over as a part path. */
38
+ /**
39
+ * Put parsed hyperlinks on a sheet, in document order, whatever the cells they cover hold. `targetOf`
40
+ * resolves a relationship id to its raw Target: a URL for the external links hyperlinks almost always
41
+ * are, so it must stay unresolved against the package rather than being handed over as a part path.
42
+ */
77
43
  export declare function applyHyperlinks(sheet: Worksheet, links: readonly ParsedHyperlink[], targetOf: (relId: string) => string | undefined): void;
78
44
  export {};
@@ -1,39 +1,16 @@
1
- import { tryDecodeRange } from '../../core/address.js';
2
- import { isHyperlinkValue, isRichTextValue } from '../../core/value.js';
1
+ import { boundedRect, tryDecodeRange } from '../../core/address.js';
3
2
  import {} from '../../xml/xml-read.js';
4
3
  import { localName } from '../../xml/xml-scan.js';
5
4
  import { escapeAttr, textAttr } from '../../xml/xml.js';
6
5
  import { relAttr } from '../opc/namespaces.js';
7
- export function collectHyperlinks(cells) {
8
- const links = [];
9
- for (const cell of cells) {
10
- const value = cell.value;
11
- if (isHyperlinkValue(value)) {
12
- links.push({
13
- ref: value.range ?? cell.address,
14
- row: cell.row,
15
- col: cell.col,
16
- target: value.hyperlink,
17
- ...(value.tooltip !== undefined ? { tooltip: value.tooltip } : {}),
18
- });
19
- }
20
- }
21
- return links;
22
- }
23
- export function* liveCells(sheet) {
24
- for (const { cells } of sheet.rows())
25
- yield* cells;
26
- }
27
- export function isExternalHyperlink(link) {
28
- return link.relId !== undefined && link.target !== undefined;
29
- }
6
+ import { REL } from './relationships.js';
30
7
  export function planHyperlinks(links, rels) {
31
8
  return links.map((link) => {
32
9
  const tooltip = link.tooltip !== undefined ? { tooltip: link.tooltip } : {};
33
10
  if (link.target.startsWith('#')) {
34
11
  return { ref: link.ref, location: link.target.slice(1), ...tooltip };
35
12
  }
36
- return { ref: link.ref, relId: rels.next(), target: link.target, ...tooltip };
13
+ return { ref: link.ref, relId: rels.addExternal(REL.hyperlink, link.target), ...tooltip };
37
14
  });
38
15
  }
39
16
  export function hyperlinksXml(links) {
@@ -77,19 +54,13 @@ export function applyHyperlinks(sheet, links, targetOf) {
77
54
  if (target === undefined)
78
55
  continue;
79
56
  const decoded = tryDecodeRange(link.ref);
80
- if (decoded === undefined || decoded.tl.col === undefined || decoded.tl.row === undefined)
57
+ if (decoded === undefined || boundedRect(decoded) === undefined)
81
58
  continue;
82
- const cell = sheet.getCell(decoded.tl.address);
83
- const cellValue = cell.value;
84
- const text = typeof cellValue === 'string' ? cellValue : isRichTextValue(cellValue) ? cellValue : '';
85
- const spansRange = decoded.tl.address !== decoded.br.address;
86
- const value = {
87
- hyperlink: target,
88
- text,
59
+ sheet.addHyperlink({
60
+ ref: link.ref,
61
+ target,
89
62
  ...(link.tooltip !== undefined ? { tooltip: link.tooltip } : {}),
90
- ...(spansRange ? { range: link.ref } : {}),
91
- };
92
- cell.value = value;
63
+ });
93
64
  }
94
65
  }
95
66
  function resolveTarget(link, targetOf) {
@@ -1,20 +1,32 @@
1
- import { type AnchorPoint, type Extent, type ImageAnchor, type ImageEditAs } from '../../core/image.ts';
1
+ import { type AnchorPoint, type Extent, type ImageAnchor, type ImageCrop, type ImageEditAs, type PictureProperties } from '../../core/image.ts';
2
2
  /** The content type for a media part's `<Default Extension>` entry in `[Content_Types].xml`. */
3
3
  export declare function imageContentType(extension: string): string;
4
- /** One image placed in a drawing: where it sits and the drawing-local relationship id that ties it
5
- * to its media bytes. */
4
+ /** One image placed in a drawing: where it sits, the drawing-local relationship id that ties it to its
5
+ * media bytes, and the picture's own properties. */
6
6
  export interface DrawingImage {
7
7
  readonly anchor: ImageAnchor;
8
8
  /** The `r:embed` id referencing this image's entry in the drawing's own `.rels`. */
9
9
  readonly embedId: string;
10
+ readonly properties: PictureProperties;
11
+ /** The `r:id` of the hyperlink relationship in the drawing's own `.rels`, when the picture is a link. */
12
+ readonly hyperlinkId?: string;
10
13
  }
11
14
  /** The `xl/drawings/drawing{n}.xml` part: one anchor per image, two-cell or one-cell by its shape. */
12
15
  export declare function drawingXml(images: readonly DrawingImage[]): string;
13
- /** The drawing's `_rels/drawing{n}.xml.rels`: one image relationship per anchor, in `embedId` order
14
- * (`rId1`, `rId2`, …), each pointing at the media part the anchor shows. */
15
- export declare function drawingRelsXml(mediaTargets: readonly string[]): string;
16
+ /**
17
+ * A drawing kept from a file with pictures added to it: each picture's anchor goes after the drawing's
18
+ * own content, so it draws on top, as a picture inserted last does in Excel.
19
+ *
20
+ * A drawing numbers its shapes by `cNvPr id`, unique within it, so the pictures take ids past the
21
+ * highest one there. Their `r:embed` and link ids are the caller's, allocated past the relationships
22
+ * the drawing already holds.
23
+ *
24
+ * @throws {AuthoringError} if the kept part has no `<wsDr>` root to add a picture to.
25
+ */
26
+ export declare function mergePictures(drawing: string, images: readonly DrawingImage[]): string;
16
27
  /** An image anchor parsed from a drawing part, with the `r:embed` id that names its media. A two-cell
17
- * anchor carries `to` (and may carry `editAs`); a one-cell anchor carries `ext` instead. */
28
+ * anchor carries `to` and its `editAs`, the schema default filled in; a one-cell anchor carries `ext`
29
+ * instead. */
18
30
  export interface ParsedImageAnchor {
19
31
  readonly from: AnchorPoint;
20
32
  readonly to?: AnchorPoint;
@@ -22,13 +34,34 @@ export interface ParsedImageAnchor {
22
34
  readonly editAs?: ImageEditAs;
23
35
  readonly rotation?: number;
24
36
  readonly embed: string;
37
+ readonly description?: string;
38
+ readonly title?: string;
39
+ readonly crop?: ImageCrop;
40
+ /** The `r:id` of the picture's `a:hlinkClick`, for the caller to resolve against the drawing's rels. */
41
+ readonly hyperlinkId?: string;
42
+ readonly tooltip?: string;
25
43
  }
26
- /** Parse a drawing part into its image anchors (both `<xdr:twoCellAnchor>` and `<xdr:oneCellAnchor>`).
27
- * Anchors that are not pictures (a chart, a shape) carry no `<a:blip r:embed>` and are skipped, so a
28
- * mixed drawing yields only its images. */
29
- export declare function parseDrawing(xml: string): ParsedImageAnchor[];
30
- /** Whether a drawing part holds anchor content beyond plain pictures: a chart, shape, connector, or
31
- * group. Excel packs every one of a sheet's anchors into a single drawing part, so a sheet with both a
32
- * picture and a chart yields a mixed drawing; modeling only its pictures and re-serialising from them
33
- * would drop the chart. The reader uses this to fall back to whole-drawing byte-preservation instead. */
34
- export declare function drawingHasUnmodeledContent(xml: string): boolean;
44
+ /** A drawing part as the reader sees it: the picture anchors it could model, and whether those anchors
45
+ * are the whole of it. */
46
+ export interface ParsedDrawing {
47
+ readonly anchors: readonly ParsedImageAnchor[];
48
+ /**
49
+ * Whether every anchor is a two-cell or one-cell anchor holding exactly one embedded picture. When it
50
+ * is not, writing the drawing back from `anchors` would leave the rest out, so the reader keeps the
51
+ * whole part byte for byte instead.
52
+ */
53
+ readonly fullyModeled: boolean;
54
+ }
55
+ /**
56
+ * Parse a drawing part into its picture anchors (`<xdr:twoCellAnchor>` and `<xdr:oneCellAnchor>`), and
57
+ * decide in the same scan whether they are the whole drawing. It is fully modeled only when every
58
+ * anchor holds exactly one picture whose bytes are embedded; anything else in it, including a picture
59
+ * that links its image rather than embedding it, has no place in the image model.
60
+ *
61
+ * This used to be two scans: one listing the content the model skips, and this one. The list missed
62
+ * an absolute anchor and a linked picture, so a drawing holding either counted as modeled and lost them
63
+ * on write. What an anchor holds is now judged by what the parse found in it, one embedded picture or
64
+ * not, so no kind of anchor content can slip past unnamed; the list left names only the anchor kinds
65
+ * and wrappers this parse does not otherwise open an anchor for.
66
+ */
67
+ export declare function parseDrawing(xml: string): ParsedDrawing;