@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,8 @@
1
+ import { type Font } from '../../core/style.ts';
2
+ import { type XmlAttributes } from '../../xml/xml-read.ts';
3
+ import { type StyleTable } from '../style/xf-style.ts';
4
+ export type FontDraft = {
5
+ -readonly [K in keyof Font]?: Font[K];
6
+ };
7
+ export declare function parseStyleTable(xml: string): StyleTable;
8
+ export declare function applyFontChild(draft: FontDraft, local: string, attrs: XmlAttributes): void;
@@ -0,0 +1,551 @@
1
+ // The style-table reader: `xl/styles.xml` in, a flat table of resolved cell formats out. It is a
2
+ // single streaming pass over the shared sub-tables (`<numFmts>`, `<fills>`, `<fonts>`, `<borders>`)
3
+ // and the two xf tables (`<cellXfs>`, `<cellStyleXfs>`), flattening the id-indirection so a cell's
4
+ // `s` index maps straight to its facets. A construct it does not recognise is skipped, never guessed.
5
+ //
6
+ // Only the parsing is XML-specific. What an xf resolves *to* — `XfStyle`, the built-in number
7
+ // formats, applying an xf to a cell — is a property of the OOXML style model rather than of its
8
+ // spelling, and lives above both codecs in `../style/xf-style.ts`; the `.xlsb` style reader builds
9
+ // the same table from BIFF12 records.
10
+ import { assignStyleFacets, isBorderStyle, isFillPatternType, isFontScheme, isFontVerticalAlignment, isHorizontalAlignment, isNamedUnderlineStyle, isVerticalAlignment, } from "../../core/style.js";
11
+ import { boolPresent, boolStrict, closeEmptyElements, localName, xmlEvents, } from "../../xml/xml-read.js";
12
+ import { numFmtCodeFor } from "../style/xf-style.js";
13
+ import { parseColor } from "./styles.js";
14
+ // The four sides plus the diagonal — the edge elements a <border> can hold, in the order the
15
+ // schema lists them. This one tuple drives the edge-name union, the membership set (which drives
16
+ // edge parsing without a per-name branch), and the "does any edge carry a style" scan below.
17
+ const BORDER_EDGE_NAMES = ['left', 'right', 'top', 'bottom', 'diagonal'];
18
+ const BORDER_EDGES = new Set(BORDER_EDGE_NAMES);
19
+ // Style-table elements that commit on their close: a bare <font/>/<border/>/<patternFill/>/
20
+ // <gradientFill/>/<xf/> or a self-closing border edge is expanded to open+close so each commits
21
+ // exactly once in onClose, never in a duplicated (and easily-forgotten) self-closing branch.
22
+ const STYLE_EMPTY_CLOSES = new Set([
23
+ 'font',
24
+ 'border',
25
+ 'patternFill',
26
+ 'gradientFill',
27
+ 'xf',
28
+ ...BORDER_EDGES,
29
+ ]);
30
+ // styles.xml is a shared table: <numFmts> defines custom format codes by id, <fills> lists
31
+ // the fills, and <cellXfs> lists the cell formats, each naming a fill and a number format by
32
+ // id. We flatten that indirection into one array — cellXfs index → resolved {fill, numFmt} —
33
+ // so a cell/row/column style index maps straight to its facets. The schema orders <numFmts>
34
+ // and <fills> before <cellXfs>, so both lookups are complete before an xf references them.
35
+ export function parseStyleTable(xml) {
36
+ if (xml === '')
37
+ return { cellXfs: [], namedStyles: [] };
38
+ let fills = [];
39
+ let fonts = [];
40
+ let borders = [];
41
+ let numFmtCodes = new Map();
42
+ const xfStyles = [];
43
+ // The named-style layer: <cellStyleXfs> holds the base formats a cell's xfId links to; <cellStyles>
44
+ // labels them by name/builtinId. Parsed in parallel with cellXfs, then zipped and merged below.
45
+ const namedXfs = [];
46
+ let cellStyleNames = [];
47
+ // One streaming pass, but each top-level sub-table drives its own focused sub-parser over the slice
48
+ // of events between its open and close. The schema orders the shared tables (<numFmts>, <fonts>,
49
+ // <fills>, <borders>) before the xf tables, so their results are complete before an <xf> resolves
50
+ // against them. Every recognised container name is plural and unique to the styleSheet root — none
51
+ // appears inside a <dxf>'s singular <font>/<fill>/<border> children — so skipping an unrecognised
52
+ // section here drops exactly what the old flat pass gated off with its `in*` flags.
53
+ const events = closeEmptyElements(xmlEvents(xml), STYLE_EMPTY_CLOSES);
54
+ let next = events.next();
55
+ while (next.done !== true) {
56
+ const event = next.value;
57
+ if (event.kind === 'open' && !event.selfClosing) {
58
+ switch (localName(event.name)) {
59
+ case 'numFmts':
60
+ numFmtCodes = parseNumFmts(events);
61
+ break;
62
+ case 'fonts':
63
+ fonts = parseFonts(events);
64
+ break;
65
+ case 'fills':
66
+ fills = parseFills(events);
67
+ break;
68
+ case 'borders':
69
+ borders = parseBorders(events);
70
+ break;
71
+ case 'cellStyleXfs':
72
+ namedXfs.push(...parseXfTable(events, 'cellStyleXfs', { fills, fonts, borders, numFmtCodes }));
73
+ break;
74
+ case 'cellXfs':
75
+ xfStyles.push(...parseXfTable(events, 'cellXfs', { fills, fonts, borders, numFmtCodes }));
76
+ break;
77
+ case 'cellStyles':
78
+ cellStyleNames = parseCellStyles(events);
79
+ break;
80
+ }
81
+ }
82
+ next = events.next();
83
+ }
84
+ // Layer each cellXfs entry over the named style its xfId links to: a facet the direct format sets
85
+ // wins; one it leaves unset falls through to the named style. The xfId is carried through so the
86
+ // link survives a re-write. A draft only holds keys for facets it actually set, so the spread merge
87
+ // takes the named base and lets the direct entry override exactly what it names.
88
+ const cellXfs = xfStyles.map((xf) => {
89
+ if (xf.xfId === undefined)
90
+ return xf;
91
+ const named = namedXfs[xf.xfId];
92
+ return named === undefined ? xf : { ...named, ...xf };
93
+ });
94
+ // Zip the resolved cellStyleXfs facets with their cellStyles name/builtinId into the model's named
95
+ // styles, index for index (a cellStyle's xfId is its cellStyleXfs index).
96
+ const namedStyles = namedXfs.map((xf, index) => {
97
+ const label = cellStyleNames.find((entry) => entry.xfId === index);
98
+ const style = {};
99
+ assignStyleFacets(style, xf);
100
+ if (label?.name !== undefined)
101
+ style.name = label.name;
102
+ if (label?.builtinId !== undefined)
103
+ style.builtinId = label.builtinId;
104
+ return style;
105
+ });
106
+ // Font 0 is the workbook's declared default, so it is carried out whole as well as flattened onto
107
+ // the xfs that name it — see {@link StyleTable.defaultFont}.
108
+ const defaultFont = fonts[0];
109
+ return defaultFont === undefined ? { cellXfs, namedStyles } : { cellXfs, namedStyles, defaultFont };
110
+ }
111
+ // Pull events off the shared stream up to — and consuming — the close of `container`, yielding only
112
+ // those strictly inside it. A sub-table parser loops this to completion (never breaking), so it drives
113
+ // its own small state machine over exactly its section without closing the underlying generator, and
114
+ // the outer pass resumes at the element after the container's close.
115
+ function* until(events, container) {
116
+ let next = events.next();
117
+ while (next.done !== true) {
118
+ const event = next.value;
119
+ if (event.kind === 'close' && localName(event.name) === container)
120
+ return;
121
+ yield event;
122
+ next = events.next();
123
+ }
124
+ }
125
+ // <numFmts> entries are self-closing, so they are read on open. A code with no id, or the General
126
+ // id 0, contributes nothing.
127
+ function parseNumFmts(events) {
128
+ const codes = new Map();
129
+ for (const event of until(events, 'numFmts')) {
130
+ if (event.kind === 'open' && localName(event.name) === 'numFmt') {
131
+ const id = Number(event.attrs.numFmtId);
132
+ if (Number.isInteger(id) && id > 0 && event.attrs.formatCode !== undefined) {
133
+ codes.set(id, event.attrs.formatCode);
134
+ }
135
+ }
136
+ }
137
+ return codes;
138
+ }
139
+ function parseFonts(events) {
140
+ const fonts = [];
141
+ let fontDraft = null;
142
+ for (const event of until(events, 'fonts')) {
143
+ if (event.kind === 'open') {
144
+ const local = localName(event.name);
145
+ // A <font>'s children are self-closing, so they are read here on open.
146
+ if (local === 'font')
147
+ fontDraft = {};
148
+ else if (fontDraft !== null)
149
+ applyFontChild(fontDraft, local, event.attrs);
150
+ }
151
+ else if (event.kind === 'close' && localName(event.name) === 'font' && fontDraft !== null) {
152
+ fonts.push(Object.keys(fontDraft).length > 0 ? fontDraft : undefined);
153
+ fontDraft = null;
154
+ }
155
+ }
156
+ return fonts;
157
+ }
158
+ function parseFills(events) {
159
+ const fills = [];
160
+ let pattern = '';
161
+ let fgColor;
162
+ let bgColor;
163
+ // A gradient fill accumulates from <gradientFill> open to close; its stops fill in as <stop>/<color>
164
+ // pairs arrive. `fillSlotAt` marks where in `fills` the current <fill> began, so its close can keep a
165
+ // slot even when the fill body was neither a pattern nor a gradient — index alignment is load-bearing.
166
+ let gradientDraft = null;
167
+ let fillSlotAt = -1;
168
+ for (const event of until(events, 'fills')) {
169
+ if (event.kind === 'open') {
170
+ const attrs = event.attrs;
171
+ switch (localName(event.name)) {
172
+ case 'fill':
173
+ // Mark where this <fill> starts so its close can guarantee exactly one slot — a fill body
174
+ // that is neither <patternFill> nor <gradientFill> (or a gradient we could not parse) must
175
+ // still consume an id, or every later fill index shifts and cells mis-resolve their fill.
176
+ fillSlotAt = fills.length;
177
+ break;
178
+ case 'patternFill':
179
+ pattern = attrs.patternType ?? 'none';
180
+ fgColor = undefined;
181
+ bgColor = undefined;
182
+ break;
183
+ case 'fgColor':
184
+ fgColor = parseColor(attrs);
185
+ break;
186
+ case 'bgColor':
187
+ bgColor = parseColor(attrs);
188
+ break;
189
+ case 'gradientFill':
190
+ gradientDraft = {
191
+ fill: {
192
+ type: 'gradient',
193
+ gradient: attrs.type === 'path' ? 'path' : 'linear',
194
+ stops: [],
195
+ },
196
+ stopPosition: null,
197
+ stopColor: undefined,
198
+ };
199
+ assignGradientNumbers(gradientDraft.fill, attrs);
200
+ break;
201
+ case 'stop':
202
+ if (gradientDraft !== null) {
203
+ const position = Number(attrs.position);
204
+ gradientDraft.stopPosition = Number.isFinite(position) ? position : 0;
205
+ gradientDraft.stopColor = undefined;
206
+ }
207
+ break;
208
+ case 'color':
209
+ // The colour of the open <stop>; committed to a GradientStop when the stop closes.
210
+ if (gradientDraft !== null)
211
+ gradientDraft.stopColor = parseColor(attrs);
212
+ break;
213
+ }
214
+ }
215
+ else if (event.kind === 'close') {
216
+ switch (localName(event.name)) {
217
+ case 'patternFill':
218
+ fills.push(toFill(pattern, fgColor, bgColor));
219
+ break;
220
+ case 'stop':
221
+ if (gradientDraft !== null && gradientDraft.stopPosition !== null) {
222
+ const stop = {
223
+ position: gradientDraft.stopPosition,
224
+ color: gradientDraft.stopColor ?? {},
225
+ };
226
+ gradientDraft.fill.stops = [...gradientDraft.fill.stops, stop];
227
+ gradientDraft.stopPosition = null;
228
+ gradientDraft.stopColor = undefined;
229
+ }
230
+ break;
231
+ case 'gradientFill':
232
+ if (gradientDraft !== null) {
233
+ fills.push(gradientDraft.fill);
234
+ gradientDraft = null;
235
+ }
236
+ break;
237
+ case 'fill':
238
+ // Backstop the slot: if this <fill>'s body pushed nothing (unparsed/unknown content), keep an
239
+ // empty slot so id alignment holds and later fills still resolve to the right cells.
240
+ if (fills.length === fillSlotAt)
241
+ fills.push(undefined);
242
+ break;
243
+ }
244
+ }
245
+ }
246
+ return fills;
247
+ }
248
+ function parseBorders(events) {
249
+ const borders = [];
250
+ let borderDraft = null;
251
+ // Which edge of the current border a nested <color> belongs to; null between edges.
252
+ let currentEdge = null;
253
+ for (const event of until(events, 'borders')) {
254
+ if (event.kind === 'open') {
255
+ const local = localName(event.name);
256
+ const attrs = event.attrs;
257
+ if (local === 'border') {
258
+ borderDraft = {};
259
+ currentEdge = null;
260
+ if (boolStrict(attrs.diagonalUp))
261
+ borderDraft.diagonalUp = true;
262
+ if (boolStrict(attrs.diagonalDown))
263
+ borderDraft.diagonalDown = true;
264
+ }
265
+ else if (borderDraft !== null) {
266
+ // A border's edges and their <color> children are all read on open (each is self-closing bar a
267
+ // coloured edge, whose colour child is itself self-closing). An edge whose style is absent or
268
+ // an unrecognised token is dropped — the side simply carries no border.
269
+ if (BORDER_EDGES.has(local)) {
270
+ if (attrs.style !== undefined && isBorderStyle(attrs.style)) {
271
+ currentEdge = local;
272
+ borderDraft[currentEdge] = { style: attrs.style };
273
+ }
274
+ else {
275
+ currentEdge = null;
276
+ }
277
+ }
278
+ else if (local === 'color' && currentEdge !== null) {
279
+ const edge = borderDraft[currentEdge];
280
+ if (edge !== undefined)
281
+ borderDraft[currentEdge] = { style: edge.style, color: parseColor(attrs) };
282
+ }
283
+ }
284
+ }
285
+ else if (event.kind === 'close') {
286
+ const local = localName(event.name);
287
+ if (local === 'border') {
288
+ if (borderDraft !== null) {
289
+ borders.push(borderToStyle(borderDraft));
290
+ borderDraft = null;
291
+ currentEdge = null;
292
+ }
293
+ }
294
+ else if (borderDraft !== null && BORDER_EDGES.has(local)) {
295
+ // A coloured edge closes after its <color> child; drop the edge context so a stray later
296
+ // <color> cannot attach to it.
297
+ currentEdge = null;
298
+ }
299
+ }
300
+ }
301
+ return borders;
302
+ }
303
+ // Both <cellXfs> and <cellStyleXfs> hold <xf> with identical structure; they differ only in which
304
+ // table the result lands in and whether an xfId link is meaningful (only cellXfs entries link to a
305
+ // named style). One parser serves both, told by `container` which it is reading.
306
+ function parseXfTable(events, container, deps) {
307
+ const xfs = [];
308
+ const captureXfId = container === 'cellXfs';
309
+ // The xf being read; held from open to close so its <alignment>/<protection> children can attach
310
+ // before it is committed. null outside an <xf>.
311
+ let pendingXf = null;
312
+ for (const event of until(events, container)) {
313
+ if (event.kind === 'open') {
314
+ const local = localName(event.name);
315
+ if (local === 'xf') {
316
+ // Hold the xf open until its close so an <alignment>/<protection> child can attach first; a
317
+ // self-closing <xf/> is expanded to a close, so it commits there too, child-free.
318
+ pendingXf = resolveXf(event.attrs, deps, captureXfId);
319
+ }
320
+ else if (pendingXf !== null && local === 'alignment') {
321
+ // An xf's <alignment> child arrives before the xf closes; attach it to the pending xf.
322
+ const alignment = parseAlignment(event.attrs);
323
+ if (alignment !== undefined)
324
+ pendingXf.alignment = alignment;
325
+ }
326
+ else if (pendingXf !== null && local === 'protection') {
327
+ // An xf's <protection> child likewise arrives before the xf closes.
328
+ const protection = parseProtection(event.attrs);
329
+ if (protection !== undefined)
330
+ pendingXf.protection = protection;
331
+ }
332
+ }
333
+ else if (event.kind === 'close' && localName(event.name) === 'xf' && pendingXf !== null) {
334
+ xfs.push(pendingXf);
335
+ pendingXf = null;
336
+ }
337
+ }
338
+ return xfs;
339
+ }
340
+ // Resolve an <xf>'s facet ids against the shared sub-tables into a draft. `captureXfId` is set only
341
+ // for cellXfs entries, the sole table whose xfId links to a named style.
342
+ function resolveXf(attrs, deps, captureXfId) {
343
+ const fillId = Number(attrs.fillId);
344
+ const fill = Number.isInteger(fillId) ? deps.fills[fillId] : undefined;
345
+ const fontId = Number(attrs.fontId);
346
+ // Font id 0 is the workbook default font (a real Calibri-11-style face), not an absence — unlike
347
+ // border id 0, which is a genuinely empty border. So an xf naming font 0 resolves to that default
348
+ // face, giving every cell a concrete font to render.
349
+ const font = Number.isInteger(fontId) ? deps.fonts[fontId] : undefined;
350
+ const borderId = Number(attrs.borderId);
351
+ // Border id 0 is the empty default; only a custom border (id > 0) is an explicit one.
352
+ const border = Number.isInteger(borderId) && borderId > 0 ? deps.borders[borderId] : undefined;
353
+ const numFmt = resolveNumFmt(attrs.numFmtId, deps.numFmtCodes);
354
+ const draft = {};
355
+ if (fill)
356
+ draft.fill = fill;
357
+ if (numFmt !== undefined)
358
+ draft.numFmt = numFmt;
359
+ if (font)
360
+ draft.font = font;
361
+ if (border)
362
+ draft.border = border;
363
+ // The quote-prefix flag is an attribute on the xf itself (no shared sub-table); carry it only when
364
+ // set so an ordinary cell does not gain a spurious `quotePrefix: false`.
365
+ if (boolStrict(attrs.quotePrefix))
366
+ draft.quotePrefix = true;
367
+ // A cellXfs entry's xfId links it to a named style; capture it only when it points beyond the Normal
368
+ // default (0), so an ordinary cell carries no spurious named-style link.
369
+ if (captureXfId && attrs.xfId !== undefined) {
370
+ const xfId = Number(attrs.xfId);
371
+ if (Number.isInteger(xfId) && xfId > 0)
372
+ draft.xfId = xfId;
373
+ }
374
+ return draft;
375
+ }
376
+ // A <cellStyle> (inside <cellStyles>) names a cellStyleXfs entry by xfId; it is self-closing, so it
377
+ // is read on open.
378
+ function parseCellStyles(events) {
379
+ const names = [];
380
+ for (const event of until(events, 'cellStyles')) {
381
+ if (event.kind === 'open' && localName(event.name) === 'cellStyle') {
382
+ const attrs = event.attrs;
383
+ const xfId = Number(attrs.xfId);
384
+ if (Number.isInteger(xfId)) {
385
+ const entry = { xfId };
386
+ if (attrs.name !== undefined)
387
+ entry.name = attrs.name;
388
+ if (attrs.builtinId !== undefined) {
389
+ const builtinId = Number(attrs.builtinId);
390
+ if (Number.isInteger(builtinId))
391
+ entry.builtinId = builtinId;
392
+ }
393
+ names.push(entry);
394
+ }
395
+ }
396
+ }
397
+ return names;
398
+ }
399
+ // A <font> child element sets one facet on the draft. Boolean flags honour their `val`: a
400
+ // bare tag or val="1"/"true" is on, val="0"/"false" is off (an explicit-false flag is not
401
+ // truthy merely because the tag is present). An unrecognised child is ignored.
402
+ export function applyFontChild(draft, local, attrs) {
403
+ switch (local) {
404
+ case 'b':
405
+ draft.bold = boolPresent(attrs.val);
406
+ break;
407
+ case 'i':
408
+ draft.italic = boolPresent(attrs.val);
409
+ break;
410
+ case 'strike':
411
+ draft.strike = boolPresent(attrs.val);
412
+ break;
413
+ case 'outline':
414
+ draft.outline = boolPresent(attrs.val);
415
+ break;
416
+ case 'u':
417
+ // A bare <u/> is a single underline; a named style (single/double/…) carries through; but
418
+ // val="none" is the explicit ABSENCE of an underline, so it must read back falsy — not the
419
+ // truthy string "none" that a consumer's `if (font.underline)` would mistake for underlined. An
420
+ // unrecognised token keeps the "is underlined" fact but drops the unknown style (a plain true).
421
+ draft.underline =
422
+ attrs.val === undefined
423
+ ? true
424
+ : attrs.val === 'none'
425
+ ? false
426
+ : isNamedUnderlineStyle(attrs.val)
427
+ ? attrs.val
428
+ : true;
429
+ break;
430
+ case 'vertAlign':
431
+ if (attrs.val !== undefined && isFontVerticalAlignment(attrs.val))
432
+ draft.vertAlign = attrs.val;
433
+ break;
434
+ case 'sz': {
435
+ const size = Number(attrs.val);
436
+ if (Number.isFinite(size))
437
+ draft.size = size;
438
+ break;
439
+ }
440
+ case 'color':
441
+ draft.color = parseColor(attrs);
442
+ break;
443
+ // `<name>` in a styles `<font>`, `<rFont>` in a rich-text run's `<rPr>` — the same font face.
444
+ case 'name':
445
+ case 'rFont':
446
+ if (attrs.val !== undefined)
447
+ draft.name = attrs.val;
448
+ break;
449
+ case 'family': {
450
+ const family = Number(attrs.val);
451
+ if (Number.isInteger(family))
452
+ draft.family = family;
453
+ break;
454
+ }
455
+ case 'charset': {
456
+ const charset = Number(attrs.val);
457
+ if (Number.isInteger(charset))
458
+ draft.charset = charset;
459
+ break;
460
+ }
461
+ case 'scheme':
462
+ if (attrs.val !== undefined && isFontScheme(attrs.val))
463
+ draft.scheme = attrs.val;
464
+ break;
465
+ default:
466
+ break;
467
+ }
468
+ }
469
+ // An xf's numFmtId resolves against the custom codes first, then the built-in table; the
470
+ // General format (id 0) and any unrecognised id mean the cell carries no explicit format.
471
+ function resolveNumFmt(raw, custom) {
472
+ return raw === undefined ? undefined : numFmtCodeFor(Number(raw), custom);
473
+ }
474
+ function toFill(pattern, fgColor, bgColor) {
475
+ // `none` (and an absent patternType) is the absence of a fill; an unrecognised token is dropped the
476
+ // same way — like the border-edge style above — so a foreign pattern we do not model leaves the cell
477
+ // unfilled rather than propagating a token the writer would later re-emit unvalidated.
478
+ if (!isFillPatternType(pattern) || pattern === 'none')
479
+ return undefined;
480
+ return {
481
+ type: 'pattern',
482
+ pattern,
483
+ ...(fgColor ? { fgColor } : {}),
484
+ ...(bgColor ? { bgColor } : {}),
485
+ };
486
+ }
487
+ // Copy the numeric <gradientFill> attributes (degree; the path insets) onto a gradient draft, keeping
488
+ // only the finite ones so an absent or malformed attribute leaves the field its OOXML default (unset).
489
+ function assignGradientNumbers(fill, attrs) {
490
+ for (const key of ['degree', 'left', 'right', 'top', 'bottom']) {
491
+ const value = Number(attrs[key]);
492
+ if (attrs[key] !== undefined && Number.isFinite(value))
493
+ fill[key] = value;
494
+ }
495
+ }
496
+ // An accumulated border with no styled edge and no diagonal direction is the empty default:
497
+ // it carries nothing, so it resolves to undefined rather than an all-empty Border object.
498
+ function borderToStyle(draft) {
499
+ const hasEdge = BORDER_EDGE_NAMES.some((edge) => draft[edge] !== undefined);
500
+ if (!hasEdge && draft.diagonalUp === undefined && draft.diagonalDown === undefined)
501
+ return undefined;
502
+ return draft;
503
+ }
504
+ // Read an <alignment> element's attributes into an Alignment, keeping only facets that differ
505
+ // from the default. Boolean flags honour their parsed value — wrapText="0" is off, so it must
506
+ // not fabricate a { wrapText: false } alignment — and an element carrying only defaults yields
507
+ // undefined rather than an empty alignment object.
508
+ function parseAlignment(attrs) {
509
+ const out = {};
510
+ // `general` is the default and reads back as no explicit horizontal alignment; an unrecognised
511
+ // token (like an out-of-enum vertical one) is dropped rather than trusted into the model.
512
+ if (attrs.horizontal !== undefined &&
513
+ attrs.horizontal !== 'general' &&
514
+ isHorizontalAlignment(attrs.horizontal)) {
515
+ out.horizontal = attrs.horizontal;
516
+ }
517
+ if (attrs.vertical !== undefined && isVerticalAlignment(attrs.vertical))
518
+ out.vertical = attrs.vertical;
519
+ if (attrs.textRotation !== undefined) {
520
+ const rotation = Number(attrs.textRotation);
521
+ if (Number.isFinite(rotation) && rotation !== 0)
522
+ out.textRotation = rotation;
523
+ }
524
+ if (boolStrict(attrs.wrapText))
525
+ out.wrapText = true;
526
+ if (attrs.indent !== undefined) {
527
+ const indent = Number(attrs.indent);
528
+ if (Number.isInteger(indent) && indent !== 0)
529
+ out.indent = indent;
530
+ }
531
+ if (boolStrict(attrs.shrinkToFit))
532
+ out.shrinkToFit = true;
533
+ if (attrs.readingOrder !== undefined) {
534
+ const order = Number(attrs.readingOrder);
535
+ if (Number.isInteger(order) && order !== 0)
536
+ out.readingOrder = order;
537
+ }
538
+ return Object.keys(out).length > 0 ? out : undefined;
539
+ }
540
+ // Read a <protection> element into a Protection, keeping only facets that differ from the OOXML
541
+ // default. `locked` defaults to TRUE, so only an explicit `locked="0"` carries information (an
542
+ // unlocked cell) — a default or explicit-true cell must not read back as { locked: true }; `hidden`
543
+ // defaults to false, so only `hidden="1"` is carried. An element with only defaults yields undefined.
544
+ function parseProtection(attrs) {
545
+ const out = {};
546
+ if (attrs.locked === '0' || attrs.locked === 'false')
547
+ out.locked = false;
548
+ if (boolStrict(attrs.hidden))
549
+ out.hidden = true;
550
+ return Object.keys(out).length > 0 ? out : undefined;
551
+ }
@@ -0,0 +1,4 @@
1
+ import type { Worksheet } from '../../core/worksheet.ts';
2
+ import type { XfStyle } from '../style/xf-style.ts';
3
+ import type { SharedString } from './cell-value.ts';
4
+ export declare function parseWorksheet(xml: string, sheet: Worksheet, sharedStrings: readonly SharedString[], xfStyles: ReadonlyArray<XfStyle>): void;