@shbernal/ts-xlsx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +230 -0
  3. package/dist/core/address.d.ts +49 -0
  4. package/dist/core/address.js +125 -0
  5. package/dist/core/autofilter.d.ts +55 -0
  6. package/dist/core/autofilter.js +48 -0
  7. package/dist/core/cell.d.ts +132 -0
  8. package/dist/core/cell.js +249 -0
  9. package/dist/core/color-resolution.d.ts +58 -0
  10. package/dist/core/color-resolution.js +170 -0
  11. package/dist/core/column.d.ts +87 -0
  12. package/dist/core/column.js +167 -0
  13. package/dist/core/comment-thread.d.ts +116 -0
  14. package/dist/core/comment-thread.js +62 -0
  15. package/dist/core/conditional-formatting-overlay.d.ts +15 -0
  16. package/dist/core/conditional-formatting-overlay.js +24 -0
  17. package/dist/core/conditional-formatting.d.ts +70 -0
  18. package/dist/core/conditional-formatting.js +35 -0
  19. package/dist/core/containers.d.ts +2 -0
  20. package/dist/core/containers.js +25 -0
  21. package/dist/core/data-validation-overlay.d.ts +28 -0
  22. package/dist/core/data-validation-overlay.js +56 -0
  23. package/dist/core/data-validation.d.ts +38 -0
  24. package/dist/core/data-validation.js +13 -0
  25. package/dist/core/date.d.ts +25 -0
  26. package/dist/core/date.js +58 -0
  27. package/dist/core/formula.d.ts +49 -0
  28. package/dist/core/formula.js +319 -0
  29. package/dist/core/grid-edits.d.ts +22 -0
  30. package/dist/core/grid-edits.js +206 -0
  31. package/dist/core/image.d.ts +76 -0
  32. package/dist/core/image.js +65 -0
  33. package/dist/core/internal.d.ts +11 -0
  34. package/dist/core/internal.js +29 -0
  35. package/dist/core/merge.d.ts +13 -0
  36. package/dist/core/merge.js +26 -0
  37. package/dist/core/modern-functions.d.ts +1 -0
  38. package/dist/core/modern-functions.js +165 -0
  39. package/dist/core/page-setup.d.ts +94 -0
  40. package/dist/core/page-setup.js +6 -0
  41. package/dist/core/pivot-table.d.ts +113 -0
  42. package/dist/core/pivot-table.js +249 -0
  43. package/dist/core/preserved.d.ts +58 -0
  44. package/dist/core/preserved.js +5 -0
  45. package/dist/core/protection.d.ts +67 -0
  46. package/dist/core/protection.js +68 -0
  47. package/dist/core/range.d.ts +112 -0
  48. package/dist/core/range.js +283 -0
  49. package/dist/core/row.d.ts +73 -0
  50. package/dist/core/row.js +135 -0
  51. package/dist/core/style.d.ts +246 -0
  52. package/dist/core/style.js +125 -0
  53. package/dist/core/table-style.d.ts +67 -0
  54. package/dist/core/table-style.js +90 -0
  55. package/dist/core/table.d.ts +194 -0
  56. package/dist/core/table.js +291 -0
  57. package/dist/core/theme.d.ts +90 -0
  58. package/dist/core/theme.js +272 -0
  59. package/dist/core/value.d.ts +115 -0
  60. package/dist/core/value.js +144 -0
  61. package/dist/core/workbook-protection.d.ts +27 -0
  62. package/dist/core/workbook-protection.js +25 -0
  63. package/dist/core/workbook.d.ts +528 -0
  64. package/dist/core/workbook.js +732 -0
  65. package/dist/core/worksheet-model.d.ts +34 -0
  66. package/dist/core/worksheet-model.js +100 -0
  67. package/dist/core/worksheet.d.ts +647 -0
  68. package/dist/core/worksheet.js +1025 -0
  69. package/dist/customui/errors.d.ts +15 -0
  70. package/dist/customui/errors.js +15 -0
  71. package/dist/customui/index.d.ts +2 -0
  72. package/dist/customui/index.js +7 -0
  73. package/dist/customui/ribbon.d.ts +86 -0
  74. package/dist/customui/ribbon.js +180 -0
  75. package/dist/entries/core.d.ts +23 -0
  76. package/dist/entries/core.js +20 -0
  77. package/dist/entries/csv.d.ts +2 -0
  78. package/dist/entries/csv.js +6 -0
  79. package/dist/entries/customui.d.ts +1 -0
  80. package/dist/entries/customui.js +3 -0
  81. package/dist/entries/errors.d.ts +7 -0
  82. package/dist/entries/errors.js +20 -0
  83. package/dist/entries/vba.d.ts +2 -0
  84. package/dist/entries/vba.js +7 -0
  85. package/dist/entries/xlsb.d.ts +1 -0
  86. package/dist/entries/xlsb.js +7 -0
  87. package/dist/entries/xlsx.d.ts +5 -0
  88. package/dist/entries/xlsx.js +18 -0
  89. package/dist/errors.d.ts +65 -0
  90. package/dist/errors.js +66 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.js +19 -0
  93. package/dist/io/csv/read.d.ts +15 -0
  94. package/dist/io/csv/read.js +113 -0
  95. package/dist/io/csv/write.d.ts +28 -0
  96. package/dist/io/csv/write.js +124 -0
  97. package/dist/io/opc/errors.d.ts +46 -0
  98. package/dist/io/opc/errors.js +49 -0
  99. package/dist/io/opc/inflate.d.ts +15 -0
  100. package/dist/io/opc/inflate.js +91 -0
  101. package/dist/io/opc/namespaces.d.ts +18 -0
  102. package/dist/io/opc/namespaces.js +18 -0
  103. package/dist/io/opc/part-paths.d.ts +4 -0
  104. package/dist/io/opc/part-paths.js +35 -0
  105. package/dist/io/opc/read-opc.d.ts +24 -0
  106. package/dist/io/opc/read-opc.js +153 -0
  107. package/dist/io/opc/read-options.d.ts +10 -0
  108. package/dist/io/opc/read-options.js +7 -0
  109. package/dist/io/opc/rels.d.ts +15 -0
  110. package/dist/io/opc/rels.js +28 -0
  111. package/dist/io/opc/sniff-format.d.ts +27 -0
  112. package/dist/io/opc/sniff-format.js +92 -0
  113. package/dist/io/style/xf-style.d.ts +47 -0
  114. package/dist/io/style/xf-style.js +95 -0
  115. package/dist/io/xlsb/errors.d.ts +15 -0
  116. package/dist/io/xlsb/errors.js +15 -0
  117. package/dist/io/xlsb/formula.d.ts +48 -0
  118. package/dist/io/xlsb/formula.js +398 -0
  119. package/dist/io/xlsb/primitives.d.ts +86 -0
  120. package/dist/io/xlsb/primitives.js +230 -0
  121. package/dist/io/xlsb/ptg-functions.d.ts +16 -0
  122. package/dist/io/xlsb/ptg-functions.js +659 -0
  123. package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
  124. package/dist/io/xlsb/read-shared-strings.js +22 -0
  125. package/dist/io/xlsb/read-styles.d.ts +4 -0
  126. package/dist/io/xlsb/read-styles.js +386 -0
  127. package/dist/io/xlsb/read-worksheet.d.ts +9 -0
  128. package/dist/io/xlsb/read-worksheet.js +301 -0
  129. package/dist/io/xlsb/read.d.ts +20 -0
  130. package/dist/io/xlsb/read.js +199 -0
  131. package/dist/io/xlsb/record-stream.d.ts +14 -0
  132. package/dist/io/xlsb/record-stream.js +56 -0
  133. package/dist/io/xlsb/record-types.d.ts +55 -0
  134. package/dist/io/xlsb/record-types.js +73 -0
  135. package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
  136. package/dist/io/xlsx/cell-accumulator.js +172 -0
  137. package/dist/io/xlsx/cell-value.d.ts +32 -0
  138. package/dist/io/xlsx/cell-value.js +85 -0
  139. package/dist/io/xlsx/comments.d.ts +74 -0
  140. package/dist/io/xlsx/comments.js +243 -0
  141. package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
  142. package/dist/io/xlsx/conditional-formatting.js +464 -0
  143. package/dist/io/xlsx/data-validation.d.ts +20 -0
  144. package/dist/io/xlsx/data-validation.js +258 -0
  145. package/dist/io/xlsx/edit-vba.d.ts +23 -0
  146. package/dist/io/xlsx/edit-vba.js +139 -0
  147. package/dist/io/xlsx/errors.d.ts +17 -0
  148. package/dist/io/xlsx/errors.js +17 -0
  149. package/dist/io/xlsx/hyperlinks.d.ts +42 -0
  150. package/dist/io/xlsx/hyperlinks.js +139 -0
  151. package/dist/io/xlsx/images.d.ts +34 -0
  152. package/dist/io/xlsx/images.js +218 -0
  153. package/dist/io/xlsx/namespaces.d.ts +55 -0
  154. package/dist/io/xlsx/namespaces.js +55 -0
  155. package/dist/io/xlsx/package-plan.d.ts +103 -0
  156. package/dist/io/xlsx/package-plan.js +168 -0
  157. package/dist/io/xlsx/pivot-read.d.ts +5 -0
  158. package/dist/io/xlsx/pivot-read.js +134 -0
  159. package/dist/io/xlsx/pivot.d.ts +12 -0
  160. package/dist/io/xlsx/pivot.js +141 -0
  161. package/dist/io/xlsx/read-rows.d.ts +85 -0
  162. package/dist/io/xlsx/read-rows.js +286 -0
  163. package/dist/io/xlsx/read-styles.d.ts +8 -0
  164. package/dist/io/xlsx/read-styles.js +551 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +4 -0
  166. package/dist/io/xlsx/read-worksheet.js +528 -0
  167. package/dist/io/xlsx/read.d.ts +32 -0
  168. package/dist/io/xlsx/read.js +680 -0
  169. package/dist/io/xlsx/relationships.d.ts +33 -0
  170. package/dist/io/xlsx/relationships.js +47 -0
  171. package/dist/io/xlsx/rich-runs.d.ts +13 -0
  172. package/dist/io/xlsx/rich-runs.js +59 -0
  173. package/dist/io/xlsx/rich-text.d.ts +7 -0
  174. package/dist/io/xlsx/rich-text.js +23 -0
  175. package/dist/io/xlsx/shared-formulas.d.ts +6 -0
  176. package/dist/io/xlsx/shared-formulas.js +52 -0
  177. package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
  178. package/dist/io/xlsx/shared-strings-read.js +70 -0
  179. package/dist/io/xlsx/shared-strings.d.ts +17 -0
  180. package/dist/io/xlsx/shared-strings.js +50 -0
  181. package/dist/io/xlsx/sheet-properties.d.ts +13 -0
  182. package/dist/io/xlsx/sheet-properties.js +211 -0
  183. package/dist/io/xlsx/styles.d.ts +138 -0
  184. package/dist/io/xlsx/styles.js +821 -0
  185. package/dist/io/xlsx/tables.d.ts +9 -0
  186. package/dist/io/xlsx/tables.js +208 -0
  187. package/dist/io/xlsx/threaded-comments.d.ts +102 -0
  188. package/dist/io/xlsx/threaded-comments.js +308 -0
  189. package/dist/io/xlsx/workbook-xml.d.ts +12 -0
  190. package/dist/io/xlsx/workbook-xml.js +353 -0
  191. package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
  192. package/dist/io/xlsx/worksheet-xml.js +643 -0
  193. package/dist/io/xlsx/write-stream.d.ts +171 -0
  194. package/dist/io/xlsx/write-stream.js +399 -0
  195. package/dist/io/xlsx/write.d.ts +81 -0
  196. package/dist/io/xlsx/write.js +396 -0
  197. package/dist/io/xlsx/x14-ext.d.ts +9 -0
  198. package/dist/io/xlsx/x14-ext.js +12 -0
  199. package/dist/vba/cfb-writer.d.ts +19 -0
  200. package/dist/vba/cfb-writer.js +277 -0
  201. package/dist/vba/cfb.d.ts +16 -0
  202. package/dist/vba/cfb.js +324 -0
  203. package/dist/vba/codepage.d.ts +18 -0
  204. package/dist/vba/codepage.js +87 -0
  205. package/dist/vba/errors.d.ts +24 -0
  206. package/dist/vba/errors.js +24 -0
  207. package/dist/vba/index.d.ts +5 -0
  208. package/dist/vba/index.js +15 -0
  209. package/dist/vba/ms-ovba.d.ts +12 -0
  210. package/dist/vba/ms-ovba.js +176 -0
  211. package/dist/vba/project-editor.d.ts +62 -0
  212. package/dist/vba/project-editor.js +377 -0
  213. package/dist/vba/project.d.ts +45 -0
  214. package/dist/vba/project.js +160 -0
  215. package/dist/vba/vba-encoding.d.ts +14 -0
  216. package/dist/vba/vba-encoding.js +39 -0
  217. package/dist/xml/errors.d.ts +15 -0
  218. package/dist/xml/errors.js +15 -0
  219. package/dist/xml/xml-read.d.ts +107 -0
  220. package/dist/xml/xml-read.js +283 -0
  221. package/dist/xml/xml.d.ts +41 -0
  222. package/dist/xml/xml.js +86 -0
  223. package/package.json +117 -0
@@ -0,0 +1,464 @@
1
+ // Serialising conditional formatting to the classic `<conditionalFormatting>` worksheet element.
2
+ //
3
+ // Each block names its target range(s) in a `sqref` attribute and holds one or more `<cfRule>`
4
+ // children. A rule's shape depends on its type: a `dataBar`/`colorScale`/`iconSet` carries a scale
5
+ // element (its `<cfvo>` anchors and colours), while a `cellIs`/`expression`/`top10`/… carries its
6
+ // operands as `<formula>` children and points at a differential style by `dxfId`. A rule the library
7
+ // does not model in depth still round-trips its attributes, so nothing is silently dropped on save.
8
+ //
9
+ // A data bar's richer facets — its gradient fill, its negative-value fill colour, its axis colour —
10
+ // have no home in the classic `<dataBar>` element; Excel stores them only in the 2009 x14 extension.
11
+ // So a data-bar rule carrying any of them is written twice: the classic element (its anchors and bar
12
+ // colour, understood by every consumer) plus an `<x14:dataBar>` in the worksheet `<extLst>` carrying
13
+ // the extras, the two linked by a shared id. The reader folds the extension back onto the classic
14
+ // rule, so the gradient flag and the two extra colours survive a round-trip rather than being dropped.
15
+ import { boolAttr, escapeAttr, escapeText, stripFormulaEquals } from "../../xml/xml.js";
16
+ import { boolStrict, coerceNumericLiteral, localName, parseXml } from "../../xml/xml-read.js";
17
+ // The x14/xm extension namespaces and ext-URI GUIDs are declared inline on the `<ext>` elements
18
+ // exactly as Excel writes them, so no worksheet-root xmlns is needed. `CF_EXT_URI` scopes the
19
+ // worksheet's x14 conditional formattings; `DATABAR_LINK_EXT_URI` scopes the `<x14:id>` link a
20
+ // classic cfRule carries to name its extension.
21
+ import { CF_EXT_URI, DATABAR_LINK_EXT_URI, XM_NS } from "./namespaces.js";
22
+ import { colorAttrs, parseColor } from "./styles.js";
23
+ import { x14Ext } from "./x14-ext.js";
24
+ // Excel's default data bar when the author supplies none: a min/max anchor pair and its standard blue.
25
+ const DEFAULT_DATABAR_CFVO = [{ type: 'min' }, { type: 'max' }];
26
+ const DEFAULT_DATABAR_COLOR = { argb: 'FF638EC6' };
27
+ // A data bar needs the x14 extension only when it carries a facet the classic element cannot express.
28
+ // A plain data bar (anchors and bar colour alone) stays classic-only, so an unadorned rule never
29
+ // fabricates an empty extension block.
30
+ function needsDataBarExt(rule) {
31
+ return (rule.gradient !== undefined ||
32
+ rule.negativeFillColor !== undefined ||
33
+ rule.axisColor !== undefined);
34
+ }
35
+ // The synthetic id linking a classic cfRule to its x14 extension. Excel uses a random GUID; any unique
36
+ // token that matches on both ends works, so a deterministic per-sheet index keeps the output stable
37
+ // and testable.
38
+ function dataBarExtGuid(index) {
39
+ return `{00000000-0000-0000-0000-${String(index + 1).padStart(12, '0')}}`;
40
+ }
41
+ // Assign every extended data bar its link id once, keyed by rule identity. The classic pass (which
42
+ // emits the `<extLst>` link on the cfRule) and the extension pass (which emits the `<x14:cfRule id>`)
43
+ // both read the id from this map, so the two ends of a link agree by construction — not by the two
44
+ // passes happening to walk the rules in the same order. A rule absent from the map needs no extension.
45
+ function dataBarExtLinks(formattings) {
46
+ const links = new Map();
47
+ let index = 0;
48
+ for (const cf of formattings) {
49
+ for (const rule of cf.rules) {
50
+ if (rule.type === 'dataBar' && needsDataBarExt(rule))
51
+ links.set(rule, dataBarExtGuid(index++));
52
+ }
53
+ }
54
+ return links;
55
+ }
56
+ // The three built-in visual rules. Each renders a built-in visual and carries no differential
57
+ // style (so the write side skips the dxf), and each nests its `<color>` children differently (so
58
+ // the read side, {@link ScaleKind}, tracks which one it is inside). This tuple drives both.
59
+ const SCALE_KINDS = ['dataBar', 'colorScale', 'iconSet'];
60
+ const SCALE_TYPES = new Set(SCALE_KINDS);
61
+ /**
62
+ * Serialise every conditional formatting on a sheet into its `<conditionalFormatting>` blocks, in
63
+ * insertion order. Returns '' when the sheet has none. A rule missing a `priority` is assigned the
64
+ * next free one, so the output always satisfies Excel's requirement that every cfRule carry one.
65
+ */
66
+ export function conditionalFormattingsXml(formattings, styles) {
67
+ if (formattings.length === 0)
68
+ return '';
69
+ const priority = { next: 1 };
70
+ const extLinks = dataBarExtLinks(formattings);
71
+ return formattings.map((cf) => blockXml(cf, styles, priority, extLinks)).join('');
72
+ }
73
+ /**
74
+ * The worksheet `<extLst>` `<ext>` carrying the x14 data-bar extensions, or '' when no data bar needs
75
+ * one. Each extension's `<x14:cfRule id>` is read from the same {@link dataBarExtLinks} map the classic
76
+ * pass uses for the `<x14:id>` its cfRule carries, so the two ends of a link always match. Emitted bare
77
+ * (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single `<extLst>` beside the
78
+ * data-validation extension.
79
+ */
80
+ export function conditionalFormattingsExtXml(formattings) {
81
+ const extLinks = dataBarExtLinks(formattings);
82
+ const items = [];
83
+ for (const cf of formattings) {
84
+ for (const rule of cf.rules) {
85
+ const guid = extLinks.get(rule);
86
+ if (guid !== undefined)
87
+ items.push(x14DataBarXml(cf.ref, rule, guid));
88
+ }
89
+ }
90
+ if (items.length === 0)
91
+ return '';
92
+ return x14Ext(CF_EXT_URI, `<x14:conditionalFormattings>${items.join('')}</x14:conditionalFormattings>`);
93
+ }
94
+ // One `<x14:conditionalFormatting>`: an `<x14:cfRule type="dataBar" id>` mirroring the classic anchors
95
+ // as `<x14:cfvo>` and adding the facets the classic element cannot carry (gradient, negative-fill and
96
+ // axis colours), with the target range in an `<xm:sqref>` child — the shape Excel writes.
97
+ function x14DataBarXml(ref, rule, guid) {
98
+ const cfvo = rule.cfvo && rule.cfvo.length > 0 ? rule.cfvo : DEFAULT_DATABAR_CFVO;
99
+ const anchors = cfvo.map(x14CfvoXml).join('');
100
+ const gradient = boolAttr('gradient', rule.gradient);
101
+ const negative = rule.negativeFillColor !== undefined
102
+ ? `<x14:negativeFillColor ${colorAttrs(rule.negativeFillColor)}/>`
103
+ : '';
104
+ const axis = rule.axisColor !== undefined ? `<x14:axisColor ${colorAttrs(rule.axisColor)}/>` : '';
105
+ return (`<x14:conditionalFormatting xmlns:xm="${XM_NS}">` +
106
+ `<x14:cfRule type="dataBar" id="${guid}">` +
107
+ `<x14:dataBar${gradient}>${anchors}${negative}${axis}</x14:dataBar>` +
108
+ `</x14:cfRule><xm:sqref>${escapeText(ref)}</xm:sqref></x14:conditionalFormatting>`);
109
+ }
110
+ // An x14 scale anchor. A `min`/`max` carries no value and self-closes; the rest wrap their value in an
111
+ // `<xm:f>` (the extension form stores every anchor value as a formula).
112
+ function x14CfvoXml(cfvo) {
113
+ const type = escapeAttr(cfvo.type);
114
+ if (cfvo.value === undefined)
115
+ return `<x14:cfvo type="${type}"/>`;
116
+ return `<x14:cfvo type="${type}"><xm:f>${escapeText(String(cfvo.value))}</xm:f></x14:cfvo>`;
117
+ }
118
+ // The `<extLst>` a classic data-bar cfRule carries to name its x14 extension by shared id.
119
+ function cfRuleExtLinkXml(guid) {
120
+ return `<extLst>${x14Ext(DATABAR_LINK_EXT_URI, `<x14:id>${guid}</x14:id>`)}</extLst>`;
121
+ }
122
+ function blockXml(cf, styles, priority, extLinks) {
123
+ const rules = cf.rules.map((rule) => ruleXml(rule, styles, priority, extLinks)).join('');
124
+ return `<conditionalFormatting sqref="${escapeAttr(cf.ref)}">${rules}</conditionalFormatting>`;
125
+ }
126
+ function ruleXml(rule, styles, priority, extLinks) {
127
+ const p = rule.priority ?? priority.next;
128
+ // Keep the running counter ahead of any explicit priority so later auto-assigned ones stay unique.
129
+ priority.next = Math.max(priority.next, p) + 1;
130
+ const attrs = [`type="${escapeAttr(rule.type)}"`];
131
+ const dxfId = resolveDxfId(rule, styles);
132
+ if (dxfId !== undefined)
133
+ attrs.push(`dxfId="${dxfId}"`);
134
+ attrs.push(`priority="${p}"`);
135
+ if (rule.stopIfTrue)
136
+ attrs.push('stopIfTrue="1"');
137
+ if (rule.aboveAverage === false)
138
+ attrs.push('aboveAverage="0"');
139
+ if (rule.equalAverage)
140
+ attrs.push('equalAverage="1"');
141
+ if (rule.bottom)
142
+ attrs.push('bottom="1"');
143
+ if (rule.percent)
144
+ attrs.push('percent="1"');
145
+ if (rule.operator !== undefined)
146
+ attrs.push(`operator="${escapeAttr(rule.operator)}"`);
147
+ if (rule.text !== undefined)
148
+ attrs.push(`text="${escapeAttr(rule.text)}"`);
149
+ if (rule.timePeriod !== undefined)
150
+ attrs.push(`timePeriod="${escapeAttr(rule.timePeriod)}"`);
151
+ if (rule.rank !== undefined)
152
+ attrs.push(`rank="${rule.rank}"`);
153
+ if (rule.stdDev !== undefined)
154
+ attrs.push(`stdDev="${rule.stdDev}"`);
155
+ let body = SCALE_TYPES.has(rule.type) ? scaleXml(rule) : formulaeXml(rule.formulae);
156
+ // A data bar with x14-only facets links to its extension by the id assigned in dataBarExtLinks; the
157
+ // extension itself rides in the worksheet <extLst>. The link is the cfRule's last child, after the
158
+ // dataBar. A rule absent from the map carries no extension.
159
+ const extGuid = extLinks.get(rule);
160
+ if (extGuid !== undefined)
161
+ body += cfRuleExtLinkXml(extGuid);
162
+ return body === ''
163
+ ? `<cfRule ${attrs.join(' ')}/>`
164
+ : `<cfRule ${attrs.join(' ')}>${body}</cfRule>`;
165
+ }
166
+ // A rule points at a differential style either by a preserved index read from a file (`dxfId`) or by
167
+ // a style authored on the rule (interned here). The preserved index wins — it references the original
168
+ // file's dxf table, which the writer re-emits verbatim. Scale rules never carry one.
169
+ function resolveDxfId(rule, styles) {
170
+ if (SCALE_TYPES.has(rule.type))
171
+ return undefined;
172
+ if (rule.dxfId !== undefined)
173
+ return Number(rule.dxfId);
174
+ if (rule.style !== undefined)
175
+ return styles.differentialStyleId(rule.style);
176
+ return undefined;
177
+ }
178
+ function formulaeXml(formulae) {
179
+ if (formulae === undefined)
180
+ return '';
181
+ return formulae.map((f) => `<formula>${escapeText(stripFormulaEquals(f))}</formula>`).join('');
182
+ }
183
+ function scaleXml(rule) {
184
+ if (rule.type === 'dataBar')
185
+ return dataBarXml(rule);
186
+ if (rule.type === 'colorScale')
187
+ return colorScaleXml(rule);
188
+ return iconSetXml(rule);
189
+ }
190
+ // A data bar states its low and high anchors and its bar colour. The minimal call (no cfvo, no colour)
191
+ // gains Excel's own defaults — a min/max anchor pair and the standard blue — rather than an invalid
192
+ // empty element. The gradient flag and the negative-fill/axis colours have no home in this classic
193
+ // element; they ride in the x14 extension (see {@link conditionalFormattingsExtXml}), linked from the
194
+ // cfRule that wraps this by a shared id.
195
+ function dataBarXml(rule) {
196
+ const cfvo = rule.cfvo && rule.cfvo.length > 0 ? rule.cfvo : DEFAULT_DATABAR_CFVO;
197
+ const color = rule.color ?? DEFAULT_DATABAR_COLOR;
198
+ const anchors = cfvo.map(cfvoXml).join('');
199
+ return `<dataBar>${anchors}<color ${colorAttrs(color)}/></dataBar>`;
200
+ }
201
+ // A colour scale pairs each anchor with a colour; a missing colour list falls back to none, still a
202
+ // well-formed (if plain) element.
203
+ function colorScaleXml(rule) {
204
+ const anchors = (rule.cfvo ?? []).map(cfvoXml).join('');
205
+ const colors = (rule.colors ?? []).map((c) => `<color ${colorAttrs(c)}/>`).join('');
206
+ return `<colorScale>${anchors}${colors}</colorScale>`;
207
+ }
208
+ function iconSetXml(rule) {
209
+ const name = rule.iconSet !== undefined ? ` iconSet="${escapeAttr(rule.iconSet)}"` : '';
210
+ const anchors = (rule.cfvo ?? []).map(cfvoXml).join('');
211
+ return `<iconSet${name}>${anchors}</iconSet>`;
212
+ }
213
+ // One scale anchor. `min`/`max` carry no value; the rest state theirs in `val` (a formula anchor's
214
+ // value is its formula text, escaped like any attribute).
215
+ function cfvoXml(cfvo) {
216
+ const val = cfvo.value !== undefined ? ` val="${escapeAttr(String(cfvo.value))}"` : '';
217
+ return `<cfvo type="${escapeAttr(cfvo.type)}"${val}/>`;
218
+ }
219
+ /**
220
+ * Parse a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
221
+ * blocks supply every rule; the x14 extension (`<x14:conditionalFormatting>` inside `<extLst>`) is
222
+ * read only to enrich a classic data bar with the facets the classic element cannot carry — the
223
+ * gradient flag and the negative-fill and axis colours — matched by the shared id the two ends link
224
+ * on. An extension rule with no classic counterpart (a rule that lives only in x14) is ignored, so it
225
+ * is never half-read into a broken classic rule.
226
+ */
227
+ export function parseConditionalFormattings(xml) {
228
+ const blocks = [];
229
+ let block;
230
+ let draft;
231
+ let scale;
232
+ let capturingFormula = false;
233
+ let formulaText = '';
234
+ // Classic data-bar rules that named an extension, paired with the id they linked on, plus the
235
+ // extensions gathered from the worksheet <extLst>. The two are married after the pass — the
236
+ // extension always follows the classic blocks in document order, so it is known by then.
237
+ const linked = [];
238
+ const extById = new Map();
239
+ let x14Ext;
240
+ let x14ExtId;
241
+ let capturingX14Id = false;
242
+ let x14IdText = '';
243
+ parseXml(xml, {
244
+ onOpen(name, attrs, selfClosing) {
245
+ const ln = localName(name);
246
+ if (name.includes(':')) {
247
+ // The `<x14:id>` a classic data bar carries to name its extension: capture its text into the
248
+ // open draft. The rest are the worksheet extension's own elements.
249
+ if (ln === 'id' && draft !== undefined) {
250
+ capturingX14Id = true;
251
+ x14IdText = '';
252
+ }
253
+ else if (ln === 'cfRule') {
254
+ x14Ext = attrs.type === 'dataBar' && attrs.id !== undefined ? emptyExt() : undefined;
255
+ x14ExtId = attrs.id;
256
+ }
257
+ else if (x14Ext !== undefined && ln === 'dataBar') {
258
+ // gradient defaults to true in the x14 schema, so an absent attribute reads as a gradient.
259
+ x14Ext.gradient = attrs.gradient !== '0';
260
+ }
261
+ else if (x14Ext !== undefined && ln === 'negativeFillColor') {
262
+ x14Ext.negativeFillColor = parseColor(attrs);
263
+ }
264
+ else if (x14Ext !== undefined && ln === 'axisColor') {
265
+ x14Ext.axisColor = parseColor(attrs);
266
+ }
267
+ return;
268
+ }
269
+ if (ln === 'conditionalFormatting') {
270
+ block = { ref: attrs.sqref ?? '', rules: [] };
271
+ }
272
+ else if (ln === 'cfRule' && block !== undefined) {
273
+ // A rule with no operands (e.g. duplicateValues) is a self-closing element that fires no
274
+ // close event, so it must be finalised here; one with children waits for its </cfRule>.
275
+ if (selfClosing) {
276
+ block.rules.push(finalizeRule(newDraft(attrs)));
277
+ }
278
+ else {
279
+ draft = newDraft(attrs);
280
+ scale = undefined;
281
+ }
282
+ }
283
+ else if (draft !== undefined &&
284
+ (ln === 'dataBar' || ln === 'colorScale' || ln === 'iconSet')) {
285
+ scale = ln;
286
+ if (ln === 'iconSet' && attrs.iconSet !== undefined)
287
+ draft.iconSet = attrs.iconSet;
288
+ }
289
+ else if (draft !== undefined && ln === 'cfvo') {
290
+ draft.cfvo.push(parseCfvo(attrs));
291
+ }
292
+ else if (draft !== undefined && ln === 'color') {
293
+ const color = parseColor(attrs);
294
+ if (scale === 'dataBar')
295
+ draft.color = color;
296
+ else
297
+ draft.colors.push(color);
298
+ }
299
+ else if (draft !== undefined && ln === 'formula') {
300
+ capturingFormula = true;
301
+ formulaText = '';
302
+ }
303
+ },
304
+ onText(chunk) {
305
+ if (capturingFormula)
306
+ formulaText += chunk;
307
+ if (capturingX14Id)
308
+ x14IdText += chunk;
309
+ },
310
+ onClose(name) {
311
+ const ln = localName(name);
312
+ if (name.includes(':')) {
313
+ if (ln === 'id' && capturingX14Id) {
314
+ if (draft !== undefined)
315
+ draft.x14Id = x14IdText;
316
+ capturingX14Id = false;
317
+ }
318
+ else if (ln === 'cfRule' && x14Ext !== undefined && x14ExtId !== undefined) {
319
+ extById.set(x14ExtId, x14Ext);
320
+ x14Ext = undefined;
321
+ x14ExtId = undefined;
322
+ }
323
+ return;
324
+ }
325
+ if (ln === 'formula' && capturingFormula) {
326
+ if (draft !== undefined)
327
+ draft.formulae.push(coerceNumericLiteral(formulaText));
328
+ capturingFormula = false;
329
+ }
330
+ else if (ln === 'dataBar' || ln === 'colorScale' || ln === 'iconSet') {
331
+ scale = undefined;
332
+ }
333
+ else if (ln === 'cfRule' && draft !== undefined) {
334
+ const rule = finalizeRule(draft);
335
+ if (block !== undefined)
336
+ block.rules.push(rule);
337
+ if (draft.x14Id !== undefined)
338
+ linked.push({ rule, id: draft.x14Id });
339
+ draft = undefined;
340
+ }
341
+ else if (ln === 'conditionalFormatting' && block !== undefined) {
342
+ blocks.push(block);
343
+ block = undefined;
344
+ }
345
+ },
346
+ });
347
+ for (const { rule, id } of linked) {
348
+ const ext = extById.get(id);
349
+ if (ext === undefined)
350
+ continue;
351
+ if (ext.gradient !== undefined)
352
+ rule.gradient = ext.gradient;
353
+ if (ext.negativeFillColor !== undefined)
354
+ rule.negativeFillColor = ext.negativeFillColor;
355
+ if (ext.axisColor !== undefined)
356
+ rule.axisColor = ext.axisColor;
357
+ }
358
+ return blocks;
359
+ }
360
+ function emptyExt() {
361
+ return { gradient: undefined, negativeFillColor: undefined, axisColor: undefined };
362
+ }
363
+ /**
364
+ * Extract the differential-style (`<dxf>`) fragments from styles.xml, each verbatim. Preserving the
365
+ * raw XML — rather than reparsing and re-serialising — is what keeps a foreign dxf's number format a
366
+ * real format code on re-write instead of a coerced `"[object Object]"`, and keeps every conditional
367
+ * formatting's `dxfId` index pointing at the same style it did in the source file.
368
+ */
369
+ export function parseDxfs(stylesXml) {
370
+ const block = /<dxfs\b[^>]*>([\s\S]*?)<\/dxfs>/.exec(stylesXml);
371
+ if (block === null)
372
+ return [];
373
+ const inner = block[1] ?? '';
374
+ return [...inner.matchAll(/<dxf\b[^>]*>[\s\S]*?<\/dxf>|<dxf\b[^>]*\/>/g)].map((m) => m[0] ?? '');
375
+ }
376
+ function newDraft(attrs) {
377
+ return {
378
+ type: attrs.type ?? '',
379
+ priority: parseFiniteAttr(attrs.priority),
380
+ stopIfTrue: boolStrict(attrs.stopIfTrue),
381
+ operator: attrs.operator,
382
+ text: attrs.text,
383
+ timePeriod: attrs.timePeriod,
384
+ rank: parseFiniteAttr(attrs.rank),
385
+ stdDev: parseFiniteAttr(attrs.stdDev),
386
+ percent: boolStrict(attrs.percent),
387
+ bottom: boolStrict(attrs.bottom),
388
+ // aboveAverage defaults to true in OOXML; only an explicit "0" means below-average.
389
+ aboveAverage: attrs.aboveAverage === undefined ? undefined : attrs.aboveAverage !== '0',
390
+ equalAverage: boolStrict(attrs.equalAverage),
391
+ dxfId: parseIndexAttr(attrs.dxfId),
392
+ iconSet: undefined,
393
+ formulae: [],
394
+ cfvo: [],
395
+ colors: [],
396
+ color: undefined,
397
+ x14Id: undefined,
398
+ };
399
+ }
400
+ // priority/rank/stdDev must be finite; a malformed value is dropped rather than propagated as NaN —
401
+ // `priority` in particular feeds the writer's running priority counter (see `ruleXml`), so one bad
402
+ // value would otherwise poison every later rule's auto-assigned priority on the same sheet.
403
+ function parseFiniteAttr(value) {
404
+ if (value === undefined)
405
+ return undefined;
406
+ const n = Number(value);
407
+ return Number.isFinite(n) ? n : undefined;
408
+ }
409
+ // dxfId is preserved as the raw string (not renumbered) so it keeps pointing at the same slot in the
410
+ // dxf table on re-write; it must still be a non-negative integer, so a malformed value is dropped
411
+ // rather than later coercing to `dxfId="NaN"` in {@link resolveDxfId}.
412
+ function parseIndexAttr(value) {
413
+ if (value === undefined)
414
+ return undefined;
415
+ const n = Number(value);
416
+ return Number.isInteger(n) && n >= 0 ? value : undefined;
417
+ }
418
+ function finalizeRule(draft) {
419
+ const rule = { type: draft.type };
420
+ if (draft.priority !== undefined)
421
+ rule.priority = draft.priority;
422
+ if (draft.stopIfTrue)
423
+ rule.stopIfTrue = true;
424
+ if (draft.operator !== undefined)
425
+ rule.operator = draft.operator;
426
+ if (draft.text !== undefined)
427
+ rule.text = draft.text;
428
+ if (draft.timePeriod !== undefined)
429
+ rule.timePeriod = draft.timePeriod;
430
+ if (draft.rank !== undefined)
431
+ rule.rank = draft.rank;
432
+ if (draft.stdDev !== undefined)
433
+ rule.stdDev = draft.stdDev;
434
+ if (draft.percent)
435
+ rule.percent = true;
436
+ if (draft.bottom)
437
+ rule.bottom = true;
438
+ if (draft.aboveAverage !== undefined)
439
+ rule.aboveAverage = draft.aboveAverage;
440
+ if (draft.equalAverage)
441
+ rule.equalAverage = true;
442
+ if (draft.dxfId !== undefined)
443
+ rule.dxfId = draft.dxfId;
444
+ if (draft.iconSet !== undefined)
445
+ rule.iconSet = draft.iconSet;
446
+ if (draft.formulae.length > 0)
447
+ rule.formulae = draft.formulae;
448
+ if (draft.cfvo.length > 0)
449
+ rule.cfvo = draft.cfvo;
450
+ if (draft.colors.length > 0)
451
+ rule.colors = draft.colors;
452
+ if (draft.color !== undefined)
453
+ rule.color = draft.color;
454
+ return rule;
455
+ }
456
+ function parseCfvo(attrs) {
457
+ const type = (attrs.type ?? 'num');
458
+ const cfvo = { type };
459
+ if (attrs.val !== undefined) {
460
+ // A `formula` anchor's value is an expression and stays a string; the rest are numeric.
461
+ cfvo.value = type === 'formula' ? attrs.val : coerceNumericLiteral(attrs.val);
462
+ }
463
+ return cfvo;
464
+ }
@@ -0,0 +1,20 @@
1
+ import type { DataValidationEntry } from '../../core/data-validation.ts';
2
+ import type { Worksheet } from '../../core/worksheet.ts';
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.
5
+ * The extended rules are emitted separately by {@link dataValidationsExtXml}. */
6
+ export declare function dataValidationsXml(entries: readonly DataValidationEntry[]): string;
7
+ /** The `<ext>` carrying the extended (`<x14:dataValidation>`) rules, or '' when the sheet declares
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. */
10
+ export declare function dataValidationsExtXml(entries: readonly DataValidationEntry[]): string;
11
+ /** Parse every standard `<dataValidation>` out of a worksheet part into range-bound rules. */
12
+ export declare function parseDataValidations(xml: string): DataValidationEntry[];
13
+ /** Parse every extended `<x14:dataValidation>` out of a worksheet's `<extLst>` into range-bound
14
+ * rules tagged `extended`, so a cross-sheet or whole-column list validation Excel stored only in the
15
+ * 2009 extension form is read back rather than dropped. The standard parser ignores these (they are
16
+ * prefixed); this one, symmetrically, handles only the prefixed elements. */
17
+ export declare function parseExtendedDataValidations(xml: string): DataValidationEntry[];
18
+ /** Fold parsed validations onto a sheet, each bound to its original range and carrying its form: an
19
+ * `extended` entry is re-attached as extended so a round-trip writes it back to the x14 block. */
20
+ export declare function applyDataValidations(sheet: Worksheet, entries: readonly DataValidationEntry[]): void;